blob: 7aac75984d2c3dd5acd27cca08e292e44d64980d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Red Hat Incorporated - is/setExecutable() code
*******************************************************************************/
package org.eclipse.team.internal.ccvs.core.resources;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.*;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.core.variants.CachedResourceVariant;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.*;
import org.eclipse.team.internal.ccvs.core.client.Command.*;
import org.eclipse.team.internal.ccvs.core.client.Log;
import org.eclipse.team.internal.ccvs.core.client.listeners.ILogEntryListener;
import org.eclipse.team.internal.ccvs.core.client.listeners.LogListener;
import org.eclipse.team.internal.ccvs.core.connection.CVSServerException;
import org.eclipse.team.internal.ccvs.core.filehistory.CVSResourceVariantFileRevision;
import org.eclipse.team.internal.ccvs.core.filesystem.CVSURI;
import org.eclipse.team.internal.ccvs.core.syncinfo.*;
/**
* This class provides the implementation of ICVSRemoteFile and IManagedFile for
* use by the repository and sync view.
*/
public class RemoteFile extends RemoteResource implements ICVSRemoteFile {
/*
* Listener for accumulating the entries fetched using the "cvs log" command
*/
private final class LogEntryListener implements ILogEntryListener {
private final List<ILogEntry> entries = new ArrayList<>();
@Override
public void handleLogEntryReceived(ILogEntry entry) {
if (entry.getRemoteFile().getRepositoryRelativePath().equals(getRepositoryRelativePath())) {
entries.add(entry);
}
}
public ILogEntry[] getEntries() {
return entries.toArray(new ILogEntry[entries.size()]);
}
}
// sync info in byte form
private byte[] syncBytes;
// cache the log entry for the remote file
private ILogEntry entry;
// state that indicates that the handle is actively fetching content
private boolean fetching = false;
// executable bit
private boolean executable = false;
/**
* Static method which creates a file as a single child of its parent.
* This should only be used when one is only interested in the file alone.
*
* The returned RemoteFile represents the base of the local resource.
* If the local resource does not have a base, then null is returned
* even if the resource does exists remotely (e.g. created by another party).
*/
public static RemoteFile getBase(RemoteFolder parent, ICVSFile managed) throws CVSException {
Assert.isNotNull(parent, "A parent folder must be provided for file " + managed.getName()); //$NON-NLS-1$
byte[] syncBytes = managed.getSyncBytes();
if ((syncBytes == null) || ResourceSyncInfo.isAddition(syncBytes)) {
// Either the file is unmanaged or has just been added (i.e. doesn't necessarily have a remote)
return null;
}
if (ResourceSyncInfo.isDeletion(syncBytes)) {
syncBytes = ResourceSyncInfo.convertFromDeletion(syncBytes);
}
RemoteFile file = new RemoteFile(parent, syncBytes);
parent.setChildren(new ICVSRemoteResource[] {file});
return file;
}
/**
* This method is used by the CVS subscribers to create file handles.
*/
public static RemoteFile fromBytes(IResource local, byte[] bytes, byte[] parentBytes) throws CVSException {
Assert.isNotNull(bytes);
Assert.isTrue(local.getType() == IResource.FILE);
RemoteFolder parent = RemoteFolder.fromBytes(local.getParent(), parentBytes);
RemoteFile file = new RemoteFile(parent, bytes);
parent.setChildren(new ICVSRemoteResource[] {file});
return file;
}
/**
* Create a remote file handle for the given file path that is relative to the
* given location.
*/
public static RemoteFile create(String filePath, ICVSRepositoryLocation location) {
return create(filePath, location, null, null);
}
/**
* Create a remote file handle for the given file path that is relative to the
* given location.
*/
public static RemoteFile create(String filePath, ICVSRepositoryLocation location, CVSTag tag, String revision) {
Assert.isNotNull(filePath);
Assert.isNotNull(location);
IPath path = new Path(null, filePath);
if (tag != null && revision != null && tag.getName().equals(revision))
tag = null;
RemoteFolder parent = new RemoteFolder(null /* parent */, location, path.removeLastSegments(1).toString(), tag /* tag */);
RemoteFile file = new RemoteFile(parent, Update.STATE_NONE, path.lastSegment(), revision /* revision */, null /* keyword mode */, tag /* tag */);
parent.setChildren(new ICVSRemoteResource[] {file});
return file;
}
/**
* Constructor for RemoteFile that should be used when nothing is know about the
* file ahead of time.
* @param parent the folder that is the parent of the file
* @param workspaceSyncState the workspace state (use Update.STATE_NONE if unknown)
* @param name the name of the file
* @param revision revision of the file or <code>null</code> if the revision is not known
* @param keywordMode keyword mode of the file or <code>null</code> if the mode is not known
* @param tag tag for the file
*/
public RemoteFile(RemoteFolder parent, int workspaceSyncState, String name, String revision, KSubstOption keywordMode, CVSTag tag) {
this(parent, name, workspaceSyncState, getSyncBytes(name, revision, keywordMode, tag));
}
private static byte[] getSyncBytes(String name, String revision, KSubstOption keywordMode, CVSTag tag) {
if (revision == null) {
revision = ResourceSyncInfo.ADDED_REVISION;
}
if (keywordMode == null) {
// Always use a blank mode for remote files so that
// the proper mode will be obtained when the contents
// are fetched
keywordMode = KSubstOption.fromMode(""); //$NON-NLS-1$
}
MutableResourceSyncInfo newInfo = new MutableResourceSyncInfo(name, revision);
newInfo.setKeywordMode(keywordMode);
newInfo.setTag(tag);
return newInfo.getBytes();
}
public RemoteFile(RemoteFolder parent, byte[] syncBytes) throws CVSException {
this(parent, Update.STATE_NONE, syncBytes);
}
/* package */ RemoteFile(RemoteFolder parent, int workspaceSyncState, byte[] syncBytes) throws CVSException {
this(parent, ResourceSyncInfo.getName(syncBytes), workspaceSyncState, syncBytes);
}
private RemoteFile(RemoteFolder parent, String name, int workspaceSyncState, byte[] syncBytes) {
super(parent, name);
this.syncBytes = syncBytes;
setWorkspaceSyncState(workspaceSyncState);
}
/**
* @see ICVSResource#accept(ICVSResourceVisitor)
*/
@Override
public void accept(ICVSResourceVisitor visitor) throws CVSException {
visitor.visitFile(this);
}
/**
* @see ICVSResource#accept(ICVSResourceVisitor, boolean)
*/
@Override
public void accept(ICVSResourceVisitor visitor, boolean recurse) throws CVSException {
visitor.visitFile(this);
}
/**
* @see ICVSRemoteFile#getContents()
*/
@Override
public InputStream getContents(IProgressMonitor monitor) throws CVSException {
try {
return getStorage(monitor).getContents();
} catch (CoreException e) {
throw CVSException.wrapException(e);
}
}
@Override
protected void fetchContents(IProgressMonitor monitor) throws TeamException {
try {
aboutToReceiveContents(getSyncBytes());
internalFetchContents(monitor);
// If the fetch succeeded but no contents were cached from the server
// than we can assume that the remote file has no contents.
if (!isContentsCached()) {
setContents(new ByteArrayInputStream(new byte[0]), monitor);
}
} finally {
doneReceivingContents();
}
}
private void internalFetchContents(IProgressMonitor monitor) throws CVSException {
monitor.beginTask(CVSMessages.RemoteFile_getContents, 100);
monitor.subTask(CVSMessages.RemoteFile_getContents);
if (getRevision().equals(ResourceSyncInfo.ADDED_REVISION)) {
// The revision of the remote file is not known so we need to use the tag to get the status of the file
CVSTag tag = getSyncInfo().getTag();
if (tag == null) tag = CVSTag.DEFAULT;
RemoteFolderMemberFetcher fetcher = new RemoteFolderMemberFetcher((RemoteFolder)getParent(), tag);
fetcher.updateFileRevisions(new ICVSFile[] { this }, Policy.subMonitorFor(monitor, 10));
}
Session session = new Session(getRepository(), parent, false /* create backups */);
session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
try {
IStatus status = Command.UPDATE.execute(
session,
Command.NO_GLOBAL_OPTIONS,
new LocalOption[] {
Update.makeTagOption(new CVSTag(getRevision(), CVSTag.VERSION)),
Update.IGNORE_LOCAL_CHANGES },
new ICVSResource[] { this },
null,
Policy.subMonitorFor(monitor, 80));
if (status.getCode() == CVSStatus.SERVER_ERROR) {
throw new CVSServerException(status);
}
} finally {
session.close();
monitor.done();
}
}
@Override
public ILogEntry getLogEntry(IProgressMonitor monitor) throws CVSException {
if (entry == null) {
monitor = Policy.monitorFor(monitor);
monitor.beginTask(CVSMessages.RemoteFile_getLogEntries, 100);
Session session = new Session(getRepository(), parent, false /* output to console */);
session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
try {
try {
LogEntryListener listener = new LogEntryListener();
IStatus status = Command.LOG.execute(
session,
Command.NO_GLOBAL_OPTIONS,
new LocalOption[] {
Log.makeRevisionOption(getRevision())},
new ICVSResource[] { RemoteFile.this },
new LogListener(RemoteFile.this, listener),
Policy.subMonitorFor(monitor, 90));
ILogEntry[] entries = listener.getEntries();
if (entries.length == 1) {
entry = entries[0];
}
if (status.getCode() == CVSStatus.SERVER_ERROR) {
throw new CVSServerException(status);
}
} finally {
monitor.done();
}
} finally {
session.close();
}
}
return entry;
}
/**
* @see ICVSRemoteFile#getLogEntries()
*/
@Override
public ILogEntry[] getLogEntries(IProgressMonitor monitor) throws CVSException {
monitor = Policy.monitorFor(monitor);
monitor.beginTask(CVSMessages.RemoteFile_getLogEntries, 100);
Session session = new Session(getRepository(), parent, false /* output to console */);
session.open(Policy.subMonitorFor(monitor, 10), false /* read-only */);
try {
QuietOption quietness = CVSProviderPlugin.getPlugin().getQuietness();
try {
CVSProviderPlugin.getPlugin().setQuietness(Command.VERBOSE);
LogEntryListener listener = new LogEntryListener();
IStatus status = Command.LOG.execute(
session,
Command.NO_GLOBAL_OPTIONS, Command.NO_LOCAL_OPTIONS,
new ICVSResource[] { RemoteFile.this }, new LogListener(RemoteFile.this, listener),
Policy.subMonitorFor(monitor, 90));
if (status.getCode() == CVSStatus.SERVER_ERROR) {
throw new CVSServerException(status);
}
return listener.getEntries();
} finally {
CVSProviderPlugin.getPlugin().setQuietness(quietness);
monitor.done();
}
} finally {
session.close();
}
}
/**
* @see ICVSRemoteFile#getRevision()
*/
@Override
public String getRevision() {
try {
return ResourceSyncInfo.getRevision(syncBytes);
} catch (CVSException e) {
CVSProviderPlugin.log(e);
return ResourceSyncInfo.ADDED_REVISION;
}
}
private KSubstOption getKeywordMode() {
try {
return ResourceSyncInfo.getKeywordMode(syncBytes);
} catch (CVSException e) {
CVSProviderPlugin.log(e);
return KSubstOption.getDefaultTextMode();
}
}
/*
* Get a different revision of the remote file.
*
* We must also create a new parent since the child is accessed through the parent from within CVS commands.
* Therefore, we need a new parent so that we can fetch the contents of the remote file revision
*/
public RemoteFile toRevision(String revision) {
RemoteFolder newParent = new RemoteFolder(null, parent.getRepository(), parent.getRepositoryRelativePath(), parent.getTag());
RemoteFile file = new RemoteFile(newParent, getWorkspaceSyncState(), getName(), revision, getKeywordMode(), CVSTag.DEFAULT);
newParent.setChildren(new ICVSRemoteResource[] {file});
return file;
}
/**
* @see ICVSFile#getSyncInfo()
*/
@Override
public ResourceSyncInfo getSyncInfo() {
try {
return new ResourceSyncInfo(syncBytes);
} catch (CVSException e) {
CVSProviderPlugin.log(e);
return null;
}
}
/**
* @see ICVSResource#getRemoteLocation(ICVSFolder)
*/
@Override
public String getRemoteLocation(ICVSFolder stopSearching) throws CVSException {
return parent.getRemoteLocation(stopSearching) + Session.SERVER_SEPARATOR + getName();
}
/**
* Get the remote path for the receiver relative to the repository location path
*/
@Override
public String getRepositoryRelativePath() {
String parentPath = parent.getRepositoryRelativePath();
return parentPath + Session.SERVER_SEPARATOR + getName();
}
/**
* Return the server root directory for the repository
*/
@Override
public ICVSRepositoryLocation getRepository() {
return parent.getRepository();
}
/**
* @see IManagedFile#setFileInfo(FileProperties)
*/
@Override
public void setSyncInfo(ResourceSyncInfo fileInfo, int modificationState) {
setSyncBytes(fileInfo.getBytes(),modificationState);
}
/**
* Set the revision for this remote file.
*
* @param revision to associated with this remote file
*/
public void setRevision(String revision) throws CVSException {
syncBytes = ResourceSyncInfo.setRevision(syncBytes, revision);
}
@Override
public InputStream getContents() throws CVSException {
if (!fetching) {
// Return the cached contents
if (isContentsCached()) {
try {
InputStream cached = getCachedContents();
if (cached != null) {
return cached;
}
} catch (TeamException e) {
throw CVSException.wrapException(e);
}
}
}
// There was nothing cached so return an empty stream.
// This is done to allow the contents to be fetched
// (i.e. update sends empty contents and real contents are sent back)
return new ByteArrayInputStream(new byte[0]);
}
@Override
protected InputStream getCachedContents() throws TeamException {
if (isHandleCached()) {
RemoteFile file = (RemoteFile)getCachedHandle();
if (file != null) {
byte[] newSyncBytes = file.getSyncBytes();
if (newSyncBytes != null) {
// Make sure the sync bytes match the content that is being accessed
syncBytes = newSyncBytes;
}
}
}
return super.getCachedContents();
}
@Override
public void setContents(InputStream stream, int responseType, boolean keepLocalHistory, IProgressMonitor monitor) throws CVSException {
try {
setContents(stream, monitor);
} catch (TeamException e) {
throw CVSException.wrapException(e);
}
}
@Override
public void setReadOnly(boolean readOnly) {
// RemoteFiles are always read only
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public Date getTimeStamp() {
return getSyncInfo().getTimeStamp();
}
@Override
public void setTimeStamp(Date date) {
// RemoteFiles are not mutable so do not support timestamp changes
}
/**
* @see ICVSFile#moveTo(String)
*/
@Override
public void copyTo(String mFile) {
// Do nothing
}
@Override
public ICVSRemoteResource[] members(IProgressMonitor progress) {
return new ICVSRemoteResource[0];
}
@Override
public boolean isContainer() {
return false;
}
@Override
public boolean isFolder() {
return false;
}
/*
* @see ICVSResource#tag(CVSTag, LocalOption[], IProgressMonitor)
*
* The revision of the remote file is used as the base for the tagging operation
*/
@Override
public IStatus tag(final CVSTag tag, final LocalOption[] localOptions, IProgressMonitor monitor) throws CVSException {
monitor = Policy.monitorFor(monitor);
monitor.beginTask(null, 100);
Session session = new Session(getRepository(), getParent(), true /* output to console */);
session.open(Policy.subMonitorFor(monitor, 10), true /* open for modification */);
try {
return Command.RTAG.execute(
session,
Command.NO_GLOBAL_OPTIONS,
localOptions,
new CVSTag(getRevision(), CVSTag.VERSION),
tag,
new ICVSRemoteResource[] { RemoteFile.this },
Policy.subMonitorFor(monitor, 90));
} finally {
session.close();
}
}
@Override
public boolean equals(Object target) {
if (this == target)
return true;
if (!(target instanceof RemoteFile))
return false;
RemoteFile remote = (RemoteFile) target;
return super.equals(target) && remote.getRevision().equals(getRevision());
}
/**
* @see org.eclipse.team.internal.ccvs.core.ICVSFile#checkout(int)
*/
@Override
public void edit(int notifications, boolean notifyForWritable, IProgressMonitor monitor) {
// do nothing
}
/**
* @see org.eclipse.team.internal.ccvs.core.ICVSFile#uncheckout()
*/
@Override
public void unedit(IProgressMonitor monitor) {
// do nothing
}
/**
* @see org.eclipse.team.internal.ccvs.core.ICVSFile#notificationCompleted()
*/
@Override
public void notificationCompleted() {
// do nothing
}
/**
* @see org.eclipse.team.internal.ccvs.core.ICVSFile#getPendingNotification()
*/
@Override
public NotifyInfo getPendingNotification() {
return null;
}
/**
* @see RemoteResource#forTag(ICVSRemoteFolder, CVSTag)
*/
@Override
public ICVSRemoteResource forTag(ICVSRemoteFolder parent, CVSTag tag) {
return new RemoteFile((RemoteFolder)parent, getWorkspaceSyncState(), getName(), getRevision(), getKeywordMode(), tag);
}
/**
* @see org.eclipse.team.internal.ccvs.core.ICVSRemoteResource#forTag(org.eclipse.team.internal.ccvs.core.CVSTag)
*/
@Override
public ICVSRemoteResource forTag(CVSTag tag) {
RemoteFolderTree remoteFolder = new RemoteFolderTree(null, getRepository(),
((ICVSRemoteFolder)getParent()).getRepositoryRelativePath(),
tag);
RemoteFile remoteFile = (RemoteFile)forTag(remoteFolder, tag);
remoteFolder.setChildren(new ICVSRemoteResource[] { remoteFile });
return remoteFile;
}
/**
* @see org.eclipse.team.internal.ccvs.core.ICVSFile#committed(org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo)
*/
@Override
public void checkedIn(String info, boolean commit) {
// do nothing
}
/**
* @see org.eclipse.team.internal.ccvs.core.ICVSFile#isEdited()
*/
@Override
public boolean isEdited() {
return false;
}
/**
* @see org.eclipse.team.internal.ccvs.core.ICVSFile#getSyncBytes()
*/
@Override
public byte[] getSyncBytes() {
return syncBytes;
}
/**
* @see org.eclipse.team.internal.ccvs.core.ICVSFile#setSyncBytes(byte[])
*/
@Override
public void setSyncBytes(byte[] syncBytes, int modificationState) {
if (fetching) {
RemoteFile file = (RemoteFile)getCachedHandle();
if (file == null) {
cacheHandle();
} else if (file != this) {
file.setSyncBytes(syncBytes, modificationState);
}
}
this.syncBytes = syncBytes;
}
@Override
public String toString() {
return super.toString() + " " + getRevision(); //$NON-NLS-1$
}
@Override
public String getContentIdentifier() {
return getRevision();
}
/**
* Callback which indicates that the remote file is about to receive contents that should be cached
* @param entryLine
*/
public void aboutToReceiveContents(byte[] entryLine) {
try {
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=189025
entryLine = ResourceSyncInfo.setSlot(syncBytes, 3, new byte[0]);
} catch (CVSException e) {
// log it and proceed
CVSProviderPlugin.log(e);
}
setSyncBytes(entryLine, ICVSFile.CLEAN);
fetching = true;
}
/**
* The contents for the file have already been provided.
*/
public void doneReceivingContents() {
fetching = false;
}
@Override
public boolean isContentsCached() {
// Made public for use by FileContentCachingService
return super.isContentsCached();
}
/**
* Cache the contents of the given IFile as the contents for this remote file handle.
* The caller must ensure that the local file is mapped to the same revision and is
* not modified since it was loaded from CVS.
* @param file
* @throws CoreException
* @throws TeamException
*/
public void setContents(IFile file, IProgressMonitor monitor) throws TeamException, CoreException {
setContents(file.getContents(), monitor);
}
@Override
public void setExecutable(boolean executable) throws CVSException {
// store executable bit;
this.executable = executable;
if (!isHandleCached()) {
cacheHandle();
}
RemoteFile file = (RemoteFile)getCachedHandle();
if (file != this) {
file.setExecutable(executable);
}
}
@Override
public boolean isExecutable() throws CVSException {
// return executable bit
return executable;
}
@Override
public CachedResourceVariant getCachedHandle() {
return super.getCachedHandle();
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter(Class<T> adapter) {
if (adapter == IFileRevision.class)
return (T) new CVSResourceVariantFileRevision(this);
return super.getAdapter(adapter);
}
public CVSURI toCVSURI() {
ResourceSyncInfo info = getSyncInfo();
return new CVSURI(getRepository(), new Path(getRepositoryRelativePath()), info.getTag(), info.getRevision());
}
}