diff options
author | Pascal Rapicault | 2013-02-16 14:37:50 +0000 |
---|---|---|
committer | Pascal Rapicault | 2013-02-16 14:37:50 +0000 |
commit | 31e44d1b2d70d84078ff36f6498258693b6db7d7 (patch) | |
tree | 88b0fdc4fb83425adf4477ff8c4cc86b4fddbbaa | |
parent | 1ff11052e53d648dec8305eea7c51a111e53d39a (diff) | |
download | rt.equinox.p2-31e44d1b2d70d84078ff36f6498258693b6db7d7.tar.gz rt.equinox.p2-31e44d1b2d70d84078ff36f6498258693b6db7d7.tar.xz rt.equinox.p2-31e44d1b2d70d84078ff36f6498258693b6db7d7.zip |
trigger migration when install movedv20130216-143750
3 files changed, 379 insertions, 9 deletions
diff --git a/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/AutomaticUpdatePlugin.java b/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/AutomaticUpdatePlugin.java index a4dac61fd..c55d00019 100644 --- a/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/AutomaticUpdatePlugin.java +++ b/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/AutomaticUpdatePlugin.java @@ -15,8 +15,7 @@ import java.net.URL; import org.eclipse.core.runtime.*; import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus; -import org.eclipse.equinox.p2.core.IAgentLocation; -import org.eclipse.equinox.p2.core.IProvisioningAgent; +import org.eclipse.equinox.p2.core.*; import org.eclipse.equinox.p2.engine.IProfileRegistry; import org.eclipse.equinox.p2.engine.ProfileScope; import org.eclipse.equinox.p2.operations.ProvisioningSession; @@ -182,4 +181,13 @@ public class AutomaticUpdatePlugin extends AbstractUIPlugin { public ProvisioningSession getSession() { return session; } + + public IProvisioningAgentProvider getAgentProvider() { + ServiceReference<?> ref = getContext().getServiceReference(IProvisioningAgentProvider.SERVICE_NAME); + if (ref == null) + return null; + IProvisioningAgentProvider agentProvider = (IProvisioningAgentProvider) getContext().getService(ref); + getContext().ungetService(ref); + return agentProvider; + } } diff --git a/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/AutomaticUpdateScheduler.java b/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/AutomaticUpdateScheduler.java index 674b1f217..417291d74 100644 --- a/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/AutomaticUpdateScheduler.java +++ b/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/AutomaticUpdateScheduler.java @@ -14,10 +14,12 @@ package org.eclipse.equinox.internal.p2.ui.sdk.scheduler; import com.ibm.icu.util.Calendar; import com.ibm.icu.util.ULocale; +import java.io.File; import java.util.Iterator; import java.util.Set; import org.eclipse.core.runtime.*; import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper; +import org.eclipse.equinox.internal.p2.engine.EngineActivator; import org.eclipse.equinox.internal.p2.garbagecollector.GarbageCollector; import org.eclipse.equinox.internal.p2.metadata.query.UpdateQuery; import org.eclipse.equinox.internal.p2.ui.sdk.scheduler.migration.ImportFromInstallationWizard_c; @@ -32,6 +34,7 @@ import org.eclipse.equinox.p2.query.IQuery; import org.eclipse.equinox.p2.query.QueryUtil; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.osgi.service.datalocation.Location; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.*; @@ -45,6 +48,7 @@ import org.eclipse.ui.statushandlers.StatusManager; public class AutomaticUpdateScheduler implements IStartup { private static final String ECLIPSE_P2_SKIP_MIGRATION_WIZARD = "eclipse.p2.skipMigrationWizard"; //$NON-NLS-1$ + private static final String ECLIPSE_P2_SKIP_MOVED_INSTALL_DETECTION = "eclipse.p2.skipMovedInstallDetection"; //$NON-NLS-1$ // values are to be picked up from the arrays DAYS and HOURS public static final String P_DAY = "day"; //$NON-NLS-1$ @@ -108,19 +112,71 @@ public class AutomaticUpdateScheduler implements IStartup { if (skipWizard) return false; - if (!baseChangedSinceLastPresentationOfWizard(agent, registry, currentProfile)) - return false; + IProfile previousProfile = null; + if (!skipFirstTimeMigration() && !configurationSpecifiedManually() && isFirstTimeRunningThisSharedInstance(agent, registry, currentProfile)) { + File searchRoot = getSearchLocation(); + if (searchRoot == null) + return false; + previousProfile = new PreviousConfigurationFinder(getConfigurationLocation().getParentFile()).findPreviousInstalls(searchRoot, getInstallFolder()); + if (previousProfile == null) + return false; + + //At this point we consider that the migration is done since we will present something to the user. + registry.setProfileStateProperty(currentProfile.getProfileId(), registry.listProfileTimestamps(currentProfile.getProfileId())[0], "INITIAL", "DONE"); + } - final IProfile previousProfile = findMostRecentReset(registry, currentProfile); + if (previousProfile == null && baseChangedSinceLastPresentationOfWizard(agent, registry, currentProfile)) + previousProfile = findMostRecentReset(registry, currentProfile); - if (previousProfile != null) { - if (needsMigration(previousProfile, currentProfile)) { - openMigrationWizard(previousProfile); - } + if (previousProfile == null) + return false; + + if (needsMigration(previousProfile, currentProfile)) { + openMigrationWizard(previousProfile); } return true; } + private File getInstallFolder() { + Location configurationLocation = (Location) ServiceHelper.getService(EngineActivator.getContext(), Location.class.getName(), Location.INSTALL_FILTER); + return new File(configurationLocation.getURL().getPath()); + + } + + //The search location is two level up from teh configuration location. + private File getSearchLocation() { + File parent = getConfigurationLocation().getParentFile(); + if (parent == null) + return null; + return parent.getParentFile(); + } + + private File getConfigurationLocation() { + Location configurationLocation = (Location) ServiceHelper.getService(EngineActivator.getContext(), Location.class.getName(), Location.CONFIGURATION_FILTER); + File configurationFolder = new File(configurationLocation.getURL().getPath()); + return configurationFolder; + } + + //Check if the user has explicitly specified -configuration on the command line + private boolean configurationSpecifiedManually() { + String commandLine = System.getProperty("eclipse.commands"); //$NON-NLS-1$ + if (commandLine == null) + return false; + return commandLine.contains("-configuration\n"); //$NON-NLS-1$ + } + + private boolean skipFirstTimeMigration() { + return Boolean.TRUE.toString().equalsIgnoreCase(System.getProperty(ECLIPSE_P2_SKIP_MOVED_INSTALL_DETECTION)); + } + + private boolean isFirstTimeRunningThisSharedInstance(IProvisioningAgent agent, IProfileRegistry registry, IProfile currentProfile) { + long[] history = registry.listProfileTimestamps(currentProfile.getProfileId()); + boolean isInitial = IProfile.STATE_SHARED_INSTALL_VALUE_INITIAL.equals(registry.getProfileStateProperties(currentProfile.getProfileId(), history[0]).get(IProfile.STATE_PROP_SHARED_INSTALL)); + if (isInitial) + return false; + return "DONE".equals(registry.getProfileStateProperties(currentProfile.getProfileId(), history[0]).get("INITIAL")); + } + /** * @param previousProfile is the profile used previous to the current one * @param currentProfile is the current profile used by eclipse. diff --git a/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/PreviousConfigurationFinder.java b/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/PreviousConfigurationFinder.java new file mode 100644 index 000000000..9aa6f2303 --- /dev/null +++ b/bundles/org.eclipse.equinox.p2.ui.sdk.scheduler/src/org/eclipse/equinox/internal/p2/ui/sdk/scheduler/PreviousConfigurationFinder.java @@ -0,0 +1,306 @@ +package org.eclipse.equinox.internal.p2.ui.sdk.scheduler; + +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.eclipse.core.runtime.URIUtil; +import org.eclipse.equinox.p2.core.IProvisioningAgent; +import org.eclipse.equinox.p2.core.ProvisionException; +import org.eclipse.equinox.p2.engine.IProfile; +import org.eclipse.equinox.p2.engine.IProfileRegistry; + +public class PreviousConfigurationFinder { + + private static final Pattern path = Pattern.compile("(.+)_(.*)_(\\d+)_.*"); //$NON-NLS-1$ + + static class Identifier { + private static final String DELIM = ". _-"; //$NON-NLS-1$ + private int major, minor, service; + + Identifier(int major, int minor, int service) { + super(); + this.major = major; + this.minor = minor; + this.service = service; + } + + /** + * @throws NumberFormatException if cannot parse the major and minor version components + */ + Identifier(String versionString) { + super(); + StringTokenizer tokenizer = new StringTokenizer(versionString, DELIM); + + // major + if (tokenizer.hasMoreTokens()) + major = Integer.parseInt(tokenizer.nextToken()); + + // minor + if (tokenizer.hasMoreTokens()) + minor = Integer.parseInt(tokenizer.nextToken()); + + try { + // service + if (tokenizer.hasMoreTokens()) + service = Integer.parseInt(tokenizer.nextToken()); + } catch (NumberFormatException nfe) { + // ignore the service qualifier in that case and default to 0 + // this will allow us to tolerate other non-conventional version numbers + } + } + + /** + * Returns true if this id is considered to be greater than or equal to the given baseline. + * e.g. + * 1.2.9 >= 1.3.1 -> false + * 1.3.0 >= 1.3.1 -> false + * 1.3.1 >= 1.3.1 -> true + * 1.3.2 >= 1.3.1 -> true + * 2.0.0 >= 1.3.1 -> true + */ + boolean isGreaterEqualTo(Identifier minimum) { + if (major < minimum.major) + return false; + if (major > minimum.major) + return true; + // major numbers are equivalent so check minor + if (minor < minimum.minor) + return false; + if (minor > minimum.minor) + return true; + // minor numbers are equivalent so check service + return service >= minimum.service; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Identifier) + return false; + Identifier o = (Identifier) other; + if (major == o.major && minor == o.minor && service == o.service) + return true; + return false; + } + } + + static class ConfigurationData { + String productId; + Identifier version; + String installPathHashcode; + File config; + + public ConfigurationData(String productId, Identifier version, String installPathHashcode, File config) { + this.productId = productId; + this.version = version; + this.installPathHashcode = installPathHashcode; + this.config = config; + } + + public String getProductId() { + return productId; + } + + public Identifier getVersion() { + return version; + } + + public String getInstallPathHashcode() { + return installPathHashcode; + } + + public File getConfig() { + return config; + } + } + + private File currentConfig; + + public PreviousConfigurationFinder(File currentConfiguration) { + currentConfig = currentConfiguration; + } + + private ConfigurationData extractConfigurationData(File candidate) { + Matcher m = path.matcher(candidate.getName()); + if (!m.matches()) + return null; + return new ConfigurationData(m.group(1), new Identifier(m.group(2)), m.group(3), candidate.getAbsoluteFile()); + } + + public IProfile findPreviousInstalls(File searchRoot, File installFolder) { + List<ConfigurationData> potentialConfigurations = readPreviousConfigurations(searchRoot); + Object[] productInfo = loadEclipseProductFile(installFolder); + ConfigurationData match = findMostRelevantConfiguration(potentialConfigurations, getInstallDirHash(installFolder), productInfo); + if (match == null) + match = findMostRelevantConfiguration(potentialConfigurations, productInfo); + if (match == null) + return null; + return fromConfigurationToProfile(match.getConfig()); + } + + private IProfile fromConfigurationToProfile(File configurationFolder) { + //TODO dispose the agent + String toBeImportedProfileId = null; + File config = new File(configurationFolder, "configuration/config.ini"); //$NON-NLS-1$ + URI configArea = config.getParentFile().toURI(); + InputStream is = null; + // default area + File p2DataArea = new File(configurationFolder, "p2"); //$NON-NLS-1$ + try { + Properties props = new Properties(); + is = new FileInputStream(config); + props.load(is); + toBeImportedProfileId = props.getProperty("eclipse.p2.profile"); //$NON-NLS-1$ + String url = props.getProperty("eclipse.p2.data.area"); //$NON-NLS-1$ + if (url != null) { + final String CONFIG_DIR = "@config.dir/"; //$NON-NLS-1$ + final String FILE_PROTOCOL = "file:"; //$NON-NLS-1$ + if (url.startsWith(CONFIG_DIR)) + url = FILE_PROTOCOL + url.substring(CONFIG_DIR.length()); + p2DataArea = new File(URIUtil.makeAbsolute(URIUtil.fromString(new File(url.substring(FILE_PROTOCOL.length())).isAbsolute() ? url : url.substring(FILE_PROTOCOL.length())), configArea)); + } + } catch (IOException ioe) { + //ignore + } catch (URISyntaxException e) { + return null; + } finally { + try { + is.close(); + } catch (IOException ioe) { + //ignore + } + is = null; + } + if (p2DataArea.exists()) { + IProvisioningAgent agent = null; + try { + agent = AutomaticUpdatePlugin.getDefault().getAgentProvider().createAgent(p2DataArea.toURI()); + } catch (ProvisionException e) { + //Can't happen + } + IProfileRegistry registry = (IProfileRegistry) agent.getService(IProfileRegistry.SERVICE_NAME); + if (toBeImportedProfileId != null) + return registry.getProfile(toBeImportedProfileId); + + //TODO we may need to set the SELF profile on the registry to load the repos + IProfile[] allProfiles = registry.getProfiles(); + if (allProfiles.length == 1) + return allProfiles[0]; + + // IMetadataRepositoryManager metadataRepoMgr = (IMetadataRepositoryManager) agent.getService(IMetadataRepositoryManager.SERVICE_NAME); + // URI[] metadataRepos = metadataRepoMgr.getKnownRepositories(IRepositoryManager.REPOSITORIES_NON_SYSTEM); + //TODO deal with the repos + } + return null; + } + + private ConfigurationData findMostRelevantConfiguration(List<ConfigurationData> configurations, String installDirHash, Object[] productInfo) { + ConfigurationData bestMatch = null; + int numberOfcriteriaMet = 0; + for (ConfigurationData candidate : configurations) { + int criteriaMet = 0; + if (!candidate.getInstallPathHashcode().equals(installDirHash)) + continue; + criteriaMet++; + if (!candidate.getProductId().equals(productInfo[0])) + continue; + criteriaMet++; + if (!candidate.getVersion().equals(productInfo[1])) + continue; //This is most likely ourselves + criteriaMet++; + if (criteriaMet > numberOfcriteriaMet) { + bestMatch = candidate; + numberOfcriteriaMet = criteriaMet; + } else if (criteriaMet == numberOfcriteriaMet) { + if (bestMatch.getConfig().lastModified() < candidate.getConfig().lastModified()) + bestMatch = candidate; + } + } + return bestMatch; + } + + //Out of a set of configuration, find the one with the most similar product info. + //TODO do we look for the higer or lower versions? + private ConfigurationData findMostRelevantConfiguration(List<ConfigurationData> configurations, Object[] productInfo) { + ConfigurationData bestMatch = null; + int numberOfcriteriaMet = 0; + for (ConfigurationData candidate : configurations) { + int criteriaMet = 0; + criteriaMet++; + if (!candidate.getProductId().equals(productInfo[0])) + continue; + criteriaMet++; + if (candidate.getVersion().equals(productInfo[1])) + continue; //This is most likely ourselves + else if (bestMatch != null && (candidate.getVersion().equals(bestMatch.getVersion()))) + criteriaMet++; + if (criteriaMet > numberOfcriteriaMet) { + bestMatch = candidate; + numberOfcriteriaMet = criteriaMet; + } else if (criteriaMet == numberOfcriteriaMet) { + if (bestMatch.getConfig().lastModified() < candidate.getConfig().lastModified()) + bestMatch = candidate; + } + } + return bestMatch; + } + + //Load the .eclipseproduct file in the base of the installation. This logic is very similar to the one found in the launcher + private Object[] loadEclipseProductFile(File installDir) { + final String ECLIPSE = "eclipse"; //$NON-NLS-1$ + final String PRODUCT_SITE_MARKER = ".eclipseproduct"; //$NON-NLS-1$ + final String PRODUCT_SITE_ID = "id"; //$NON-NLS-1$ + final String PRODUCT_SITE_VERSION = "version"; //$NON-NLS-1$ + + File eclipseProduct = new File(installDir, PRODUCT_SITE_MARKER); + String appId = null; + Identifier appVersion = null; + if (eclipseProduct.exists()) { + Properties props = new Properties(); + try { + props.load(new FileInputStream(eclipseProduct)); + appId = props.getProperty(PRODUCT_SITE_ID); + if (appId == null || appId.trim().length() == 0) + appId = ECLIPSE; + String version = props.getProperty(PRODUCT_SITE_VERSION); + if (version == null || version.trim().length() == 0) + appVersion = new Identifier(0, 0, 0); + else + appVersion = new Identifier(version); + + } catch (IOException e) { + return new String[0]; + } + } else { + return new String[0]; + } + return new Object[] {appId, appVersion}; + } + + //Iterate through a folder to look for potential configuration folders and reify them. + private List<ConfigurationData> readPreviousConfigurations(File configurationFolder) { + File[] candidates = configurationFolder.listFiles(); + List<ConfigurationData> configurations = new ArrayList<ConfigurationData>(candidates.length); + for (File candidate : candidates) { + if (!candidate.isDirectory()) + continue; + if (candidate.equals(currentConfig)) + continue; + ConfigurationData tmp = extractConfigurationData(candidate); + if (tmp != null) + configurations.add(tmp); + } + return configurations; + } + + //Simplified code computing the hashCode of the install location. The real runtime code is in the launcher + private String getInstallDirHash(File installFolder) { + try { + return Integer.toString(installFolder.getCanonicalPath().hashCode()); + } catch (IOException e) { + return ""; //$NON-NLS-1$ + } + } +} |