001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.operations.plugin; 019 020import java.io.BufferedReader; 021import java.io.BufferedWriter; 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.FileOutputStream; 025import java.io.FileWriter; 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.InputStreamReader; 029import java.io.PrintWriter; 030import java.net.URL; 031import java.net.URLConnection; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.List; 035import java.util.Map; 036import java.util.TreeMap; 037import java.util.jar.JarInputStream; 038import java.util.jar.Manifest; 039import java.util.logging.Level; 040import java.util.logging.Logger; 041 042import org.apache.commons.io.FileUtils; 043import org.apache.commons.io.IOUtils; 044import org.simpleframework.xml.core.Persister; 045import org.syncany.Client; 046import org.syncany.config.Config; 047import org.syncany.config.LocalEventBus; 048import org.syncany.config.UserConfig; 049import org.syncany.crypto.CipherUtil; 050import org.syncany.operations.Operation; 051import org.syncany.operations.daemon.messages.ConnectToHostExternalEvent; 052import org.syncany.operations.daemon.messages.PluginInstallExternalEvent; 053import org.syncany.operations.plugin.PluginOperationOptions.PluginListMode; 054import org.syncany.operations.plugin.PluginOperationResult.PluginResultCode; 055import org.syncany.plugins.Plugin; 056import org.syncany.plugins.Plugins; 057import org.syncany.util.EnvironmentUtil; 058import org.syncany.util.FileUtil; 059import org.syncany.util.StringUtil; 060 061import com.github.zafarkhaja.semver.Version; 062import com.google.common.base.Function; 063import com.google.common.base.Predicate; 064import com.google.common.collect.Iterables; 065import com.google.common.collect.Lists; 066 067/** 068 * The plugin operation installs, removes and lists storage {@link Plugin}s. 069 * 070 * <p>The plugin implements these three functionalities as different 071 * {@link PluginOperationAction}: 072 * 073 * <ul> 074 * <li><code>INSTALL</code>: Installation means copying a file to the user plugin directory 075 * as specified by {@link UserConfig#getUserPluginLibDir()}. A plugin can be installed 076 * from a local JAR file, a URL (the operation downloads a JAR file), or the 077 * API host (the operation find the plugin using the 'list' action and downloads 078 * the JAR file).</li> 079 * <li><code>REMOVE</code>: Removal means deleting a JAR file from the user plugin 080 * directoryThis action. This action simply finds the responsible plugin JAR 081 * file and deletes it. Only JAR files inside the user plugin direcory can be 082 * deleted.</li> 083 * <li><code>LIST</code>: Listing refers to a local and a remote list. The locally installed 084 * plugins can be queried by {@link Plugins#list()}. These plugins' JAR files must be 085 * in the application's class path. Remotely available plugins are queried through the 086 * API.</li> 087 * </ul> 088 * 089 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 090 */ 091public class PluginOperation extends Operation { 092 private static final Logger logger = Logger.getLogger(PluginOperation.class.getSimpleName()); 093 094 private static final String API_DEFAULT_ENDPOINT_URL = "https://api.syncany.org/v3"; 095 private static final String API_PLUGIN_LIST_REQUEST_FORMAT = "%s/plugins/list?appVersion=%s&snapshots=%s&pluginId=%s&os=%s&arch=%s"; 096 097 private static final String PURGEFILE_FILENAME = "purgefile"; 098 private static final String UPDATE_FILENAME = "updatefile"; 099 100 private PluginOperationOptions options; 101 private PluginOperationResult result; 102 103 private LocalEventBus eventBus; 104 105 public PluginOperation(Config config, PluginOperationOptions options) { 106 super(config); 107 108 this.options = options; 109 this.result = new PluginOperationResult(); 110 111 this.eventBus = LocalEventBus.getInstance(); 112 } 113 114 @Override 115 public PluginOperationResult execute() throws Exception { 116 result.setAction(options.getAction()); 117 118 switch (options.getAction()) { 119 case LIST: 120 return executeList(); 121 122 case INSTALL: 123 return executeInstall(); 124 125 case REMOVE: 126 return executeRemove(); 127 128 case UPDATE: 129 return executeUpdate(); 130 131 default: 132 throw new Exception("Unknown action: " + options.getAction()); 133 } 134 } 135 136 private PluginOperationResult executeUpdate() throws Exception { 137 List<String> updateablePlugins = findUpdateCandidates(); 138 List<String> erroneousPlugins = Lists.newArrayList(); 139 List<String> delayedPlugins = Lists.newArrayList(); 140 141 // update only a specific plugin if it is updatable and provided 142 String forcePluginId = options.getPluginId(); 143 logger.log(Level.FINE, "Force plugin is " + forcePluginId); 144 if (forcePluginId != null) { 145 if (updateablePlugins.contains(forcePluginId)) { 146 updateablePlugins = Lists.newArrayList(forcePluginId); 147 } 148 else { 149 logger.log(Level.WARNING, "User requested to update a non-updatable plugin: " + forcePluginId); 150 erroneousPlugins.add(forcePluginId); 151 updateablePlugins = Lists.newArrayList(); // empty list 152 } 153 } 154 155 logger.log(Level.INFO, "The following plugins can be automatically updated: " + StringUtil.join(updateablePlugins, ", ")); 156 157 for (String pluginId : updateablePlugins) { 158 // first remove 159 PluginOperationResult removeResult = executeRemove(pluginId); 160 161 if (removeResult.getResultCode() == PluginResultCode.NOK) { 162 logger.log(Level.SEVERE, "Unable to remove " + pluginId + " during the update process"); 163 erroneousPlugins.add(pluginId); 164 continue; 165 } 166 167 // ... and install again 168 if (EnvironmentUtil.isWindows()) { 169 logger.log(Level.FINE, "Appending jar to updatefile"); 170 File updatefilePath = new File(UserConfig.getUserConfigDir(), UPDATE_FILENAME); 171 172 try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(updatefilePath, true)))) { 173 out.println(pluginId + (options.isSnapshots() ? " --snapshot" : "")); 174 delayedPlugins.add(pluginId); 175 } 176 catch (IOException e) { 177 logger.log(Level.SEVERE, "Unable to append to updatefile " + updatefilePath, e); 178 erroneousPlugins.add(pluginId); 179 } 180 } 181 else { 182 PluginOperationResult installResult = executeInstallFromApiHost(pluginId); 183 184 if (installResult.getResultCode() == PluginResultCode.NOK) { 185 logger.log(Level.SEVERE, "Unable to install " + pluginId + " during the update process"); 186 erroneousPlugins.add(pluginId); 187 } 188 } 189 } 190 191 if (erroneousPlugins.size() > 0 && erroneousPlugins.size() == updateablePlugins.size()) { 192 result.setResultCode(PluginResultCode.NOK); 193 } 194 else { 195 result.setResultCode(PluginResultCode.OK); 196 } 197 198 result.setUpdatedPluginIds(updateablePlugins); 199 result.setErroneousPluginIds(erroneousPlugins); 200 result.setDelayedPluginIds(delayedPlugins); 201 202 return result; 203 } 204 205 private List<String> findUpdateCandidates() throws Exception { 206 List<ExtendedPluginInfo> updateCandidates = executeList().getPluginList(); 207 208 Iterables.removeIf(updateCandidates, new Predicate<ExtendedPluginInfo>() { 209 @Override 210 public boolean apply(ExtendedPluginInfo pluginInfo) { 211 return !pluginInfo.isInstalled() || !pluginInfo.canUninstall() || !pluginInfo.isOutdated(); 212 } 213 }); 214 215 return Lists.transform(updateCandidates, new Function<ExtendedPluginInfo, String>() { 216 @Override 217 public String apply(ExtendedPluginInfo pluginInfo) { 218 return pluginInfo.getLocalPluginInfo().getPluginId(); 219 } 220 }); 221 } 222 223 private PluginOperationResult executeRemove() throws Exception { 224 return executeRemove(options.getPluginId()); 225 } 226 227 private PluginOperationResult executeRemove(String pluginId) throws Exception { 228 Plugin plugin = Plugins.get(pluginId); 229 230 if (plugin == null) { 231 throw new Exception("Plugin not installed."); 232 } 233 234 File pluginJarFile = getJarFile(plugin); 235 boolean canUninstall = canUninstall(pluginJarFile); 236 237 if (canUninstall) { 238 PluginInfo pluginInfo = readPluginInfoFromJar(pluginJarFile); 239 240 logger.log(Level.INFO, "Uninstalling plugin from file " + pluginJarFile); 241 boolean deleted = pluginJarFile.delete(); 242 243 // JAR files are locked on Windows, adding JAR filename to a list for delayed deletion (by batch file) 244 if (EnvironmentUtil.isWindows() || !deleted) { 245 logger.log(Level.FINE, "Appending jar to purgefile (" + EnvironmentUtil.isWindows() + ", "+ deleted +")"); 246 File purgefilePath = new File(UserConfig.getUserConfigDir(), PURGEFILE_FILENAME); 247 248 try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(purgefilePath, true)))) { 249 out.println(pluginJarFile.getAbsolutePath()); 250 } 251 catch (IOException e) { 252 logger.log(Level.SEVERE, "Unable to append to purgefile " + purgefilePath, e); 253 } 254 } 255 256 // refresh plugin list 257 Plugins.refresh(); 258 259 result.setSourcePluginPath(pluginJarFile.getAbsolutePath()); 260 result.setAffectedPluginInfo(pluginInfo); 261 result.setResultCode(PluginResultCode.OK); 262 } 263 else { 264 logger.log(Level.INFO, "Plugin can NOT be uninstalled because class location not in " + UserConfig.getUserPluginLibDir()); 265 result.setResultCode(PluginResultCode.NOK); 266 } 267 268 return result; 269 } 270 271 private boolean canUninstall(File pluginJarFile) { 272 File globalUserPluginDir = UserConfig.getUserPluginLibDir(); 273 return pluginJarFile != null && pluginJarFile.getAbsolutePath().startsWith(globalUserPluginDir.getAbsolutePath()); 274 } 275 276 private File getJarFile(Plugin plugin) { 277 Class<? extends Plugin> pluginClass = plugin.getClass(); 278 URL pluginClassLocation = pluginClass.getResource('/' + pluginClass.getName().replace('.', '/') + ".class"); 279 String pluginClassLocationStr = pluginClassLocation.toString(); 280 281 logger.log(Level.INFO, "Plugin class is at " + pluginClassLocationStr); 282 283 if (pluginClassLocationStr.startsWith("jar:file:")) { 284 int indexStartAfterSchema = "jar:file:".length(); 285 int indexEndAtExclamationPoint = pluginClassLocationStr.indexOf("!"); 286 File pluginJarFile = new File(pluginClassLocationStr.substring(indexStartAfterSchema, indexEndAtExclamationPoint)); 287 288 logger.log(Level.INFO, "Plugin is in JAR at " + pluginJarFile); 289 return pluginJarFile; 290 } 291 else { 292 logger.log(Level.INFO, "Plugin is not in a JAR file. Probably in test environment."); 293 return null; 294 } 295 } 296 297 private PluginOperationResult executeInstall() throws Exception { 298 String pluginId = options.getPluginId(); 299 File potentialLocalPluginJarFile = new File(pluginId); 300 301 if (pluginId.matches("^https?://.+")) { 302 return executeInstallFromUrl(pluginId); 303 } 304 else if (potentialLocalPluginJarFile.exists()) { 305 return executeInstallFromLocalFile(potentialLocalPluginJarFile); 306 } 307 else { 308 return executeInstallFromApiHost(pluginId); 309 } 310 } 311 312 private PluginOperationResult executeInstallFromApiHost(String pluginId) throws Exception { 313 checkPluginNotInstalled(pluginId); 314 315 PluginInfo pluginInfo = getRemotePluginInfo(pluginId); 316 317 if (pluginInfo == null) { 318 throw new Exception("Plugin with ID '" + pluginId + "' not found"); 319 } 320 321 checkPluginCompatibility(pluginInfo); 322 323 eventBus.post(new PluginInstallExternalEvent(pluginInfo.getDownloadUrl())); 324 325 File tempPluginJarFile = downloadPluginJar(pluginInfo.getDownloadUrl()); 326 String expectedChecksum = pluginInfo.getSha256sum(); 327 String actualChecksum = calculateChecksum(tempPluginJarFile); 328 329 if (expectedChecksum == null || !expectedChecksum.equals(actualChecksum)) { 330 throw new Exception("Checksum mismatch. Expected: " + expectedChecksum + ", but was: " + actualChecksum); 331 } 332 333 logger.log(Level.INFO, "Plugin JAR checksum verified: " + actualChecksum); 334 335 File targetPluginJarFile = installPlugin(tempPluginJarFile, pluginInfo); 336 337 result.setSourcePluginPath(pluginInfo.getDownloadUrl()); 338 result.setTargetPluginPath(targetPluginJarFile.getAbsolutePath()); 339 result.setAffectedPluginInfo(pluginInfo); 340 result.setResultCode(PluginResultCode.OK); 341 342 return result; 343 } 344 345 private void checkPluginCompatibility(PluginInfo pluginInfo) throws Exception { 346 Version applicationVersion = Version.valueOf(Client.getApplicationVersion()); 347 Version pluginAppMinVersion = Version.valueOf(pluginInfo.getPluginAppMinVersion()); 348 349 logger.log(Level.INFO, "Checking plugin compatibility:"); 350 logger.log(Level.INFO, "- Application version: " + Client.getApplicationVersion() + "(" + applicationVersion + ")"); 351 logger.log(Level.INFO, "- Plugin min. application version: " + pluginInfo.getPluginAppMinVersion() + "(" + pluginAppMinVersion + ")"); 352 353 if (applicationVersion.lessThan(pluginAppMinVersion)) { 354 throw new Exception("Plugin is incompatible to this application version. Plugin min. application version is " 355 + pluginInfo.getPluginAppMinVersion() + ", current application version is " + Client.getApplicationVersion()); 356 } 357 358 // Verify if any conflicting plugins are installed 359 logger.log(Level.INFO, "Checking for conflicting plugins."); 360 361 List<String> conflictingIds = pluginInfo.getConflictingPluginIds(); 362 List<String> conflictingInstalledIds = new ArrayList<String>(); 363 364 if (conflictingIds != null) { 365 for (String pluginId : conflictingIds) { 366 Plugin plugin = Plugins.get(pluginId); 367 368 if (plugin != null) { 369 logger.log(Level.INFO, "- Conflicting plugin " + pluginId + " found."); 370 conflictingInstalledIds.add(pluginId); 371 } 372 373 logger.log(Level.FINE, "- Conflicting plugin " + pluginId + " not installed"); 374 } 375 } 376 377 result.setConflictingPlugins(conflictingInstalledIds); 378 } 379 380 private String calculateChecksum(File tempPluginJarFile) throws Exception { 381 CipherUtil.enableUnlimitedStrength(); 382 383 byte[] actualChecksum = FileUtil.createChecksum(tempPluginJarFile, "SHA256"); 384 return StringUtil.toHex(actualChecksum); 385 } 386 387 private PluginOperationResult executeInstallFromLocalFile(File pluginJarFile) throws Exception { 388 eventBus.post(new PluginInstallExternalEvent(pluginJarFile.getAbsolutePath())); 389 390 PluginInfo pluginInfo = readPluginInfoFromJar(pluginJarFile); 391 392 checkPluginNotInstalled(pluginInfo.getPluginId()); 393 checkPluginCompatibility(pluginInfo); 394 395 File targetPluginJarFile = installPlugin(pluginJarFile, pluginInfo); 396 397 result.setSourcePluginPath(pluginJarFile.getPath()); 398 result.setTargetPluginPath(targetPluginJarFile.getPath()); 399 result.setAffectedPluginInfo(pluginInfo); 400 result.setResultCode(PluginResultCode.OK); 401 402 return result; 403 } 404 405 private PluginOperationResult executeInstallFromUrl(String downloadJarUrl) throws Exception { 406 eventBus.post(new PluginInstallExternalEvent(downloadJarUrl)); 407 408 File tempPluginJarFile = downloadPluginJar(downloadJarUrl); 409 PluginInfo pluginInfo = readPluginInfoFromJar(tempPluginJarFile); 410 411 checkPluginNotInstalled(pluginInfo.getPluginId()); 412 checkPluginCompatibility(pluginInfo); 413 414 File targetPluginJarFile = installPlugin(tempPluginJarFile, pluginInfo); 415 416 result.setSourcePluginPath(downloadJarUrl); 417 result.setTargetPluginPath(targetPluginJarFile.getPath()); 418 result.setAffectedPluginInfo(pluginInfo); 419 result.setResultCode(PluginResultCode.OK); 420 421 return result; 422 } 423 424 private void checkPluginNotInstalled(String pluginId) throws Exception { 425 Plugin locallyInstalledPlugin = Plugins.get(pluginId); 426 427 if (locallyInstalledPlugin != null) { 428 throw new Exception("Plugin '" + pluginId + "' already installed. Use 'sy plugin remove " + pluginId + "' to uninstall it first."); 429 } 430 431 logger.log(Level.INFO, "Plugin '" + pluginId + "' not installed. Okay!"); 432 } 433 434 private PluginInfo readPluginInfoFromJar(File pluginJarFile) throws Exception { 435 try (JarInputStream jarStream = new JarInputStream(new FileInputStream(pluginJarFile))) { 436 Manifest jarManifest = jarStream.getManifest(); 437 438 if (jarManifest == null) { 439 throw new Exception("Given file is not a valid Syncany plugin file (not a JAR file, or no manifest)."); 440 } 441 442 String pluginId = jarManifest.getMainAttributes().getValue("Plugin-Id"); 443 444 if (pluginId == null) { 445 throw new Exception("Given file is not a valid Syncany plugin file (no plugin ID in manifest)."); 446 } 447 448 PluginInfo pluginInfo = new PluginInfo(); 449 450 pluginInfo.setPluginId(pluginId); 451 pluginInfo.setPluginName(jarManifest.getMainAttributes().getValue("Plugin-Name")); 452 pluginInfo.setPluginVersion(jarManifest.getMainAttributes().getValue("Plugin-Version")); 453 pluginInfo.setPluginDate(jarManifest.getMainAttributes().getValue("Plugin-Date")); 454 pluginInfo.setPluginAppMinVersion(jarManifest.getMainAttributes().getValue("Plugin-App-Min-Version")); 455 pluginInfo.setPluginRelease(Boolean.parseBoolean(jarManifest.getMainAttributes().getValue("Plugin-Release"))); 456 457 if (jarManifest.getMainAttributes().getValue("Plugin-Conflicts-With") != null) { 458 pluginInfo.setConflictingPluginIds(Arrays.asList(jarManifest.getMainAttributes().getValue("Plugin-Conflicts-With"))); 459 } 460 461 return pluginInfo; 462 } 463 } 464 465 private File installPlugin(File pluginJarFile, PluginInfo pluginInfo) throws IOException { 466 File globalUserPluginDir = UserConfig.getUserPluginLibDir(); 467 globalUserPluginDir.mkdirs(); 468 469 File targetPluginJarFile = new File(globalUserPluginDir, String.format("syncany-plugin-%s-%s.jar", pluginInfo.getPluginId(), 470 pluginInfo.getPluginVersion())); 471 472 logger.log(Level.INFO, "Installing plugin from " + pluginJarFile + " to " + targetPluginJarFile + " ..."); 473 FileUtils.copyFile(pluginJarFile, targetPluginJarFile); 474 475 return targetPluginJarFile; 476 } 477 478 /** 479 * Downloads the plugin JAR from the given URL to a temporary 480 * local location. 481 */ 482 private File downloadPluginJar(String pluginJarUrl) throws Exception { 483 URL pluginJarFile = new URL(pluginJarUrl); 484 logger.log(Level.INFO, "Querying " + pluginJarFile + " ..."); 485 486 URLConnection urlConnection = pluginJarFile.openConnection(); 487 urlConnection.setConnectTimeout(2000); 488 urlConnection.setReadTimeout(2000); 489 490 File tempPluginFile = File.createTempFile("syncany-plugin", "tmp"); 491 tempPluginFile.deleteOnExit(); 492 493 logger.log(Level.INFO, "Downloading to " + tempPluginFile + " ..."); 494 FileOutputStream tempPluginFileOutputStream = new FileOutputStream(tempPluginFile); 495 InputStream remoteJarFileInputStream = urlConnection.getInputStream(); 496 497 IOUtils.copy(remoteJarFileInputStream, tempPluginFileOutputStream); 498 499 remoteJarFileInputStream.close(); 500 tempPluginFileOutputStream.close(); 501 502 if (!tempPluginFile.exists() || tempPluginFile.length() == 0) { 503 throw new Exception("Downloading plugin file failed, URL was " + pluginJarUrl); 504 } 505 506 return tempPluginFile; 507 } 508 509 private PluginOperationResult executeList() throws Exception { 510 final Version applicationVersion = Version.valueOf(Client.getApplicationVersion()); 511 Map<String, ExtendedPluginInfo> pluginInfos = new TreeMap<String, ExtendedPluginInfo>(); 512 513 // First, list local plugins 514 if (options.getListMode() == PluginListMode.ALL || options.getListMode() == PluginListMode.LOCAL) { 515 for (PluginInfo localPluginInfo : getLocalList()) { 516 if (options.getPluginId() != null && !localPluginInfo.getPluginId().equals(options.getPluginId())) { 517 continue; 518 } 519 520 // Determine standard plugin information 521 ExtendedPluginInfo extendedPluginInfo = new ExtendedPluginInfo(); 522 523 extendedPluginInfo.setLocalPluginInfo(localPluginInfo); 524 extendedPluginInfo.setInstalled(true); 525 526 // Test if plugin can be uninstalled 527 Plugin plugin = Plugins.get(localPluginInfo.getPluginId()); 528 File pluginJarFile = getJarFile(plugin); 529 boolean canUninstall = canUninstall(pluginJarFile); 530 531 extendedPluginInfo.setCanUninstall(canUninstall); 532 533 // Add to list 534 pluginInfos.put(localPluginInfo.getPluginId(), extendedPluginInfo); 535 } 536 } 537 538 // Then, list remote plugins 539 if (options.getListMode() == PluginListMode.ALL || options.getListMode() == PluginListMode.REMOTE) { 540 for (PluginInfo remotePluginInfo : getRemotePluginInfoList()) { 541 if (options.getPluginId() != null && !remotePluginInfo.getPluginId().equals(options.getPluginId())) { 542 continue; 543 } 544 545 ExtendedPluginInfo extendedPluginInfo = pluginInfos.get(remotePluginInfo.getPluginId()); 546 boolean localPluginInstalled = extendedPluginInfo != null; 547 548 if (!localPluginInstalled) { // Locally not installed 549 extendedPluginInfo = new ExtendedPluginInfo(); 550 551 extendedPluginInfo.setInstalled(false); 552 extendedPluginInfo.setRemoteAvailable(true); 553 } 554 else { // Locally also installed 555 extendedPluginInfo.setRemoteAvailable(true); 556 557 Version localVersion = Version.valueOf(extendedPluginInfo.getLocalPluginInfo().getPluginVersion()); 558 Version remoteVersion = Version.valueOf(remotePluginInfo.getPluginVersion()); 559 Version remoteMinAppVersion = Version.valueOf(remotePluginInfo.getPluginAppMinVersion()); 560 561 boolean localVersionOutdated = localVersion.lessThan(remoteVersion); 562 boolean applicationVersionCompatible = applicationVersion.greaterThanOrEqualTo(remoteMinAppVersion); 563 boolean pluginIsOutdated = localVersionOutdated && applicationVersionCompatible; 564 565 extendedPluginInfo.setOutdated(pluginIsOutdated); 566 } 567 568 extendedPluginInfo.setRemotePluginInfo(remotePluginInfo); 569 pluginInfos.put(remotePluginInfo.getPluginId(), extendedPluginInfo); 570 } 571 } 572 573 result.setPluginList(new ArrayList<ExtendedPluginInfo>(pluginInfos.values())); 574 result.setResultCode(PluginResultCode.OK); 575 576 return result; 577 } 578 579 private List<PluginInfo> getLocalList() { 580 List<PluginInfo> localPluginInfos = new ArrayList<PluginInfo>(); 581 582 for (Plugin plugin : Plugins.list()) { 583 PluginInfo pluginInfo = new PluginInfo(); 584 585 pluginInfo.setPluginId(plugin.getId()); 586 pluginInfo.setPluginName(plugin.getName()); 587 pluginInfo.setPluginVersion(plugin.getVersion()); 588 589 localPluginInfos.add(pluginInfo); 590 } 591 592 return localPluginInfos; 593 } 594 595 private List<PluginInfo> getRemotePluginInfoList() throws Exception { 596 String remoteListStr = getRemoteListStr(null); 597 PluginListResponse pluginListResponse = new Persister().read(PluginListResponse.class, remoteListStr); 598 599 return pluginListResponse.getPlugins(); 600 } 601 602 private PluginInfo getRemotePluginInfo(String pluginId) throws Exception { 603 String remoteListStr = getRemoteListStr(pluginId); 604 PluginListResponse pluginListResponse = new Persister().read(PluginListResponse.class, remoteListStr); 605 606 if (pluginListResponse.getPlugins().size() > 0) { 607 return pluginListResponse.getPlugins().get(0); 608 } 609 else { 610 return null; 611 } 612 } 613 614 private String getRemoteListStr(String pluginId) throws Exception { 615 String appVersion = Client.getApplicationVersion(); 616 String snapshotsEnabled = (options.isSnapshots()) ? "true" : "false"; 617 String pluginIdQueryStr = (pluginId != null) ? pluginId : ""; 618 String osStr = EnvironmentUtil.getOperatingSystemDescription(); 619 String archStr = EnvironmentUtil.getArchDescription(); 620 621 String apiEndpointUrl = (options.getApiEndpoint() != null) ? options.getApiEndpoint() : API_DEFAULT_ENDPOINT_URL; 622 URL pluginListUrl = new URL(String.format(API_PLUGIN_LIST_REQUEST_FORMAT, apiEndpointUrl, appVersion, snapshotsEnabled, pluginIdQueryStr, osStr, archStr)); 623 624 logger.log(Level.INFO, "Querying " + pluginListUrl + " ..."); 625 eventBus.post(new ConnectToHostExternalEvent(pluginListUrl.getHost())); 626 627 URLConnection urlConnection = pluginListUrl.openConnection(); 628 urlConnection.setConnectTimeout(2000); 629 urlConnection.setReadTimeout(2000); 630 631 BufferedReader urlStreamReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); 632 StringBuilder responseStringBuilder = new StringBuilder(); 633 634 String line; 635 while ((line = urlStreamReader.readLine()) != null) { 636 responseStringBuilder.append(line); 637 } 638 639 String responseStr = responseStringBuilder.toString(); 640 logger.log(Level.INFO, "Response from api.syncany.org: " + responseStr); 641 642 return responseStr; 643 } 644}