/* * PluginList.java - Plugin list downloaded from server * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 2001, 2003 Slava Pestov * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit.pluginmgr; //{{{ Imports import java.io.*; import java.net.URL; import java.net.HttpURLConnection; import java.util.*; import java.util.zip.GZIPInputStream; import org.gjt.sp.util.*; import org.xml.sax.XMLReader; import org.xml.sax.InputSource; import org.xml.sax.helpers.XMLReaderFactory; import org.gjt.sp.jedit.*; //}}} /** * Plugin list downloaded from server. * @since jEdit 3.2pre2 * @version $Id$ */ class PluginList { /** * Magic numbers used for auto-detecting GZIP files. */ public static final int GZIP_MAGIC_1 = 0x1f; public static final int GZIP_MAGIC_2 = 0x8b; public static final long MILLISECONDS_PER_MINUTE = 60L * 1000L; final List<Plugin> plugins = new ArrayList<>(); final Map<String, Plugin> pluginHash = new HashMap<>(); final List<PluginSet> pluginSets = new ArrayList<>(); /** * The mirror id. * @since jEdit 4.3pre3 */ private final String id; private String cachedURL; private final Task task; String gzipURL; PluginList(Task task) { id = jEdit.getProperty("plugin-manager.mirror.id"); this.task = task; readPluginList(true); } void readPluginList(boolean allowRetry) { String mirror = buildMirror(id); if (mirror == null) return; gzipURL = jEdit.getProperty("plugin-manager.export-url"); gzipURL += "?mirror=" + mirror; String path = null; if (jEdit.getSettingsDirectory() == null) { cachedURL = gzipURL; } else { path = jEdit.getSettingsDirectory() + File.separator + "pluginMgr-Cached.xml.gz"; cachedURL = "file:///" + path; } boolean downloadIt = !id.equals(jEdit.getProperty("plugin-manager.mirror.cached-id")); if (path != null) { try { File f = new File(path); if (!f.canRead()) downloadIt = true; long currentTime = System.currentTimeMillis(); long age = currentTime - f.lastModified(); /* By default only download plugin lists every 5 minutes */ long interval = jEdit.getIntegerProperty("plugin-manager.list-cache.minutes", 5) * MILLISECONDS_PER_MINUTE; if (age > interval) { Log.log(Log.MESSAGE, this, "PluginList cached copy too old. Downloading from mirror. "); downloadIt = true; } } catch (Exception e) { Log.log(Log.MESSAGE, this, "No cached copy. Downloading from mirror. "); downloadIt = true; } } if (downloadIt && cachedURL != gzipURL) { downloadPluginList(); } InputStream in = null, inputStream = null; try { if (cachedURL != gzipURL) Log.log(Log.MESSAGE, this, "Using cached pluginlist"); inputStream = new URL(cachedURL).openStream(); XMLReader parser = XMLReaderFactory.createXMLReader(); PluginListHandler handler = new PluginListHandler(this, cachedURL); in = new BufferedInputStream(inputStream); if(in.markSupported()) { in.mark(2); int b1 = in.read(); int b2 = in.read(); in.reset(); if(b1 == GZIP_MAGIC_1 && b2 == GZIP_MAGIC_2) in = new GZIPInputStream(in); } InputSource isrc = new InputSource(new InputStreamReader(in,"UTF8")); isrc.setSystemId("jedit.jar"); parser.setContentHandler(handler); parser.setDTDHandler(handler); parser.setEntityResolver(handler); parser.setErrorHandler(handler); parser.parse(isrc); } catch (Exception e) { Log.log(Log.ERROR, this, "readpluginlist: error", e); if (cachedURL.startsWith("file:///")) { Log.log(Log.DEBUG, this, "Unable to read plugin list, deleting cached file and try again"); new File(cachedURL.substring(8)).delete(); if (allowRetry) { plugins.clear(); pluginHash.clear(); pluginSets.clear(); readPluginList(false); } } } finally { IOUtilities.closeQuietly((Closeable)in); IOUtilities.closeQuietly((Closeable)inputStream); } } /** Caches it locally */ void downloadPluginList() { BufferedInputStream is = null; BufferedOutputStream out = null; /* download the plugin list, while trying to show informative error messages. * Currently when : * - the proxy requires authentication * - another HTTP error happens (may be good to know that the site is broken) * - the host can't be reached (reported as internet access error) * Otherwise, only an error message is logged in the activity log. **/ try { task.setStatus(jEdit.getProperty("plugin-manager.list-download")); URL downloadURL = new URL(gzipURL); HttpURLConnection c = (HttpURLConnection)downloadURL.openConnection(); if(c.getResponseCode() == HttpURLConnection.HTTP_PROXY_AUTH) { GUIUtilities.error(jEdit.getActiveView() , "plugin-manager.list-download.need-password" , new Object[]{}); Log.log (Log.ERROR, this, "CacheRemotePluginList: proxy requires authentication"); } else if(c.getResponseCode() == HttpURLConnection.HTTP_OK) { InputStream inputStream = c.getInputStream(); String fileName = cachedURL.replaceFirst("file:///", ""); out = new BufferedOutputStream(new FileOutputStream(fileName)); long start = System.currentTimeMillis(); is = new BufferedInputStream(inputStream); IOUtilities.copyStream(4096, null, is, out, false); jEdit.setProperty("plugin-manager.mirror.cached-id", id); Log.log(Log.MESSAGE, this, "Updated cached pluginlist " + (System.currentTimeMillis() - start)); } else { GUIUtilities.error(jEdit.getActiveView() , "plugin-manager.list-download.generic-error" , new Object[]{c.getResponseCode(), c.getResponseMessage()}); Log.log (Log.ERROR, this, "CacheRemotePluginList: HTTP error: "+c.getResponseCode()+ c.getResponseMessage()); } } catch(java.net.UnknownHostException e) { GUIUtilities.error(jEdit.getActiveView() , "plugin-manager.list-download.disconnected" , new Object[]{e.getMessage()}); Log.log (Log.ERROR, this, "CacheRemotePluginList: error", e); } catch (Exception e) { Log.log (Log.ERROR, this, "CacheRemotePluginList: error", e); } finally { IOUtilities.closeQuietly((Closeable)out); IOUtilities.closeQuietly((Closeable)is); } } //{{{ addPlugin() method void addPlugin(Plugin plugin) { plugins.add(plugin); pluginHash.put(plugin.name,plugin); } //}}} //{{{ addPluginSet() method void addPluginSet(PluginSet set) { pluginSets.add(set); } //}}} //{{{ finished() method void finished() { // after the entire list is loaded, fill out plugin field // in dependencies for (Plugin plugin : plugins) { for (int j = 0; j < plugin.branches.size(); j++) { Branch branch = plugin.branches.get(j); for (int k = 0; k < branch.deps.size(); k++) { Dependency dep = branch.deps.get(k); if (dep.what.equals("plugin")) dep.plugin = pluginHash.get(dep.pluginName); } } } } //}}} //{{{ dump() method void dump() { for (Plugin plugin : plugins) { System.err.println(plugin); System.err.println(); } } //}}} //{{{ getMirrorId() method /** * Returns the mirror ID. * * @return the mirror ID * @since jEdit 4.3pre3 */ String getMirrorId() { return id; } //}}} //{{{ PluginSet class static class PluginSet { String name; final List<String> plugins = new ArrayList<>(); public String toString() { return plugins.toString(); } } //}}} //{{{ Plugin class public static class Plugin { String jar; String name; String description; String author; final List<Branch> branches = new ArrayList<>(); String installedVersion = null; String installedPath = null; boolean loaded = false; String getInstalledVersion() { this.loaded = false; PluginJAR[] jars = jEdit.getPluginJARs(); for(int i = 0; i < jars.length; i++) { String path = jars[i].getPath(); if(MiscUtilities.getFileName(path).equals(jar)) { EditPlugin plugin = jars[i].getPlugin(); if(plugin != null) { installedVersion = jEdit.getProperty( "plugin." + plugin.getClassName() + ".version"); this.loaded = true; return installedVersion; } else return null; } } String[] notLoadedJars = jEdit.getNotLoadedPluginJARs(); for(String path: notLoadedJars){ if(MiscUtilities.getFileName(path).equals(jar)) { try { PluginJAR.PluginCacheEntry cacheEntry = PluginJAR.getPluginCacheEntry(path); if(cacheEntry != null) { String versionKey = "plugin." + cacheEntry.pluginClass + ".version"; installedVersion = cacheEntry.cachedProperties.getProperty(versionKey); Log.log(Log.DEBUG, PluginList.class, "found installed but not loaded "+ jar + " version=" + installedVersion); installedPath = path; return installedVersion; } } catch (IOException e) { Log.log(Log.WARNING, "Unable to access cache for "+jar, e); } } } return null; } String getInstalledPath() { if(installedPath != null){ if(new File(installedPath).exists()){ return installedPath; }else{ installedPath = null; } } PluginJAR[] jars = jEdit.getPluginJARs(); for(int i = 0; i < jars.length; i++) { String path = jars[i].getPath(); if(MiscUtilities.getFileName(path).equals(jar)) return path; } return null; } /** * Find the first branch compatible with the running jEdit release. */ Branch getCompatibleBranch() { for (Branch branch : branches) { if (branch.canSatisfyDependencies()) return branch; } return null; } boolean canBeInstalled() { Branch branch = getCompatibleBranch(); return branch != null && !branch.obsolete && branch.canSatisfyDependencies(); } void install(Roster roster, String installDirectory, boolean downloadSource, boolean asDependency) { String installed = getInstalledPath(); Branch branch = getCompatibleBranch(); if(branch.obsolete) { if(installed != null) roster.addRemove(installed); return; } //branch.satisfyDependencies(roster,installDirectory, // downloadSource); if(installedVersion != null && installedPath!= null && !loaded && asDependency) { roster.addLoad(installedPath); return; } if(installed != null) { installDirectory = MiscUtilities.getParentOfPath( installed); } roster.addInstall( installed, downloadSource ? branch.downloadSource : branch.download, installDirectory, downloadSource ? branch.downloadSourceSize : branch.downloadSize); } public String toString() { return name; } } //}}} //{{{ Branch class static class Branch { String version; String date; int downloadSize; String download; int downloadSourceSize; String downloadSource; boolean obsolete; final List<Dependency> deps = new ArrayList<>(); boolean canSatisfyDependencies() { for (Dependency dep : deps) { if (!dep.canSatisfy()) return false; } return true; } void satisfyDependencies(Roster roster, String installDirectory, boolean downloadSource) { for (Dependency dep : deps) dep.satisfy(roster, installDirectory, downloadSource); } public String depsToString() { StringBuilder sb = new StringBuilder(); for (Dependency dep : deps) { if ("plugin".equals(dep.what) && dep.pluginName != null) { sb.append(dep.pluginName).append('\n'); } } return sb.toString(); } public String toString() { return "[version=" + version + ",download=" + download + ",obsolete=" + obsolete + ",deps=" + deps + ']'; } } //}}} //{{{ Dependency class static class Dependency { final String what; final String from; final String to; // only used if what is "plugin" final String pluginName; Plugin plugin; Dependency(String what, String from, String to, String pluginName) { this.what = what; this.from = from; this.to = to; this.pluginName = pluginName; } boolean isSatisfied() { if(what.equals("plugin")) { for(int i = 0; i < plugin.branches.size(); i++) { String installedVersion = plugin.getInstalledVersion(); if(installedVersion != null && (from == null || StandardUtilities.compareStrings( installedVersion,from,false) >= 0) && (to == null || StandardUtilities.compareStrings( installedVersion,to,false) <= 0)) { return true; } } return false; } else if(what.equals("jdk")) { String javaVersion = System.getProperty("java.version").substring(0,3); if((from == null || StandardUtilities.compareStrings( javaVersion,from,false) >= 0) && (to == null || StandardUtilities.compareStrings( javaVersion,to,false) <= 0)) return true; else return false; } else if(what.equals("jedit")) { String build = jEdit.getBuild(); if((from == null || StandardUtilities.compareStrings( build,from,false) >= 0) && (to == null || StandardUtilities.compareStrings( build,to,false) <= 0)) return true; else return false; } else { Log.log(Log.ERROR,this,"Invalid dependency: " + what); return false; } } boolean canSatisfy() { if(isSatisfied()) return true; if (what.equals("plugin")) return plugin.canBeInstalled(); return false; } void satisfy(Roster roster, String installDirectory, boolean downloadSource) { if(what.equals("plugin")) { String installedVersion = plugin.getInstalledVersion(); for(int i = 0; i < plugin.branches.size(); i++) { Branch branch = plugin.branches.get(i); if((installedVersion == null || StandardUtilities.compareStrings( installedVersion,branch.version,false) < 0) && (from == null || StandardUtilities.compareStrings( branch.version,from,false) >= 0) && (to == null || StandardUtilities.compareStrings( branch.version,to,false) <= 0)) { plugin.install(roster,installDirectory, downloadSource, false); return; } } } } public String toString() { return "[what=" + what + ",from=" + from + ",to=" + to + ",plugin=" + plugin + ']'; } } //}}} //{{{ Private members private static String buildMirror(String id) { if (id != null && !id.equals(MirrorList.Mirror.NONE)) { return id; } try { return getAutoSelectedMirror(); } catch (Exception e) { GUIUtilities.error(jEdit.getActiveView() , "plugin-manager.list-download.mirror-autoselect-error" , new Object[]{e}); Log.log(Log.DEBUG, PluginList.class, "Getting auto-selected mirror: error", e); } return null; } private static String getAutoSelectedMirror() throws java.io.IOException { final String samplerUrl = "http://sourceforge.net/projects/jedit/files/latest/download"; final HttpURLConnection connection = (HttpURLConnection)((new URL(samplerUrl)).openConnection()); connection.setInstanceFollowRedirects(false); final int response = connection.getResponseCode(); if (response != HttpURLConnection.HTTP_MOVED_TEMP) { throw new RuntimeException("Unexpected response: " + response + ": from " + samplerUrl); } final String redirected = connection.getHeaderField("Location"); if (redirected == null) { throw new RuntimeException("Missing Location header: " + samplerUrl); } final String prefix = "use_mirror="; final int found = redirected.lastIndexOf(prefix); if (found == -1) { throw new RuntimeException("Mirror prefix \"use_mirror\" was not found in redirected URL: " + redirected); } final int start = found + prefix.length(); final int end = redirected.indexOf('&', start); return end != -1 ? redirected.substring(start, end) : redirected.substring(start); } //}}} }