blob: 3a6f3c069735222420505aa2c740f52a2cc22e4b [file] [log] [blame]
/*******************************************************************************
* 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.
*/
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.
*/
public void clearOldDescription(IProject target) {
Workspace.clear(getOldDescriptionLocationFor(target).toFile());
}
/**
* 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.
*/
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.
*/
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).
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
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 <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
*/
@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.lineSeparator());
} 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 <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);
}
}
}