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.workspace;
020
021import icy.file.FileUtil;
022import icy.gui.dialog.ConfirmDialog;
023import icy.gui.frame.progress.FailedAnnounceFrame;
024import icy.gui.frame.progress.ProgressFrame;
025import icy.gui.frame.progress.SuccessfullAnnounceFrame;
026import icy.main.Icy;
027import icy.network.NetworkUtil;
028import icy.plugin.PluginDescriptor;
029import icy.plugin.PluginInstaller;
030import icy.plugin.PluginLoader;
031import icy.preferences.WorkspaceLocalPreferences;
032import icy.system.thread.ThreadUtil;
033import icy.util.StringUtil;
034import icy.workspace.Workspace.TaskDefinition.BandDefinition.ItemDefinition;
035
036import java.util.ArrayList;
037import java.util.EventListener;
038import java.util.List;
039
040import javax.swing.event.EventListenerList;
041
042/**
043 * @author Stephane
044 */
045public class WorkspaceInstaller implements Runnable
046{
047    public static interface WorkspaceInstallerListener extends EventListener
048    {
049        public void workspaceInstalled(WorkspaceInstallerEvent e);
050
051        public void workspaceRemoved(WorkspaceInstallerEvent e);
052    }
053
054    public static class WorkspaceInstallerEvent
055    {
056        private final Workspace workspace;
057        private final boolean successed;
058
059        public WorkspaceInstallerEvent(Workspace workspace, boolean successed)
060        {
061            super();
062
063            this.workspace = workspace;
064            this.successed = successed;
065        }
066
067        /**
068         * @return the workspace
069         */
070        public Workspace getWorkspace()
071        {
072            return workspace;
073        }
074
075        /**
076         * @return the success
077         */
078        public boolean getSuccessed()
079        {
080            return successed;
081        }
082    }
083
084    private static class WorkspaceInstallInfo
085    {
086        final Workspace workspace;
087        final boolean showConfirm;
088
089        public WorkspaceInstallInfo(Workspace workspace, boolean showConfirm)
090        {
091            super();
092
093            this.workspace = workspace;
094            this.showConfirm = showConfirm;
095        }
096    }
097
098    /**
099     * static class
100     */
101    private static final WorkspaceInstaller instance = new WorkspaceInstaller();
102
103    /**
104     * workspace to install FIFO
105     */
106    private final List<WorkspaceInstallInfo> installFIFO;
107    /**
108     * workspace to delete FIFO
109     */
110    private final List<WorkspaceInstallInfo> removeFIFO;
111
112    /**
113     * listeners
114     */
115    private final EventListenerList listeners;
116
117    /**
118     * internals
119     */
120    private Workspace installingWorkspace;
121    private Workspace desinstallingWorkspace;
122
123    private boolean installing;
124    private boolean deinstalling;
125
126    /**
127     * static class
128     */
129    private WorkspaceInstaller()
130    {
131        super();
132
133        installFIFO = new ArrayList<WorkspaceInstallInfo>();
134        removeFIFO = new ArrayList<WorkspaceInstallInfo>();
135
136        listeners = new EventListenerList();
137
138        installingWorkspace = null;
139        desinstallingWorkspace = null;
140
141        installing = false;
142        deinstalling = false;
143
144        // launch installer thread
145        new Thread(this, "Workspace installer").start();
146    }
147
148    /**
149     * install a workspace (asynchronous)
150     */
151    public static void install(Workspace workspace, boolean showConfirm)
152    {
153        if (workspace != null)
154        {
155            if (!NetworkUtil.hasInternetAccess())
156            {
157                final String text = "Cannot install '" + workspace.getName()
158                        + "' workspace : you are not connected to Internet.";
159
160                if (Icy.getMainInterface().isHeadLess())
161                    System.err.println(text);
162                else
163                    new FailedAnnounceFrame(text, 10);
164
165                return;
166            }
167
168            synchronized (instance.installFIFO)
169            {
170                instance.installFIFO.add(new WorkspaceInstallInfo(workspace, showConfirm));
171            }
172        }
173    }
174
175    /**
176     * return true if WorkspaceInstaller is processing
177     */
178    public static boolean isProcessing()
179    {
180        return isInstalling() || isDesinstalling();
181    }
182
183    /**
184     * return true if WorkspaceInstaller is installing workspace(s)
185     */
186    public static boolean isInstalling()
187    {
188        return (!instance.installFIFO.isEmpty()) || instance.installing;
189    }
190
191    /**
192     * return true if 'workspace' is in the install FIFO
193     */
194    public static boolean isWaitingForInstall(Workspace workspace)
195    {
196        final String workspaceName = workspace.getName();
197
198        synchronized (instance.installFIFO)
199        {
200            for (WorkspaceInstallInfo info : instance.installFIFO)
201                if (workspaceName.equals(info.workspace.getName()))
202                    return true;
203        }
204
205        return false;
206    }
207
208    /**
209     * return the current installed workspace (null if none)
210     */
211    public static Workspace getCurrentInstallingWorkspace()
212    {
213        return instance.installingWorkspace;
214    }
215
216    /**
217     * return true if specified workspace is currently being installed or will be installed
218     */
219    public static boolean isInstallingWorkspace(Workspace workspace)
220    {
221        if (instance.installingWorkspace != null)
222        {
223            if (instance.installingWorkspace.getName().equals(workspace.getName()))
224                return true;
225        }
226
227        return isWaitingForInstall(workspace);
228    }
229
230    /**
231     * uninstall a workspace (asynchronous)
232     */
233    public static void desinstall(Workspace workspace, boolean showConfirm)
234    {
235        if (workspace != null)
236        {
237            synchronized (instance.removeFIFO)
238            {
239                instance.removeFIFO.add(new WorkspaceInstallInfo(workspace, showConfirm));
240            }
241        }
242    }
243
244    /**
245     * return true if WorkspaceInstaller is desinstalling workspace(s)
246     */
247    public static boolean isDesinstalling()
248    {
249        return (!instance.removeFIFO.isEmpty()) || instance.deinstalling;
250    }
251
252    /**
253     * return true if 'workspace' is in the remove FIFO
254     */
255    public static boolean isWaitingForDesinstall(Workspace workspace)
256    {
257        final String workspaceName = workspace.getName();
258
259        synchronized (instance.removeFIFO)
260        {
261            for (WorkspaceInstallInfo info : instance.removeFIFO)
262                if (workspaceName.equals(info.workspace.getName()))
263                    return true;
264        }
265
266        return false;
267    }
268
269    /**
270     * return true if specified workspace is currently being desinstalled or will be desinstalled
271     */
272    public static boolean isDesinstallingWorkspace(Workspace workspace)
273    {
274        if (instance.desinstallingWorkspace != null)
275        {
276            if (instance.desinstallingWorkspace.getName().equals(workspace.getName()))
277                return true;
278        }
279
280        return isWaitingForDesinstall(workspace);
281    }
282
283    @Override
284    public void run()
285    {
286        while (!Thread.interrupted())
287        {
288            boolean empty;
289            boolean result;
290            WorkspaceInstallInfo installInfo = null;
291
292            // process installations
293            empty = installFIFO.isEmpty();
294
295            if (!empty)
296            {
297                installing = true;
298                try
299                {
300                    while (!empty)
301                    {
302                        synchronized (installFIFO)
303                        {
304                            installInfo = installFIFO.remove(0);
305                            empty = installFIFO.isEmpty();
306                        }
307
308                        // don't install if the workspace is already installed
309                        if (!WorkspaceLoader.isLoaded(installInfo.workspace))
310                        {
311                            result = installInternal(installInfo);
312                            // process on workspace installation
313                            installed(installInfo.workspace, result);
314                        }
315
316                        synchronized (installFIFO)
317                        {
318                        }
319                    }
320                }
321                finally
322                {
323                    installing = false;
324                }
325            }
326
327            // process deletions
328            empty = removeFIFO.isEmpty();
329
330            if (!empty)
331            {
332                deinstalling = true;
333                try
334                {
335                    while (!empty)
336                    {
337                        synchronized (removeFIFO)
338                        {
339                            installInfo = removeFIFO.remove(0);
340                            empty = removeFIFO.isEmpty();
341                        }
342
343                        result = desinstallInternal(installInfo);
344                        // process on workspace deletion
345                        desinstalled(installInfo.workspace, result);
346                    }
347                }
348                finally
349                {
350                    deinstalling = false;
351                }
352            }
353
354            ThreadUtil.sleep(200);
355        }
356    }
357
358    private boolean deleteWorkspace(Workspace workspace)
359    {
360        if (!FileUtil.delete(workspace.getLocalFilename(), false))
361            System.err.println("deleteWorkspace : Can't delete " + workspace.getLocalFilename());
362
363        // reload workspace list
364        WorkspaceLoader.reload();
365
366        return true;
367    }
368
369    /**
370     * Return local plugins of specified workspace
371     */
372    private ArrayList<PluginDescriptor> getLocalPlugins(Workspace workspace)
373    {
374        final ArrayList<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
375
376        for (ItemDefinition item : workspace.getAllItems())
377        {
378            if (!item.isSeparator())
379            {
380                final PluginDescriptor plugin = PluginLoader.getPlugin(item.getClassName());
381
382                // plugin found
383                if (plugin != null)
384                {
385                    // add all its dependences
386                    PluginInstaller.getLocalDependenciesOf(result, plugin);
387                    // the add the plugin itselft
388                    PluginDescriptor.addToList(result, plugin);
389                }
390            }
391        }
392
393        return result;
394    }
395
396    /**
397     * Return independent plugins of workspace (so they can be deleted)
398     */
399    private ArrayList<PluginDescriptor> getIndependentPlugins(Workspace workspace)
400    {
401        // get plugins of specified workspace
402        final ArrayList<PluginDescriptor> result = getLocalPlugins(workspace);
403        final ArrayList<PluginDescriptor> others = new ArrayList<PluginDescriptor>();
404
405        // get plugins of all others workspaces
406        for (Workspace ws : WorkspaceLoader.getWorkspaces())
407            // we can use name as id here
408            if (!StringUtil.equals(ws.getName(), workspace.getName()))
409                others.addAll(getLocalPlugins(ws));
410
411        for (PluginDescriptor plugin : others)
412        {
413            for (int i = result.size() - 1; i >= 0; i--)
414            {
415                final PluginDescriptor depPlug = result.get(i);
416
417                // have a dependence, remove from list...
418                if (plugin.equals(depPlug) || plugin.requires(depPlug))
419                    result.remove(i);
420            }
421        }
422
423        return result;
424    }
425
426    private boolean installInternal(WorkspaceInstallInfo installInfo)
427    {
428        final Workspace workspace = installInfo.workspace;
429        final boolean showConfirm = installInfo.showConfirm;
430
431        // installation start
432        installingWorkspace = workspace;
433        try
434        {
435            ProgressFrame taskFrame = null;
436            int result = 0;
437            final String workspaceName = workspace.getName();
438
439            if (showConfirm && !Icy.getMainInterface().isHeadLess())
440                taskFrame = new ProgressFrame("installing workspace '" + workspaceName + "'...");
441            try
442            {
443                // install workspace (actually install dependent plugins)
444                result = workspace.install(taskFrame);
445
446                if (result > 0)
447                {
448                    if (taskFrame != null)
449                        taskFrame.setMessage("saving workspace '" + workspaceName + "'...");
450
451                    // save workspace locally
452                    workspace.save();
453
454                    if (taskFrame != null)
455                        taskFrame.setMessage("reloading workspaces list...");
456
457                    // reload workspace list
458                    WorkspaceLoader.reload();
459                }
460            }
461            finally
462            {
463                if (taskFrame != null)
464                    taskFrame.close();
465            }
466
467            final String resMess = "Workspace '" + workspaceName + "' installation";
468
469            if (showConfirm && !Icy.getMainInterface().isHeadLess())
470            {
471                switch (result)
472                {
473                    default:
474                        new FailedAnnounceFrame(resMess + " failed !");
475                        break;
476
477                    case 1:
478                        new SuccessfullAnnounceFrame(resMess + " succeed !", 10);
479                        break;
480
481                    case 2:
482                        new SuccessfullAnnounceFrame(resMess + " succeed but some plugins cannot be installed.", 10);
483                        break;
484                }
485            }
486            else
487            {
488                switch (result)
489                {
490                    default:
491                        System.err.println(resMess + " failed !");
492                        break;
493
494                    case 1:
495                        System.out.println(resMess + " succeed !");
496                        break;
497
498                    case 2:
499                        System.out.println(resMess + " partially succeed (some plugins cannot be installed) !");
500                        break;
501                }
502            }
503
504            return result > 0;
505        }
506        finally
507        {
508            // installation end
509            installingWorkspace = null;
510        }
511    }
512
513    private boolean desinstallInternal(WorkspaceInstallInfo installInfo)
514    {
515        final Workspace workspace = installInfo.workspace;
516        final boolean showConfirm = installInfo.showConfirm;
517
518        // desinstall start
519        desinstallingWorkspace = workspace;
520        try
521        {
522            final ArrayList<PluginDescriptor> independentPlugins = new ArrayList<PluginDescriptor>();
523            final String workspaceDesc = workspace.getName();
524
525            final boolean deletePlugin;
526            final boolean result;
527            ProgressFrame taskFrame = null;
528
529            if (showConfirm)
530            {
531                String message = "<html>Do you want to also remove the associated plugins ?</html>";
532
533                deletePlugin = ConfirmDialog.confirm(message);
534            }
535            else
536                deletePlugin = true;
537
538            if (deletePlugin)
539            {
540                if (showConfirm && !Icy.getMainInterface().isHeadLess())
541                    taskFrame = new ProgressFrame("checking plugins dependences...");
542                try
543                {
544                    independentPlugins.addAll(getIndependentPlugins(workspace));
545                }
546                finally
547                {
548                    if (taskFrame != null)
549                        taskFrame.close();
550                }
551            }
552
553            if (showConfirm)
554                taskFrame = new ProgressFrame("removing workspace '" + workspaceDesc + "'...");
555            try
556            {
557                // remove workspace independent plugins
558                for (PluginDescriptor plugin : independentPlugins)
559                    if (plugin.isInstalled())
560                        PluginInstaller.desinstall(plugin, false, false);
561
562                // wait for plugins desintallation
563                PluginInstaller.waitDesinstall();
564
565                // delete workspace
566                result = deleteWorkspace(workspace);
567            }
568            finally
569            {
570                if (taskFrame != null)
571                    taskFrame.close();
572            }
573
574            final String resMess = "Workspace '" + workspaceDesc + "' remove";
575
576            if (showConfirm && !Icy.getMainInterface().isHeadLess())
577            {
578                if (result)
579                    new SuccessfullAnnounceFrame(resMess + " succeed !", 10);
580                else
581                    new FailedAnnounceFrame(resMess + " failed !");
582            }
583            else
584            {
585                if (result)
586                    System.out.println(resMess + " succeed !");
587                else
588                    System.err.println(resMess + " failed !");
589            }
590
591            return result;
592        }
593        finally
594        {
595            // desintall end
596            desinstallingWorkspace = null;
597        }
598    }
599
600    /**
601     * process on workspace install
602     */
603    private void installed(Workspace workspace, boolean result)
604    {
605        if (result)
606        {
607            // enable the installed workspace by default
608            WorkspaceLocalPreferences.setWorkspaceEnable(workspace.getName(), true);
609            // show an announcement for restart
610            Icy.announceRestart();
611        }
612
613        fireInstalledEvent(new WorkspaceInstallerEvent(workspace, result));
614    }
615
616    /**
617     * process on workspace remove
618     */
619    private void desinstalled(Workspace workspace, boolean result)
620    {
621        if (result)
622            // show an announcement for restart
623            Icy.announceRestart();
624
625        fireRemovedEvent(new WorkspaceInstallerEvent(workspace, result));
626    }
627
628    /**
629     * Add a listener
630     * 
631     * @param listener
632     */
633    public static void addListener(WorkspaceInstallerListener listener)
634    {
635        synchronized (instance.listeners)
636        {
637            instance.listeners.add(WorkspaceInstallerListener.class, listener);
638        }
639    }
640
641    /**
642     * Remove a listener
643     * 
644     * @param listener
645     */
646    public static void removeListener(WorkspaceInstallerListener listener)
647    {
648        synchronized (instance.listeners)
649        {
650            instance.listeners.remove(WorkspaceInstallerListener.class, listener);
651        }
652    }
653
654    /**
655     * fire workspace installed event
656     */
657    private void fireInstalledEvent(WorkspaceInstallerEvent e)
658    {
659        for (WorkspaceInstallerListener listener : listeners.getListeners(WorkspaceInstallerListener.class))
660            listener.workspaceInstalled(e);
661    }
662
663    /**
664     * fire workspace removed event
665     */
666    private void fireRemovedEvent(WorkspaceInstallerEvent e)
667    {
668        for (WorkspaceInstallerListener listener : listeners.getListeners(WorkspaceInstallerListener.class))
669            listener.workspaceRemoved(e);
670    }
671
672}