001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.system.audit;
020
021import java.io.IOException;
022import java.util.HashMap;
023import java.util.Map;
024
025import org.w3c.dom.Document;
026import org.w3c.dom.Node;
027
028import icy.file.FileUtil;
029import icy.gui.frame.progress.CancelableProgressFrame;
030import icy.gui.main.MainFrame;
031import icy.main.Icy;
032import icy.network.NetworkUtil;
033import icy.plugin.abstract_.Plugin;
034import icy.preferences.ApplicationPreferences;
035import icy.preferences.GeneralPreferences;
036import icy.preferences.XMLPreferences;
037import icy.system.IcyExceptionHandler;
038import icy.system.SystemUtil;
039import icy.system.thread.ThreadUtil;
040import icy.util.StringUtil;
041import icy.util.XMLUtil;
042
043/**
044 * General audit tools class.
045 * 
046 * @author Stephane
047 */
048public class Audit
049{
050    // network URL
051    static final String URL_REGISTER = NetworkUtil.WEBSITE_URL + "register/registerClient.php?";
052    static final String URL_LINK_USER = NetworkUtil.WEBSITE_URL + "register/linkUser.php?";
053    static final String URL_GET_USER_INFO = NetworkUtil.WEBSITE_URL + "register/getLinkedUserInfo.php?";
054    static final String URL_AUDIT_VERSION = NetworkUtil.WEBSITE_URL + "register/auditVersion.php?";
055    static final String URL_AUDIT_PLUGIN = NetworkUtil.WEBSITE_URL + "register/auditPlugin.php?";
056
057    // prefs & network id
058    static final String ID_REQUEST = "requestId";
059    static final String ID_ACTION = "action";
060    static final String ID_ICY_ID = "IcyId";
061    static final String ID_CLIENT_ARCH = "clientArch";
062    static final String ID_CLIENT_ID = "clientId";
063    static final String ID_CLIENT_VERSION = "clientVersion";
064    static final String ID_CLIENT_CPUNUMBER = "clientCpuNumber";
065    static final String ID_CLIENT_TOTAL_MEMORY = "clientTotalMemory";
066    static final String ID_CLIENT_MAXJAVA_MEMORY = "clientMaxJavaMemory";
067    static final String ID_JAVA_NAME = "javaName";
068    static final String ID_JAVA_VERSION = "javaVersion";
069    static final String ID_JAVA_ARCH = "javaArch";
070    static final String ID_LAST_UPLOAD_DATE = "lastUploadDate";
071
072    // xml id
073    static final String XMLID_CLIENT_ID_REQUESTED = "client_id_requested";
074    static final String XMLID_USER_LOGIN = "user_login";
075    static final String XMLID_USER_NAME = "user_name";
076
077    // directly use application preferences here
078    static XMLPreferences prefs;
079
080    static AuditStorage storage;
081    private static boolean initialized = false;
082    private static boolean auditDone;
083
084    /**
085     * Audit process on application start.<br>
086     * Check id, register...
087     */
088    public static synchronized void prepare()
089    {
090        if (initialized)
091            return;
092
093        // get preferences
094        prefs = ApplicationPreferences.getPreferences();
095
096        // probably a new installation --> need to reset id
097        if (needToResetId())
098        {
099            // reset user info if needed
100            unlinkUser();
101            // reset id
102            ApplicationPreferences.setId(-1);
103        }
104
105        // store current infos
106        storeInfos();
107        // init audit storage
108        storage = new AuditStorage();
109
110        final int id = ApplicationPreferences.getId();
111
112        // id assigned ?
113        if (id != -1)
114        {
115            final long currentTime = System.currentTimeMillis();
116            final long dayInterval = 1000 * 60 * 60 * 24;
117
118            // upload each 24 hours
119            if (currentTime > (prefs.getLong(ID_LAST_UPLOAD_DATE, 0L) + dayInterval))
120            {
121                // save upload time whatever happened
122                prefs.putLong(ID_LAST_UPLOAD_DATE, System.currentTimeMillis());
123
124                // do that in background as it can take sometime if website does not reply...
125                ThreadUtil.bgRun(new Runnable()
126                {
127                    @Override
128                    public void run()
129                    {
130                        // upload usage statistics
131                        storage.upload(id);
132                    }
133                });
134            }
135        }
136
137        initialized = true;
138        auditDone = false;
139    }
140
141    /**
142     * Audit process on network connection
143     */
144    public static void onConnect()
145    {
146        prepare();
147
148        if (!auditDone)
149            processIdAudit();
150
151        updateUserLink();
152
153        // refresh user infos (in title)
154        final MainFrame frame = Icy.getMainInterface().getMainFrame();
155        if (frame != null)
156            frame.refreshTitle();
157    }
158
159    /**
160     * Save audit data
161     */
162    public static void save()
163    {
164        // save audit data
165        if (initialized)
166            storage.save();
167    }
168
169    /**
170     * Plugin launched event audit
171     */
172    public static void pluginLaunched(Plugin plugin)
173    {
174        // we don't want to wait for initialization here (can lock the application loading for sometime)
175        // and we don't care about init usage stats (ROI and daemons plugins)...
176        if (!initialized)
177            return;
178
179        storage.pluginLaunched(plugin);
180    }
181
182    /**
183     * Plugin instanced event audit
184     */
185    public static void pluginInstanced(Plugin plugin)
186    {
187        // we don't want to wait for initialization here (can lock the application loading for sometime)
188        // and we don't care about init usage stats (ROI and daemons plugins)...
189        if (!initialized)
190            return;
191
192        storage.pluginInstanced(plugin);
193    }
194
195    /**
196     * Returns <code>true</code> if we need to reset the internal id (usually mean new installation)
197     */
198    private static boolean needToResetId()
199    {
200        if (!StringUtil.equals(ApplicationPreferences.getOs(), SystemUtil.getOSArchIdString()))
201            return true;
202
203        final int cpu = prefs.getInt(ID_CLIENT_CPUNUMBER, 0);
204        final long mem = prefs.getLong(ID_CLIENT_TOTAL_MEMORY, 0);
205        final String appFolder = ApplicationPreferences.getAppFolder();
206
207        // ignore difference on first launch else it will regenerate id for everyone
208        if ((cpu != 0) && (cpu != SystemUtil.getNumberOfCPUs()))
209            return true;
210        if ((mem != 0) && (mem != SystemUtil.getTotalMemory()))
211            return true;
212        if (!StringUtil.isEmpty(appFolder) && !StringUtil.equals(appFolder, FileUtil.APPLICATION_DIRECTORY))
213            return true;
214
215        return false;
216    }
217
218    /**
219     * Store system and application informations in preferences
220     */
221    private static void storeInfos()
222    {
223        ApplicationPreferences.setOs(SystemUtil.getOSArchIdString());
224        prefs.putInt(ID_CLIENT_CPUNUMBER, SystemUtil.getNumberOfCPUs());
225        prefs.putLong(ID_CLIENT_TOTAL_MEMORY, SystemUtil.getTotalMemory());
226        ApplicationPreferences.setAppFolder(FileUtil.APPLICATION_DIRECTORY);
227    }
228
229    private static void processIdAudit()
230    {
231        final Map<String, String> values = new HashMap<String, String>();
232        final int id = ApplicationPreferences.getId();
233
234        values.put(ID_CLIENT_ARCH, SystemUtil.getOSArchIdString());
235        values.put(ID_CLIENT_VERSION, Icy.version.toString());
236
237        // need to register
238        if (id == -1)
239        {
240            // ask for registration
241            values.put(ID_REQUEST, "1");
242
243            final Document doc = XMLUtil.loadDocument(URL_REGISTER + NetworkUtil.getContentString(values));
244
245            if (doc != null)
246            {
247                final Node root = XMLUtil.getRootElement(doc);
248                final int newId = XMLUtil.getElementIntValue(root, XMLID_CLIENT_ID_REQUESTED, -1);
249
250                // valid id --> save it
251                if (newId != -1)
252                    ApplicationPreferences.setId(newId);
253            }
254        }
255        else
256        {
257            // just audit infos
258            values.put(ID_CLIENT_ID, Integer.toString(id));
259
260            values.put(ID_CLIENT_CPUNUMBER, Integer.toString(SystemUtil.getNumberOfCPUs()));
261            values.put(ID_CLIENT_TOTAL_MEMORY, Long.toString(SystemUtil.getTotalMemory() / 10485760L));
262            values.put(ID_CLIENT_MAXJAVA_MEMORY, Long.toString(SystemUtil.getJavaMaxMemory() / 10485760L));
263            values.put(ID_JAVA_NAME, SystemUtil.getJavaName());
264            values.put(ID_JAVA_VERSION, SystemUtil.getJavaVersion());
265            values.put(ID_JAVA_ARCH, Integer.toString(SystemUtil.getJavaArchDataModel()));
266
267            try
268            {
269                NetworkUtil.postData(URL_AUDIT_VERSION, values);
270            }
271            catch (IOException e)
272            {
273                // silent fail...
274                // IcyExceptionHandler.showErrorMessage(e, false, false);
275            }
276        }
277
278        auditDone = true;
279    }
280
281    private static Map<String, String> getIdParam()
282    {
283        final int id = ApplicationPreferences.getId();
284
285        // id ok ?
286        if (id != -1)
287        {
288            final Map<String, String> values = new HashMap<String, String>();
289
290            // set id
291            values.put(ID_ICY_ID, Integer.toString(id));
292
293            return values;
294        }
295
296        return null;
297    }
298
299    public static void updateUserLink()
300    {
301        final Map<String, String> params = getIdParam();
302
303        // id param ok ?
304        if (params != null)
305        {
306            // and retrieve user infos
307            final Document doc = XMLUtil.loadDocument(URL_GET_USER_INFO + NetworkUtil.getContentString(params));
308
309            if (doc != null)
310            {
311                final Node root = XMLUtil.getRootElement(doc);
312
313                // set attached user login and name
314                GeneralPreferences.setUserLogin(XMLUtil.getElementValue(root, XMLID_USER_LOGIN, ""));
315                GeneralPreferences.setUserName(XMLUtil.getElementValue(root, XMLID_USER_NAME, ""));
316            }
317        }
318    }
319
320    public static boolean isUserLinked()
321    {
322        return !StringUtil.isEmpty(GeneralPreferences.getUserLogin());
323    }
324
325    public static void linkUser()
326    {
327        final int id = ApplicationPreferences.getId();
328
329        // id ok ?
330        if (id != -1)
331        {
332            // launch browser with link identity request
333            NetworkUtil.openBrowser(URL_LINK_USER + ID_ICY_ID + "=" + id + "&" + ID_ACTION + "=link");
334
335            if (!Icy.getMainInterface().isHeadLess())
336            {
337                // display linking in progress
338                new Thread(new Runnable()
339                {
340                    @Override
341                    public void run()
342                    {
343                        final CancelableProgressFrame waitFrame = new CancelableProgressFrame(
344                                "Waiting for user to link account...");
345
346                        while (!Thread.interrupted() && !waitFrame.isCancelRequested())
347                        {
348                            try
349                            {
350                                Thread.sleep(1000);
351                                updateUserLink();
352
353                                // user linked !
354                                if (isUserLinked())
355                                {
356                                    // stop wait
357                                    waitFrame.cancel();
358
359                                    // refresh user infos (in title)
360                                    final MainFrame frame = Icy.getMainInterface().getMainFrame();
361                                    if (frame != null)
362                                        frame.refreshTitle();
363                                }
364                            }
365                            catch (InterruptedException e)
366                            {
367                                waitFrame.cancel();
368                            }
369                        }
370
371                        // close wait frame
372                        waitFrame.close();
373                    }
374                }).start();
375            }
376        }
377    }
378
379    public static void unlinkUser()
380    {
381        final Map<String, String> params = getIdParam();
382
383        // id param ok ?
384        if (params != null)
385        {
386            // do that in background as it can take sometime...
387            ThreadUtil.bgRun(new Runnable()
388            {
389                @Override
390                public void run()
391                {
392                    // set action
393                    params.put(ID_ACTION, "unlink");
394
395                    try
396                    {
397                        // and post
398                        NetworkUtil.postData(URL_LINK_USER, params);
399                    }
400                    catch (IOException e)
401                    {
402                        // can't unlink on web site, not a big deal...
403                        System.err.print("Warning: cannot unlink online user infos.");
404                        IcyExceptionHandler.showErrorMessage(e, false, false);
405                    }
406                }
407            });
408        }
409
410        // reset attached user login and name
411        GeneralPreferences.setUserLogin("");
412        GeneralPreferences.setUserName("");
413    }
414}