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}