| /******************************************************************************* |
| * Copyright (c) 2000, 2011 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.File; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.List; |
| |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.team.core.RepositoryProvider; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.internal.ccvs.core.*; |
| import org.eclipse.team.internal.ccvs.core.client.Session; |
| import org.eclipse.team.internal.ccvs.core.syncinfo.*; |
| |
| /** |
| * Represents handles to CVS resource on the local file system. Synchronization |
| * information is taken from the CVS subdirectories. |
| */ |
| public class EclipseFile extends EclipseResource implements ICVSFile { |
| |
| private static final String TEMP_FILE_EXTENSION = ".tmp";//$NON-NLS-1$ |
| private static final IPath PROJECT_META_DATA_PATH = new Path(".project");//$NON-NLS-1$ |
| |
| /** |
| * Create a handle based on the given local resource. |
| */ |
| protected EclipseFile(IFile file) { |
| super(file); |
| } |
| |
| /* |
| * @see ICVSResource#delete() |
| */ |
| public void delete() throws CVSException { |
| try { |
| ((IFile)resource).delete(false /*force*/, true /*keepHistory*/, null); |
| } catch(CoreException e) { |
| throw CVSException.wrapException(resource, NLS.bind(CVSMessages.EclipseFile_Problem_deleting_resource, new String[] { resource.getFullPath().toString(), e.getStatus().getMessage() }), e); // |
| } |
| } |
| |
| public long getSize() { |
| return getIOFile().length(); |
| } |
| |
| public InputStream getContents() throws CVSException { |
| try { |
| return getIFile().getContents(); |
| } catch (CoreException e) { |
| throw CVSException.wrapException(resource, NLS.bind(CVSMessages.EclipseFile_Problem_accessing_resource, new String[] { resource.getFullPath().toString(), e.getStatus().getMessage() }), e); // |
| } |
| } |
| |
| /* |
| * @see ICVSFile#getTimeStamp() |
| */ |
| public Date getTimeStamp() { |
| long timestamp = getIFile().getLocalTimeStamp(); |
| if( timestamp == IResource.NULL_STAMP) { |
| // If there is no file, return the same timestamp as ioFile.lastModified() would |
| return new Date(0L); |
| } |
| return new Date((timestamp/1000)*1000); |
| } |
| |
| /* |
| * @see ICVSFile#setTimeStamp(Date) |
| */ |
| public void setTimeStamp(Date date) throws CVSException { |
| long time; |
| if (date == null) { |
| time = System.currentTimeMillis(); |
| } else { |
| time = date.getTime(); |
| } |
| EclipseSynchronizer.getInstance().setTimeStamp(this, time); |
| } |
| |
| /* |
| * @see ICVSResource#isFolder() |
| */ |
| public boolean isFolder() { |
| return false; |
| } |
| |
| /* |
| * @see ICVSFile#isModified() |
| */ |
| public boolean isModified(IProgressMonitor monitor) throws CVSException { |
| |
| // ignore the monitor, there is no valuable progress to be shown when |
| // calculating the dirty state for files. It is relatively fast. |
| |
| if (!exists()) { |
| return getSyncBytes() != null; |
| } |
| int state = EclipseSynchronizer.getInstance().getModificationState(getIFile()); |
| |
| if (state != UNKNOWN) { |
| boolean dirty = state != CLEAN; |
| // Check to make sure that cached state is the real state. |
| // They can be different if deltas happen in the wrong order. |
| if (dirty == isDirty()) { |
| return dirty; |
| } |
| } |
| |
| // nothing cached, need to manually check (and record) |
| byte[] syncBytes = getSyncBytes(); |
| if (syncBytes == null && isIgnored()) return false; |
| // unmanaged files are reported as modified |
| return EclipseSynchronizer.getInstance().setModified(this, UNKNOWN); |
| } |
| |
| /* |
| * @see ICVSResource#accept(ICVSResourceVisitor) |
| */ |
| public void accept(ICVSResourceVisitor visitor) throws CVSException { |
| visitor.visitFile(this); |
| } |
| |
| /* |
| * @see ICVSResource#accept(ICVSResourceVisitor, boolean) |
| */ |
| public void accept(ICVSResourceVisitor visitor, boolean recurse) throws CVSException { |
| visitor.visitFile(this); |
| } |
| |
| /* |
| * This is to be used by the Copy handler. The filename of the form .#filename |
| */ |
| public void copyTo(String filename) throws CVSException { |
| try { |
| IPath targetPath = new Path(null, filename); |
| IFile targetFile = getIFile().getParent().getFile(targetPath); |
| if (targetFile.exists()) { |
| // There is a file in the target location. |
| // Delete it and keep the history just in case |
| targetFile.delete(false /* force */, true /* keep history */, null); |
| } |
| getIFile().copy(targetPath, true /*force*/, null); |
| } catch(CoreException e) { |
| throw new CVSException(e.getStatus()); |
| } |
| } |
| |
| /* |
| * @see ICVSResource#getRemoteLocation() |
| */ |
| public String getRemoteLocation(ICVSFolder stopSearching) throws CVSException { |
| return getParent().getRemoteLocation(stopSearching) + SEPARATOR + getName(); |
| } |
| |
| /* |
| * @see ICVSFile#setReadOnly() |
| */ |
| public void setContents(InputStream stream, int responseType, boolean keepLocalHistory, IProgressMonitor monitor) throws CVSException { |
| try { |
| IFile file = getIFile(); |
| if (PROJECT_META_DATA_PATH.equals(file.getFullPath().removeFirstSegments(1))) { |
| responseType = UPDATED; |
| } |
| switch (responseType) { |
| case UPDATED: |
| if (resource.exists()) { |
| file.setContents(stream, false /*force*/, true /*keep history*/, monitor); |
| break; |
| } |
| case CREATED: // creating a new file so it should not exist locally |
| file.create(stream, false /*force*/, monitor); |
| break; |
| case MERGED: // merging contents into a file that exists locally |
| // Ensure we don't leave the file in a partially written state |
| IFile tempFile = file.getParent().getFile(new Path(null, file.getName() + TEMP_FILE_EXTENSION)); |
| monitor.beginTask(null, 100); |
| if (tempFile.exists()) |
| tempFile.delete(true /* force */, Policy.subMonitorFor(monitor, 25)); |
| tempFile.create(stream, true /*force*/, Policy.subMonitorFor(monitor, 25)); |
| file.delete(false /* force */, true /* keep history */, Policy.subMonitorFor(monitor, 25)); |
| tempFile.move(new Path(null, file.getName()), false /*force*/, true /*history*/, Policy.subMonitorFor(monitor, 25)); |
| monitor.done(); |
| break; |
| case UPDATE_EXISTING: // creating a new file so it should exist locally |
| file.setContents(stream, false /*force*/, true /*keep history*/, monitor); |
| break; |
| } |
| } catch(CoreException e) { |
| String message = null; |
| if (e.getStatus().getCode() == IResourceStatus.FAILED_READ_LOCAL) { |
| // This error indicates that Core couldn't read from the server stream |
| // The real reason will be in the message of the wrapped exception |
| Throwable t = e.getStatus().getException(); |
| if (t != null) message = t.getMessage(); |
| } |
| if (message == null) message = e.getMessage(); |
| throw CVSException.wrapException(resource, NLS.bind(CVSMessages.EclipseFile_Problem_writing_resource, new String[] { resource.getFullPath().toString(), message }), e); |
| } |
| } |
| |
| /* |
| * @see ICVSFile#setReadOnly() |
| */ |
| public void setReadOnly(boolean readOnly) throws CVSException { |
| ResourceAttributes attributes = resource.getResourceAttributes(); |
| if (attributes != null) { |
| attributes.setReadOnly(readOnly); |
| try { |
| resource.setResourceAttributes(attributes); |
| } catch (CoreException e) { |
| throw CVSException.wrapException(e); |
| } |
| } |
| } |
| |
| /* |
| * @see ICVSFile#isReadOnly() |
| */ |
| public boolean isReadOnly() throws CVSException { |
| return getIFile().isReadOnly(); |
| } |
| |
| /* |
| * @see ICVSFile#setExecutable() |
| */ |
| public void setExecutable(boolean executable) throws CVSException { |
| ResourceAttributes attributes = resource.getResourceAttributes(); |
| if (attributes != null) { |
| attributes.setExecutable(executable); |
| try { |
| resource.setResourceAttributes(attributes); |
| } catch (CoreException e) { |
| throw CVSException.wrapException(e); |
| } |
| } |
| } |
| |
| /* |
| * @see ICVSFile#isExectuable() |
| */ |
| public boolean isExecutable() throws CVSException { |
| ResourceAttributes attributes = resource.getResourceAttributes(); |
| if (attributes != null) { |
| return attributes.isExecutable(); |
| } else { |
| return false; |
| } |
| } |
| |
| /* |
| * Typecasting helper |
| */ |
| public IFile getIFile() { |
| return (IFile)resource; |
| } |
| |
| /* |
| * To allow accessing size and timestamp for the underlying java.io.File |
| */ |
| private File getIOFile() { |
| IPath location = resource.getLocation(); |
| if(location!=null) { |
| return location.toFile(); |
| } |
| return null; |
| } |
| /** |
| * @see ICVSFile#getLogEntries(IProgressMonitor) |
| */ |
| public ILogEntry[] getLogEntries(IProgressMonitor monitor) throws TeamException { |
| |
| // try fetching log entries only when the file's project is accessible |
| // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=190434 |
| if (getIResource() == null |
| || !getIResource().getProject().isAccessible()) |
| return new ILogEntry[0]; |
| |
| byte[] syncBytes = getSyncBytes(); |
| if(syncBytes != null && !ResourceSyncInfo.isAddition(syncBytes)) { |
| ICVSRemoteResource remoteFile = CVSWorkspaceRoot.getRemoteResourceFor(resource); |
| if (remoteFile != null) |
| return ((ICVSRemoteFile)remoteFile).getLogEntries(monitor); |
| } |
| return new ILogEntry[0]; |
| } |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSFile#setNotifyInfo(NotifyInfo) |
| */ |
| public void setNotifyInfo(NotifyInfo info) throws CVSException { |
| if (isManaged()) { |
| EclipseSynchronizer.getInstance().setNotifyInfo(resource, info); |
| // On an edit, the base should be cached |
| // On an unedit, the base should be restored (and cleared?) |
| // On a commit, the base should be cleared |
| } |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSFile#getNotifyInfo() |
| */ |
| public NotifyInfo getNotifyInfo() throws CVSException { |
| if (isManaged()) { |
| return EclipseSynchronizer.getInstance().getNotifyInfo(resource); |
| } |
| return null; |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSFile#setNotifyInfo(NotifyInfo) |
| */ |
| public void setBaserevInfo(BaserevInfo info) throws CVSException { |
| if (isManaged()) { |
| if (info == null) { |
| EclipseSynchronizer.getInstance().deleteBaserevInfo(resource); |
| EclipseSynchronizer.getInstance().deleteFileFromBaseDirectory(getIFile(), null); |
| } else |
| EclipseSynchronizer.getInstance().setBaserevInfo(resource, info); |
| } |
| } |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSFile#getNotifyInfo() |
| */ |
| public BaserevInfo getBaserevInfo() throws CVSException { |
| if (isManaged()) { |
| return EclipseSynchronizer.getInstance().getBaserevInfo(resource); |
| } |
| return null; |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSFile#checkout(int) |
| */ |
| public void edit(final int notifications, boolean notifyForWritable, IProgressMonitor monitor) throws CVSException { |
| if (!notifyForWritable && !isReadOnly()) return; |
| run(new ICVSRunnable() { |
| public void run(IProgressMonitor monitor) throws CVSException { |
| byte[] syncBytes = getSyncBytes(); |
| if (syncBytes == null || ResourceSyncInfo.isAddition(syncBytes)) return; |
| |
| // convert the notifications to internal form |
| char[] internalFormat; |
| if (notifications == NO_NOTIFICATION) { |
| internalFormat = null; |
| } else if (notifications == NOTIFY_ON_ALL) { |
| internalFormat = NotifyInfo.ALL; |
| } else { |
| List notificationCharacters = new ArrayList(); |
| if ((notifications & NOTIFY_ON_EDIT) >0) |
| notificationCharacters.add(Character.valueOf(NotifyInfo.EDIT)); |
| if ((notifications & NOTIFY_ON_UNEDIT) >0) |
| notificationCharacters.add(Character.valueOf(NotifyInfo.UNEDIT)); |
| if ((notifications & NOTIFY_ON_COMMIT) >0) |
| notificationCharacters.add(Character.valueOf(NotifyInfo.COMMIT)); |
| internalFormat = new char[notificationCharacters.size()]; |
| for (int i = 0; i < internalFormat.length; i++) { |
| internalFormat[i] = ((Character)notificationCharacters.get(i)).charValue(); |
| } |
| } |
| |
| // record the notification |
| NotifyInfo notifyInfo = new NotifyInfo(getName(), NotifyInfo.EDIT, new Date(), internalFormat); |
| setNotifyInfo(notifyInfo); |
| |
| // Only record the base if the file is not modified |
| if (!isModified(null)) { |
| EclipseSynchronizer.getInstance().copyFileToBaseDirectory(getIFile(), monitor); |
| setBaserevInfo(new BaserevInfo(getName(), ResourceSyncInfo.getRevision(syncBytes))); |
| } |
| |
| try { |
| // allow editing |
| setReadOnly(false); |
| } catch (CVSException e) { |
| // Just log and keep going |
| CVSProviderPlugin.log(e); |
| } |
| } |
| }, monitor); |
| |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSFile#uncheckout() |
| */ |
| public void unedit(IProgressMonitor monitor) throws CVSException { |
| if (isReadOnly()) return; |
| run(new ICVSRunnable() { |
| public void run(IProgressMonitor monitor) throws CVSException { |
| // record the notification |
| NotifyInfo info = getNotifyInfo(); |
| if (info != null && info.getNotificationType() == NotifyInfo.EDIT) { |
| info = null; |
| } else { |
| info = new NotifyInfo(getName(), NotifyInfo.UNEDIT, new Date(), null); |
| } |
| setNotifyInfo(info); |
| |
| if (isModified(null)) { |
| ResourceSyncInfo syncInfo = getSyncInfo(); |
| BaserevInfo baserevInfo = getBaserevInfo(); |
| EclipseSynchronizer.getInstance().restoreFileFromBaseDirectory(getIFile(), monitor); |
| // reset any changes that may have been merged from the server |
| if (!syncInfo.getRevision().equals(baserevInfo.getRevision())) { |
| MutableResourceSyncInfo newInfo = syncInfo.cloneMutable(); |
| newInfo.setRevision(baserevInfo.getRevision()); |
| newInfo.setTimeStamp(getTimeStamp()); |
| newInfo.setDeleted(false); |
| setSyncInfo(newInfo, ICVSFile.CLEAN); |
| } else { |
| // an unedited file is no longer modified |
| EclipseSynchronizer.getInstance().setModified(EclipseFile.this, CLEAN); |
| } |
| } else { |
| // We still need to report a state change |
| setSyncBytes(getSyncBytes(), ICVSFile.CLEAN); |
| } |
| setBaserevInfo(null); |
| |
| try { |
| // prevent editing |
| setReadOnly(true); |
| } catch (CVSException e) { |
| // Just log and keep going |
| CVSProviderPlugin.log(e); |
| } |
| } |
| }, monitor); |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSFile#notificationCompleted() |
| */ |
| public void notificationCompleted() throws CVSException { |
| EclipseSynchronizer.getInstance().deleteNotifyInfo(resource); |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSFile#getPendingNotification() |
| */ |
| public NotifyInfo getPendingNotification() throws CVSException { |
| return getNotifyInfo(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.internal.ccvs.core.ICVSFile#checkedIn(java.lang.String) |
| */ |
| public void checkedIn(String entryLine, boolean commit) throws CVSException { |
| ResourceSyncInfo oldInfo = getSyncInfo(); |
| ResourceSyncInfo newInfo = null; |
| int modificationState = ICVSFile.CLEAN; |
| if (entryLine == null) { |
| // cvs commit: the file contents matched the server contents so no entry line was sent |
| if (oldInfo == null) return; |
| // We should never make the timestamp go backwards so we'll set |
| // the entry line timestamp to match that of the file |
| if(! oldInfo.isAdded()) { |
| MutableResourceSyncInfo mutable = oldInfo.cloneMutable(); |
| mutable.setTimeStamp(getTimeStamp(), true /* clear merged */); |
| newInfo = mutable; |
| } |
| // (modified = false) the file will be no longer modified |
| } else if (oldInfo == null) { |
| // cvs add: addition of a file |
| newInfo = new ResourceSyncInfo(entryLine, null); |
| // an added file should show up as modified |
| modificationState = ICVSFile.DIRTY; |
| } else { |
| // cvs commit: commit of a changed file |
| // cvs update: update of a file whose contents match the server contents |
| Date timeStamp; |
| if (commit) { |
| // This is a commit. Put the file timestamp in the entry |
| timeStamp = getTimeStamp(); |
| } else { |
| // This is an update. We need to change the tiemstamp in the |
| // entry file to match the file timestamp returned by Java |
| timeStamp = oldInfo.getTimeStamp(); |
| if (timeStamp == null) { |
| timeStamp = getTimeStamp(); |
| } else { |
| // First, set the timestamp of the file to the timestamp from the entry |
| // There is a chance this will do nothing as the call to Java on some |
| // file systems munges the timestamps |
| setTimeStamp(timeStamp); |
| // To compensate for the above, reset the timestamp in the entry |
| // to match the timestamp in the file |
| timeStamp = getTimeStamp(); |
| } |
| } |
| newInfo = new ResourceSyncInfo(entryLine, timeStamp); |
| |
| } |
| //see bug 106876 |
| if (newInfo != null){ |
| CVSTag tag = newInfo.getTag(); |
| if(tag != null && CVSEntryLineTag.BASE.getName().equals(tag.getName())){ |
| newInfo = newInfo.cloneMutable(); |
| ((MutableResourceSyncInfo)newInfo).setTag(oldInfo.getTag()); |
| } |
| setSyncInfo(newInfo, modificationState); |
| } |
| clearCachedBase(); |
| } |
| |
| private void clearCachedBase() throws CVSException { |
| BaserevInfo base = getBaserevInfo(); |
| if (base != null) { |
| setBaserevInfo(null); |
| try { |
| setReadOnly(true); |
| } catch (CVSException e) { |
| // Just log and keep going |
| CVSProviderPlugin.log(e); |
| } |
| } else { |
| // Check to see if watch-edit is enabled for the project |
| CVSTeamProvider provider = (CVSTeamProvider)RepositoryProvider.getProvider(resource.getProject(), CVSProviderPlugin.getTypeId()); |
| if (provider != null && provider.isWatchEditEnabled()) { |
| try { |
| setReadOnly(true); |
| } catch (CVSException e) { |
| // Just log and keep going |
| CVSProviderPlugin.log(e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSResource#unmanage(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| public void unmanage(IProgressMonitor monitor) throws CVSException { |
| run(new ICVSRunnable() { |
| public void run(IProgressMonitor monitor) throws CVSException { |
| EclipseFile.super.unmanage(monitor); |
| clearCachedBase(); |
| } |
| }, monitor); |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSFile#isEdited() |
| */ |
| public boolean isEdited() throws CVSException { |
| return EclipseSynchronizer.getInstance().isEdited(getIFile()); |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.ICVSResource#setSyncInfo(org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo) |
| */ |
| public void setSyncInfo(ResourceSyncInfo info, int modificationState) throws CVSException { |
| setSyncBytes(info.getBytes(), info, modificationState); |
| } |
| |
| /** |
| * @see org.eclipse.team.internal.ccvs.core.resources.EclipseResource#setSyncBytes(byte[], int) |
| */ |
| public void setSyncBytes(byte[] syncBytes, int modificationState) throws CVSException { |
| setSyncBytes(syncBytes, null, modificationState); |
| } |
| |
| /* |
| * @see org.eclipse.team.internal.ccvs.core.resources.EclipseResource#setSyncBytes(byte[], int) |
| */ |
| private void setSyncBytes(byte[] syncBytes, ResourceSyncInfo info, int modificationState) throws CVSException { |
| Assert.isNotNull(syncBytes); |
| setSyncBytes(syncBytes); |
| EclipseSynchronizer.getInstance().setModified(this, modificationState); |
| } |
| |
| public void handleModification(boolean forAddition) throws CVSException { |
| if (isIgnored()) { |
| // Special case handling for when a resource passes from the un-managed state |
| // to the ignored state (e.g. ignoring the ignore file). Parent dirty state must be |
| // recalculated but since the resource's end state is ignored there is a lot of code |
| // in the plugin that simply disregards the change to the resource. |
| // There may be a better was of handling resources that transition from un-managed to |
| // ignored but for now this seems like the safest change. |
| if(! resource.isDerived()) { |
| EclipseSynchronizer.getInstance().setModified(this, CLEAN); |
| } |
| return; |
| } |
| // set the modification state to what it really is and return true if the modification state changed |
| EclipseSynchronizer.getInstance().setModified(this, UNKNOWN); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.internal.ccvs.core.ICVSResource#getRepositoryRelativePath() |
| */ |
| public String getRepositoryRelativePath() throws CVSException { |
| if (!isManaged()) return null; |
| String parentPath = getParent().getRepositoryRelativePath(); |
| if (parentPath == null) return null; |
| return parentPath + Session.SERVER_SEPARATOR + getName(); |
| } |
| |
| protected boolean isDirty() throws CVSException { |
| boolean dirty; |
| byte[] syncBytes = getSyncBytes(); |
| if (syncBytes == null) { |
| dirty = exists(); |
| } else { |
| // isMerged() must be called because when a file is updated and merged by the cvs server the timestamps |
| // are equal. Merged files should however be reported as dirty because the user should take action and commit |
| // or review the merged contents. |
| if (ResourceSyncInfo.isAddition(syncBytes) |
| || ResourceSyncInfo.isMerge(syncBytes) |
| || ResourceSyncInfo.wasDeleted(syncBytes) || !exists()) { |
| dirty = true; |
| } else { |
| // TODO: non-optimal as ResourceSyncInfo is created each time |
| ResourceSyncInfo info = new ResourceSyncInfo(syncBytes); |
| dirty = !getTimeStamp().equals(info.getTimeStamp()); |
| } |
| } |
| return dirty; |
| } |
| |
| } |
| |
| |