Copyright (c) 2000, 2016 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation Francis Lynch (Wind River) - [301563] Save and load tree snapshots Broadcom Corporation - ongoing development Sergey Prigogin (Google) - [437005] Out-of-date .snap file prevents Eclipse from running Lars Vogel - Bug 473427 Mickael Istria (Red Hat Inc.) - Bug 488937
/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation * Francis Lynch (Wind River) - [301563] Save and load tree snapshots * Broadcom Corporation - ongoing development * Sergey Prigogin (Google) - [437005] Out-of-date .snap file prevents Eclipse from running * Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427 * Mickael Istria (Red Hat Inc.) - Bug 488937 *******************************************************************************/
package org.eclipse.core.internal.resources; import java.io.*; import java.net.URI; import java.util.HashMap; import java.util.Map; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.internal.localstore.SafeChunkyInputStream; import org.eclipse.core.internal.localstore.SafeChunkyOutputStream; import org.eclipse.core.internal.utils.Messages; import org.eclipse.core.internal.utils.Policy; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.osgi.util.NLS; public class LocalMetaArea implements ICoreConstants { /* package */static final String F_BACKUP_FILE_EXTENSION = ".bak"; //$NON-NLS-1$ /* package */static final String F_DESCRIPTION = ".workspace"; //$NON-NLS-1$ /* package */static final String F_HISTORY_STORE = ".history"; //$NON-NLS-1$ /* package */static final String F_MARKERS = ".markers"; //$NON-NLS-1$ /* package */static final String F_OLD_PROJECT = ".prj"; //$NON-NLS-1$ /* package */static final String F_PROJECT_LOCATION = ".location"; //$NON-NLS-1$ /* package */static final String F_PROJECTS = ".projects"; //$NON-NLS-1$ /* package */static final String F_PROPERTIES = ".properties"; //$NON-NLS-1$ /* package */static final String F_REFRESH = ".refresh"; //$NON-NLS-1$ /* package */static final String F_ROOT = ".root"; //$NON-NLS-1$ /* package */static final String F_SAFE_TABLE = ".safetable"; //$NON-NLS-1$ /* package */static final String F_SNAP = ".snap"; //$NON-NLS-1$ /* package */static final String F_SNAP_EXTENSION = "snap"; //$NON-NLS-1$ /* package */static final String F_SYNCINFO = ".syncinfo"; //$NON-NLS-1$ /* package */static final String F_TREE = ".tree"; //$NON-NLS-1$ /* package */static final String URI_PREFIX = "URI//"; //$NON-NLS-1$ /* package */static final String F_METADATA = ".metadata"; //$NON-NLS-1$ protected final IPath metaAreaLocation;
The project location is just stored as an optimization, to avoid recomputing it.
/** * The project location is just stored as an optimization, to avoid recomputing it. */
protected final IPath projectMetaLocation; public LocalMetaArea() { super(); metaAreaLocation = ResourcesPlugin.getPlugin().getStateLocation(); projectMetaLocation = metaAreaLocation.append(F_PROJECTS); }
For backwards compatibility, if there is a project at the old project description location, delete it.
/** * For backwards compatibility, if there is a project at the old project * description location, delete it. */
public void clearOldDescription(IProject target) { Workspace.clear(getOldDescriptionLocationFor(target).toFile()); }
Delete the refresh snapshot once it has been used to open a new project.
/** * Delete the refresh snapshot once it has been used to open a new project. */
public void clearRefresh(IProject target) { Workspace.clear(getRefreshLocationFor(target).toFile()); } public void create(IProject target) { java.io.File file = locationFor(target).toFile(); //make sure area is empty Workspace.clear(file); file.mkdirs(); }
Creates the meta area root directory.
/** * Creates the meta area root directory. */
public synchronized void createMetaArea() throws CoreException { java.io.File workspaceLocation = metaAreaLocation.toFile(); Workspace.clear(workspaceLocation); if (!workspaceLocation.mkdirs()) { String message = NLS.bind(Messages.resources_writeWorkspaceMeta, workspaceLocation); throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, null); } }
The project is being deleted. Delete all meta-data associated with the project.
/** * The project is being deleted. Delete all meta-data associated with the * project. */
public void delete(IProject target) throws CoreException { IPath path = locationFor(target); if (!Workspace.clear(path.toFile()) && path.toFile().exists()) { String message = NLS.bind(Messages.resources_deleteMeta, target.getFullPath()); throw new ResourceException(IResourceStatus.FAILED_DELETE_METADATA, target.getFullPath(), message, null); } } public IPath getBackupLocationFor(IPath file) { return file.removeLastSegments(1).append(file.lastSegment() + F_BACKUP_FILE_EXTENSION); } public IPath getHistoryStoreLocation() { return metaAreaLocation.append(F_HISTORY_STORE); }
Returns the local file system location which contains the META data for the resources plugin (i.e., the entire workspace).
/** * Returns the local file system location which contains the META data for * the resources plugin (i.e., the entire workspace). */
public IPath getLocation() { return metaAreaLocation; }
Returns the path of the file in which to save markers for the given resource. Should only be called for the workspace root and projects.
/** * Returns the path of the file in which to save markers for the given * resource. Should only be called for the workspace root and projects. */
public IPath getMarkersLocationFor(IResource resource) { Assert.isNotNull(resource); Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); return locationFor(resource).append(F_MARKERS); }
Returns the path of the file in which to snapshot markers for the given resource. Should only be called for the workspace root and projects.
/** * Returns the path of the file in which to snapshot markers for the given * resource. Should only be called for the workspace root and projects. */
public IPath getMarkersSnapshotLocationFor(IResource resource) { return getMarkersLocationFor(resource).addFileExtension(F_SNAP_EXTENSION); }
The project description file is the only metadata file stored outside the metadata area. It is stored as a file directly under the project location. For backwards compatibility, we also have to check for a project file at the old location in the metadata area.
/** * The project description file is the only metadata file stored outside * the metadata area. It is stored as a file directly under the project * location. For backwards compatibility, we also have to check for a * project file at the old location in the metadata area. */
public IPath getOldDescriptionLocationFor(IProject target) { return locationFor(target).append(F_OLD_PROJECT); } public IPath getOldWorkspaceDescriptionLocation() { return metaAreaLocation.append(F_DESCRIPTION); } public IPath getPropertyStoreLocation(IResource resource) { int type = resource.getType(); Assert.isTrue(type != IResource.FILE && type != IResource.FOLDER); return locationFor(resource).append(F_PROPERTIES); }
Returns the path of the file in which to save the refresh snapshot for the given project.
/** * Returns the path of the file in which to save the refresh snapshot for * the given project. */
public IPath getRefreshLocationFor(IProject project) { Assert.isNotNull(project); return locationFor(project).append(F_REFRESH); } public IPath getSafeTableLocationFor(String pluginId) { IPath prefix = metaAreaLocation.append(F_SAFE_TABLE); // if the plugin is the resources plugin, we return the master table // location if (pluginId.equals(ResourcesPlugin.PI_RESOURCES)) return prefix.append(pluginId); // master table int saveNumber = getWorkspace().getSaveManager().getSaveNumber(pluginId); return prefix.append(pluginId + "." + saveNumber); //$NON-NLS-1$ }
Returns the path of the snapshot file. The name of the file is composed from a sequence number corresponding to the sequence number of tree file and ".snap" extension. Should only be called for the workspace root.
/** * Returns the path of the snapshot file. The name of the file is composed from a sequence * number corresponding to the sequence number of tree file and ".snap" extension. Should * only be called for the workspace root. */
public IPath getSnapshotLocationFor(IResource resource) { Assert.isNotNull(resource); Assert.isLegal(resource.getType() == IResource.ROOT); IPath key = resource.getFullPath().append(F_TREE); String sequenceNumber = getWorkspace().getSaveManager().getMasterTable().getProperty(key.toString()); if (sequenceNumber == null) sequenceNumber = "0"; //$NON-NLS-1$ return metaAreaLocation.append(sequenceNumber + F_SNAP); }
Returns the legacy, pre-4.4.1, path of the snapshot file. The name of the legacy snapshot file is ".snap". Should only be called for the workspace root.
/** * Returns the legacy, pre-4.4.1, path of the snapshot file. The name of the legacy snapshot * file is ".snap". Should only be called for the workspace root. */
public IPath getLegacySnapshotLocationFor(IResource resource) { Assert.isNotNull(resource); Assert.isLegal(resource.getType() == IResource.ROOT); return metaAreaLocation.append(F_SNAP); }
Returns the path of the file in which to save the sync information for the given resource. Should only be called for the workspace root and projects.
/** * Returns the path of the file in which to save the sync information for * the given resource. Should only be called for the workspace root and * projects. */
public IPath getSyncInfoLocationFor(IResource resource) { Assert.isNotNull(resource); Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT); return locationFor(resource).append(F_SYNCINFO); }
Returns the path of the file in which to snapshot the sync information for the given resource. Should only be called for the workspace root and projects.
/** * Returns the path of the file in which to snapshot the sync information * for the given resource. Should only be called for the workspace root and * projects. */
public IPath getSyncInfoSnapshotLocationFor(IResource resource) { return getSyncInfoLocationFor(resource).addFileExtension(F_SNAP_EXTENSION); }
Returns the local file system location of the tree file for the given resource. This file does not follow the same save number as its plug-in. So, the number here is called "sequence number" and not "save number" to avoid confusion.
/** * Returns the local file system location of the tree file for the given * resource. This file does not follow the same save number as its plug-in. * So, the number here is called "sequence number" and not "save number" to * avoid confusion. */
public IPath getTreeLocationFor(IResource target, boolean updateSequenceNumber) { IPath key = target.getFullPath().append(F_TREE); String sequenceNumber = getWorkspace().getSaveManager().getMasterTable().getProperty(key.toString()); if (sequenceNumber == null) sequenceNumber = "0"; //$NON-NLS-1$ if (updateSequenceNumber) { int n = Integer.parseInt(sequenceNumber) + 1; n = n < 0 ? 1 : n; sequenceNumber = Integer.toString(n); getWorkspace().getSaveManager().getMasterTable().setProperty(key.toString(), sequenceNumber); } return locationFor(target).append(sequenceNumber + F_TREE); } public IPath getWorkingLocation(IResource resource, String id) { return locationFor(resource).append(id); } protected Workspace getWorkspace() { return (Workspace) ResourcesPlugin.getWorkspace(); } public boolean hasSavedProject(IProject project) { //if there is a location file, then the project exists return getOldDescriptionLocationFor(project).toFile().exists() || locationFor(project).append(F_PROJECT_LOCATION).toFile().exists(); } public boolean hasSavedWorkspace() { return metaAreaLocation.toFile().exists() || getBackupLocationFor(metaAreaLocation).toFile().exists(); }
Returns the local file system location in which the meta data for the resource with the given path is stored.
/** * Returns the local file system location in which the meta data for the * resource with the given path is stored. */
public IPath locationFor(IPath resourcePath) { if (Path.ROOT.equals(resourcePath)) return metaAreaLocation.append(F_ROOT); return projectMetaLocation.append(resourcePath.segment(0)); }
Returns the local file system location in which the meta data for the given resource is stored.
/** * Returns the local file system location in which the meta data for the * given resource is stored. */
public IPath locationFor(IResource resource) { if (resource.getType() == IResource.ROOT) return metaAreaLocation.append(F_ROOT); return projectMetaLocation.append(resource.getProject().getName()); }
Reads and returns the project description for the given project. Returns null if there was no project description file on disk. Throws an exception if there was any failure to read the project.
/** * Reads and returns the project description for the given project. Returns * null if there was no project description file on disk. Throws an * exception if there was any failure to read the project. */
public ProjectDescription readOldDescription(IProject project) throws CoreException { IPath path = getOldDescriptionLocationFor(project); if (!path.toFile().exists()) return null; IPath tempPath = getBackupLocationFor(path); ProjectDescription description = null; try { description = new ProjectDescriptionReader(project).read(path, tempPath); } catch (IOException e) { String msg = NLS.bind(Messages.resources_readMeta, project.getName()); throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, project.getFullPath(), msg, e); } if (description == null) { String msg = NLS.bind(Messages.resources_readMeta, project.getName()); throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, project.getFullPath(), msg, null); } return description; }
Provides backward compatibility with existing workspaces based on descriptions.
/** * Provides backward compatibility with existing workspaces based on * descriptions. */
public WorkspaceDescription readOldWorkspace() { IPath path = getOldWorkspaceDescriptionLocation(); IPath tempPath = getBackupLocationFor(path); try { WorkspaceDescription oldDescription = (WorkspaceDescription) new WorkspaceDescriptionReader().read(path, tempPath); // if one of those files exist, get rid of them Workspace.clear(path.toFile()); Workspace.clear(tempPath.toFile()); return oldDescription; } catch (IOException e) { return null; } }
Returns the portions of the project description that are private, and adds them to the supplied project description. In particular, the project location, the project's dynamic references and build configurations are stored here. The project location will be set to null if the default location should be used. In the case of failure, log the exception and return silently, thus reverting to using the default location and no dynamic references. The current format of the location file is: UTF - project location int - number of dynamic project references UTF - project reference 1 ... repeat for remaining references since 3.7: int - number of build configurations UTF - configuration name in order ... repeated for N configurations UTF - active build configuration name int - number of build configurations with refs UTF - build configuration name int - number of referenced configuration UTF - project name bool - hasConfigName UTF - configName if hasConfigName ... repeat for number of referenced configurations ... repeat for number of build configurations with references
/** * Returns the portions of the project description that are private, and * adds them to the supplied project description. In particular, the * project location, the project's dynamic references and build configurations * are stored here. * The project location will be set to <code>null</code> if the default * location should be used. In the case of failure, log the exception and * return silently, thus reverting to using the default location and no * dynamic references. The current format of the location file is: * UTF - project location * int - number of dynamic project references * UTF - project reference 1 * ... repeat for remaining references * since 3.7: * int - number of build configurations * UTF - configuration name in order * ... repeated for N configurations * UTF - active build configuration name * int - number of build configurations with refs * UTF - build configuration name * int - number of referenced configuration * UTF - project name * bool - hasConfigName * UTF - configName if hasConfigName * ... repeat for number of referenced configurations * ... repeat for number of build configurations with references */
public void readPrivateDescription(IProject target, ProjectDescription description) { IPath locationFile = locationFor(target).append(F_PROJECT_LOCATION); java.io.File file = locationFile.toFile(); if (!file.exists()) { locationFile = getBackupLocationFor(locationFile); file = locationFile.toFile(); if (!file.exists()) return; } try (DataInputStream dataIn = new DataInputStream(new SafeChunkyInputStream(file, 500))) { try { String location = dataIn.readUTF(); if (location.length() > 0) { //location format < 3.2 was a local file system OS path //location format >= 3.2 is: URI_PREFIX + uri.toString() if (location.startsWith(URI_PREFIX)) description.setLocationURI(URI.create(location.substring(URI_PREFIX.length()))); else description.setLocationURI(URIUtil.toURI(Path.fromOSString(location))); } } catch (Exception e) { //don't allow failure to read the location to propagate String msg = NLS.bind(Messages.resources_exReadProjectLocation, target.getName()); Policy.log(new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e)); } //try to read the dynamic references - will fail for old location files int numRefs = dataIn.readInt(); IProject[] references = new IProject[numRefs]; IWorkspaceRoot root = getWorkspace().getRoot(); for (int i = 0; i < numRefs; i++) references[i] = root.getProject(dataIn.readUTF()); description.setDynamicReferences(references); // Since 3.7 - Build Configurations String[] configs = new String[dataIn.readInt()]; for (int i = 0; i < configs.length; i++) configs[i] = dataIn.readUTF(); if (configs.length > 0) // In the future we may decide this is better stored in the // .project, so only set if configs.length > 0 description.setBuildConfigs(configs); // Active configuration name description.setActiveBuildConfig(dataIn.readUTF()); // Build configuration references? int numBuildConifgsWithRefs = dataIn.readInt(); HashMap<String, IBuildConfiguration[]> m = new HashMap<>(numBuildConifgsWithRefs); for (int i = 0; i < numBuildConifgsWithRefs; i++) { String configName = dataIn.readUTF(); numRefs = dataIn.readInt(); IBuildConfiguration[] refs = new IBuildConfiguration[numRefs]; for (int j = 0; j < numRefs; j++) { String projName = dataIn.readUTF(); if (dataIn.readBoolean()) refs[j] = new BuildConfiguration(root.getProject(projName), dataIn.readUTF()); else refs[j] = new BuildConfiguration(root.getProject(projName), null); } m.put(configName, refs); } description.setBuildConfigReferences(m); } catch (IOException e) { //ignore - this is an old location file or an exception occurred // closing the stream } }
Writes the workspace description to the local meta area. This method is synchronized to prevent multiple current write attempts.
Deprecated:should not be called any more - workspace preferences are now maintained in the plug-in's preferences
/** * Writes the workspace description to the local meta area. This method is * synchronized to prevent multiple current write attempts. * * @deprecated should not be called any more - workspace preferences are * now maintained in the plug-in's preferences */
@Deprecated public synchronized void write(WorkspaceDescription description) throws CoreException { IPath path = getOldWorkspaceDescriptionLocation(); path.toFile().getParentFile().mkdirs(); IPath tempPath = getBackupLocationFor(path); try { new ModelObjectWriter().write(description, path, tempPath, System.getProperty("line.separator")); //$NON-NLS-1$ } catch (IOException e) { String message = NLS.bind(Messages.resources_writeWorkspaceMeta, path); throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, e); } }
Write the private project description information, including the location and the dynamic project references. See readPrivateDescription for details on the file format.
/** * Write the private project description information, including the location * and the dynamic project references. See <tt>readPrivateDescription</tt> * for details on the file format. */
public void writePrivateDescription(IProject target) throws CoreException { IPath location = locationFor(target).append(F_PROJECT_LOCATION); java.io.File file = location.toFile(); //delete any old location file Workspace.clear(file); //don't write anything if there is no interesting private metadata ProjectDescription desc = ((Project) target).internalGetDescription(); if (desc == null) return; final URI projectLocation = desc.getLocationURI(); final IProject[] prjRefs = desc.getDynamicReferences(false); final String[] buildConfigs = desc.configNames; final Map<String, IBuildConfiguration[]> configRefs = desc.getBuildConfigReferences(false); if (projectLocation == null && prjRefs.length == 0 && buildConfigs.length == 0 && configRefs.isEmpty()) return; //write the private metadata file try (SafeChunkyOutputStream output = new SafeChunkyOutputStream(file); DataOutputStream dataOut = new DataOutputStream(output);) { if (projectLocation == null) dataOut.writeUTF(""); //$NON-NLS-1$ else dataOut.writeUTF(URI_PREFIX + projectLocation); dataOut.writeInt(prjRefs.length); for (IProject prjRef : prjRefs) dataOut.writeUTF(prjRef.getName()); // Since 3.7 - build configurations + references // Write out the build configurations dataOut.writeInt(buildConfigs.length); for (String buildConfig : buildConfigs) { dataOut.writeUTF(buildConfig); } // Write active configuration name dataOut.writeUTF(desc.getActiveBuildConfig()); // Write out the configuration level references dataOut.writeInt(configRefs.size()); for (Map.Entry<String, IBuildConfiguration[]> e : configRefs.entrySet()) { String refdName = e.getKey(); IBuildConfiguration[] refs = e.getValue(); dataOut.writeUTF(refdName); dataOut.writeInt(refs.length); for (IBuildConfiguration ref : refs) { dataOut.writeUTF(ref.getProject().getName()); if (ref.getName() == null) { dataOut.writeBoolean(false); } else { dataOut.writeBoolean(true); dataOut.writeUTF(ref.getName()); } } } output.succeed(); } catch (IOException e) { String message = NLS.bind(Messages.resources_exSaveProjectLocation, target.getName()); throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e); } } }