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.plugin; 020 021import icy.main.Icy; 022import icy.network.NetworkUtil; 023import icy.network.URLUtil; 024import icy.plugin.PluginDescriptor.PluginIdent; 025import icy.plugin.PluginDescriptor.PluginNameSorter; 026import icy.plugin.PluginDescriptor.PluginOnlineIdent; 027import icy.preferences.PluginPreferences; 028import icy.preferences.RepositoryPreferences; 029import icy.preferences.RepositoryPreferences.RepositoryInfo; 030import icy.system.IcyExceptionHandler; 031import icy.system.thread.SingleProcessor; 032import icy.system.thread.ThreadUtil; 033import icy.util.XMLUtil; 034 035import java.util.ArrayList; 036import java.util.Collections; 037import java.util.EventListener; 038import java.util.HashMap; 039import java.util.List; 040import java.util.Map; 041 042import javax.swing.event.EventListenerList; 043 044import org.w3c.dom.Document; 045import org.w3c.dom.Node; 046 047/** 048 * @author stephane 049 */ 050public class PluginRepositoryLoader 051{ 052 public static interface PluginRepositoryLoaderListener extends EventListener 053 { 054 public void pluginRepositeryLoaderChanged(PluginDescriptor plugin); 055 } 056 057 private class Loader implements Runnable 058 { 059 public Loader() 060 { 061 super(); 062 } 063 064 @Override 065 public void run() 066 { 067 final List<PluginDescriptor> newPlugins = new ArrayList<PluginDescriptor>(); 068 069 try 070 { 071 final List<RepositoryInfo> repositories = RepositoryPreferences.getRepositeries(); 072 073 // load online plugins from all active repositories 074 for (RepositoryInfo repoInfo : repositories) 075 { 076 // reload requested --> stop current loading 077 if (processor.hasWaitingTasks()) 078 return; 079 080 if (repoInfo.isEnabled()) 081 { 082 final List<PluginDescriptor> pluginsRepos = loadInternal(repoInfo); 083 084 if (pluginsRepos == null) 085 { 086 failed = true; 087 return; 088 } 089 090 newPlugins.addAll(pluginsRepos); 091 } 092 } 093 094 // sort list on plugin class name 095 Collections.sort(newPlugins, PluginNameSorter.instance); 096 097 plugins = newPlugins; 098 } 099 catch (Exception e) 100 { 101 IcyExceptionHandler.showErrorMessage(e, true); 102 failed = true; 103 return; 104 } 105 106 // notify basic data has been loaded 107 loaded = true; 108 changed(null); 109 } 110 } 111 112 private static final String ID_ROOT = "plugins"; 113 private static final String ID_PLUGIN = "plugin"; 114 // private static final String ID_PATH = "path"; 115 116 /** 117 * static class 118 */ 119 private static final PluginRepositoryLoader instance = new PluginRepositoryLoader(); 120 121 /** 122 * Online plugin list 123 */ 124 List<PluginDescriptor> plugins; 125 126 /** 127 * listeners 128 */ 129 private final EventListenerList listeners; 130 131 /** 132 * internals 133 */ 134 boolean loaded; 135 boolean failed; 136 137 private final Loader loader; 138 final SingleProcessor processor; 139 140 /** 141 * static class 142 */ 143 private PluginRepositoryLoader() 144 { 145 super(); 146 147 plugins = new ArrayList<PluginDescriptor>(); 148 listeners = new EventListenerList(); 149 150 loader = new Loader(); 151 processor = new SingleProcessor(true, "Online Plugin Loader"); 152 153 loaded = false; 154 // initial loading 155 load(); 156 } 157 158 /** 159 * Return the plugins identifier list from a repository URL 160 */ 161 public static List<PluginOnlineIdent> getPluginIdents(RepositoryInfo repos) 162 { 163 String address = repos.getLocation(); 164 final boolean networkAddr = URLUtil.isNetworkURL(address); 165 final boolean betaAllowed = PluginPreferences.getAllowBeta(); 166 167 if (networkAddr && repos.getSupportParam()) 168 { 169 // prepare parameters for plugin list request 170 final Map<String, String> values = new HashMap<String, String>(); 171 172 // add kernel information parameter 173 values.put(NetworkUtil.ID_KERNELVERSION, Icy.version.toString()); 174 // add beta allowed information parameter 175 values.put(NetworkUtil.ID_BETAALLOWED, Boolean.toString(betaAllowed)); 176 // concat to address 177 address += "?" + NetworkUtil.getContentString(values); 178 } 179 180 // load the XML file 181 final Document document = XMLUtil.loadDocument(address, repos.getAuthenticationInfo(), false); 182 183 // error 184 if (document == null) 185 { 186 if (networkAddr && !NetworkUtil.hasInternetAccess()) 187 System.out.println("You are not connected to internet."); 188 189 return null; 190 } 191 192 final List<PluginOnlineIdent> result = new ArrayList<PluginOnlineIdent>(); 193 // get plugins node 194 final Node pluginsNode = XMLUtil.getElement(document.getDocumentElement(), ID_ROOT); 195 196 // plugins node found 197 if (pluginsNode != null) 198 { 199 // ident nodes 200 final List<Node> nodes = XMLUtil.getChildren(pluginsNode, ID_PLUGIN); 201 202 for (Node node : nodes) 203 { 204 final PluginOnlineIdent ident = new PluginOnlineIdent(); 205 206 ident.loadFromXML(node); 207 208 // accept only if not empty 209 if (!ident.isEmpty()) 210 { 211 // accept only if required kernel version is ok and beta accepted 212 if (ident.getRequiredKernelVersion().isLowerOrEqual(Icy.version) 213 && (betaAllowed || (!ident.getVersion().isBeta()))) 214 { 215 // check if we have several version of the same plugin 216 final int ind = PluginIdent.getIndex(result, ident.getClassName()); 217 // other version found ? 218 if (ind != -1) 219 { 220 // replace old version if needed 221 if (result.get(ind).isOlderOrEqual(ident)) 222 result.set(ind, ident); 223 } 224 else 225 result.add(ident); 226 } 227 } 228 } 229 } 230 231 return result; 232 } 233 234 /** 235 * Do loading process. 236 */ 237 private void load() 238 { 239 loaded = false; 240 failed = false; 241 242 processor.submit(loader); 243 } 244 245 /** 246 * Reload all plugins from all active repositories (old list is cleared).<br> 247 * Asynchronous process, use {@link #waitLoaded()} method to wait for basic data to be loaded. 248 */ 249 public static synchronized void reload() 250 { 251 instance.load(); 252 } 253 254 /** 255 * Load the list of online plugins located at specified repository 256 */ 257 // public static void load(final RepositoryInfo repos, boolean asynch, final boolean 258 // loadDescriptor, 259 // final boolean loadImages) 260 // { 261 // instance.loadSingleRunner.setParameters(repos, loadDescriptor, loadImages); 262 // 263 // if (asynch) 264 // ThreadUtil.bgRunSingle(instance.loadAllRunner); 265 // else 266 // instance.loadAllRunner.run(); 267 // } 268 269 /** 270 * Load and return the list of online plugins located at specified repository 271 */ 272 static List<PluginDescriptor> loadInternal(RepositoryInfo repos) 273 { 274 // we start by loading only identifier part 275 final List<PluginOnlineIdent> idents = getPluginIdents(repos); 276 277 // error while retrieving identifiers ? 278 if (idents == null) 279 { 280 System.out.println("Can't access repository '" + repos.getName() + "'"); 281 return null; 282 } 283 284 final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>(); 285 286 for (PluginOnlineIdent ident : idents) 287 { 288 try 289 { 290 result.add(new PluginDescriptor(ident, repos)); 291 } 292 catch (Exception e) 293 { 294 System.out.println("PluginRepositoryLoader.load('" + repos.getLocation() + "') error :"); 295 IcyExceptionHandler.showErrorMessage(e, false); 296 } 297 } 298 299 return result; 300 } 301 302 /** 303 * @return the pluginList 304 */ 305 public static ArrayList<PluginDescriptor> getPlugins() 306 { 307 synchronized (instance.plugins) 308 { 309 return new ArrayList<PluginDescriptor>(instance.plugins); 310 } 311 } 312 313 public static PluginDescriptor getPlugin(String className) 314 { 315 synchronized (instance.plugins) 316 { 317 return PluginDescriptor.getPlugin(instance.plugins, className); 318 } 319 } 320 321 public static List<PluginDescriptor> getPlugins(String className) 322 { 323 synchronized (instance.plugins) 324 { 325 return PluginDescriptor.getPlugins(instance.plugins, className); 326 } 327 } 328 329 /** 330 * Return the plugins list from the specified repository 331 */ 332 public static List<PluginDescriptor> getPlugins(RepositoryInfo repos) 333 { 334 final List<PluginDescriptor> result = new ArrayList<PluginDescriptor>(); 335 336 synchronized (instance.plugins) 337 { 338 for (PluginDescriptor plugin : instance.plugins) 339 if (plugin.getRepository().equals(repos)) 340 result.add(plugin); 341 } 342 343 return result; 344 } 345 346 /** 347 * @return true if loader is loading the basic informations 348 */ 349 public static boolean isLoading() 350 { 351 return instance.processor.isProcessing(); 352 } 353 354 /** 355 * @return true if basic informations (class names, versions...) are loaded. 356 */ 357 public static boolean isLoaded() 358 { 359 return instance.failed || instance.loaded; 360 } 361 362 /** 363 * Wait until basic informations are loaded. 364 */ 365 public static void waitLoaded() 366 { 367 while (!isLoaded()) 368 ThreadUtil.sleep(10); 369 } 370 371 /** 372 * @deprecated use {@link #isLoaded()} instead. 373 */ 374 @Deprecated 375 public static boolean isBasicLoaded() 376 { 377 return isLoaded(); 378 } 379 380 /** 381 * @deprecated descriptor loading is now done per descriptor when needed 382 */ 383 @Deprecated 384 public static boolean isDescriptorsLoaded() 385 { 386 return true; 387 } 388 389 /** 390 * @deprecated image loading is now done per descriptor when needed 391 */ 392 @Deprecated 393 public static boolean isImagesLoaded() 394 { 395 return true; 396 } 397 398 /** 399 * @deprecated use {@link #waitLoaded()} instead. 400 */ 401 @Deprecated 402 public static void waitBasicLoaded() 403 { 404 waitLoaded(); 405 } 406 407 /** 408 * @deprecated descriptor loading is now done per descriptor when needed 409 */ 410 @Deprecated 411 public static void waitDescriptorsLoaded() 412 { 413 // do nothing 414 } 415 416 /** 417 * Returns true if an error occurred during the plugin loading process. 418 */ 419 public static boolean failed() 420 { 421 return instance.failed; 422 } 423 424 /** 425 * Plugin list has changed 426 */ 427 void changed(PluginDescriptor plugin) 428 { 429 fireEvent(plugin); 430 } 431 432 /** 433 * Add a listener 434 * 435 * @param listener 436 */ 437 public static void addListener(PluginRepositoryLoaderListener listener) 438 { 439 synchronized (instance.listeners) 440 { 441 instance.listeners.add(PluginRepositoryLoaderListener.class, listener); 442 } 443 } 444 445 /** 446 * Remove a listener 447 * 448 * @param listener 449 */ 450 public static void removeListener(PluginRepositoryLoaderListener listener) 451 { 452 synchronized (instance.listeners) 453 { 454 instance.listeners.remove(PluginRepositoryLoaderListener.class, listener); 455 } 456 } 457 458 /** 459 * fire event 460 * 461 * @param plugin 462 */ 463 private void fireEvent(PluginDescriptor plugin) 464 { 465 for (PluginRepositoryLoaderListener listener : listeners.getListeners(PluginRepositoryLoaderListener.class)) 466 listener.pluginRepositeryLoaderChanged(plugin); 467 } 468}