/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.update.core.internal.jobs; import com.google.dart.tools.update.core.UpdateCore; import com.google.dart.tools.update.core.internal.UpdateUtils; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.osgi.framework.Bundle; import org.osgi.framework.Version; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; /** * A job to cleanup installation (notably to harvest stale plugins). */ public class CleanupInstallationJob extends Job { /** * Sorts files in reverse lexical order, ensuring that the most recent bundle id is first. * http://wiki.eclipse.org/index.php/Version_Numbering */ private static final Comparator<File> BUNDLE_SORTER = new Comparator<File>() { @Override public int compare(File f1, File f2) { try { return lexicalCompareBundleFileNames(f1.getName(), f2.getName()); } catch (IllegalArgumentException e) { UpdateCore.logError("error parsing bundle versions", e); } //fall back to lexicographic ordering return f2.getName().compareTo(f1.getName()); } }; /** * Threshold for how many versions of a bundle to keep around. */ private static final int MATCH_THRESHOLD = 2; //public for testing public static final int lexicalCompareBundleFileNames(String b1, String b2) throws IllegalArgumentException { if (b1.equalsIgnoreCase(b2)) { return 0; } Version v1 = Version.parseVersion(getBundleVersionDetails(b1)); Version v2 = Version.parseVersion(getBundleVersionDetails(b2)); return v1.compareTo(v2); } /** * Builds a map of (unqualified) bundle names to lists of bundle files. An example entry might * look like this: "org.junit" => [org.junit_4.8.2.v4_8_2_v20110321-1705.jar, * org.junit_3.8.2.v3_8_2_v20100427-1100.jar] */ private static Map<String, List<File>> buildBundleMap(File bundleDir) { File[] files = bundleDir.listFiles(); Map<String, List<File>> map = new HashMap<String, List<File>>(); for (File file : files) { String name = getBundleName(file); List<File> matches = map.get(name); if (matches == null) { matches = new ArrayList<File>(); map.put(name, matches); } matches.add(file); } return map; } private static List<File> findStaleBundles(File bundleDir) { Map<String, List<File>> bundleMap = buildBundleMap(bundleDir); List<File> duplicates = indentifyStaleBundles(bundleMap); return duplicates; } private static String getBundleName(File file) { //strips off version suffix info //for example: org.eclipse.osgi_3.7.2.v20120110-1415.jar => org.eclipse.osgi String name = file.getName(); return name.split("_[0-9].*")[0]; } private static String getBundleVersionDetails(String bundleName) { //strips off name prefix and extension suffix info //for example: org.eclipse.osgi_3.7.2.v20120110-1415.jar => 3.7.2.v20120110-1415 String version = bundleName.indexOf('_') != -1 ? bundleName.split("_")[1] : bundleName; return version.indexOf(".jar") != -1 ? version.substring(0, version.lastIndexOf(".jar")) : version; } private static File getInstallDir() throws IOException { return FileLocator.getBundleFile(UpdateCore.getInstance().getBundle()).getParentFile(); } private static Bundle[] getInstalledBundles() { return UpdateCore.getInstance().getBundle().getBundleContext().getBundles(); } private static List<File> indentifyStaleBundles(Map<String, List<File>> map) { List<File> staleBundles = new ArrayList<File>(); for (List<File> matches : map.values()) { if (matches.size() > MATCH_THRESHOLD) { Collections.sort(matches, BUNDLE_SORTER); for (File match : matches.subList(MATCH_THRESHOLD, matches.size())) { //sanity check to ensure we don't try and remove an active bundle file if (!installedBundle(match)) { staleBundles.add(match); } } } } return staleBundles; } private static boolean installedBundle(File bundleFile) { try { for (Bundle bundle : getInstalledBundles()) { if (FileLocator.getBundleFile(bundle).equals(bundleFile)) { return true; } } } catch (IOException e) { //since we can't be sure, err on the safe side return true; } return false; } public CleanupInstallationJob() { super(UpdateJobMessages.CleanupInstallationJob_label); setSystem(true); } @Override protected IStatus run(IProgressMonitor monitor) { try { List<File> staleBundles = findStaleBundles(getInstallDir()); monitor.beginTask("Removing unused bundles", staleBundles.size()); for (File file : staleBundles) { if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } UpdateUtils.delete(file, monitor); } monitor.done(); } catch (IOException e) { //don't surface the error to the user since there's nothing they can do //(moreover, we'll get another chance to cleanup on restart) UpdateCore.logError(e); } return Status.OK_STATUS; } }