| /******************************************************************************* |
| * Copyright (c) 2002, 2005 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 |
| *******************************************************************************/ |
| package com.example.movedeletehook; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IProjectDescription; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceVisitor; |
| import org.eclipse.core.resources.team.IMoveDeleteHook; |
| import org.eclipse.core.resources.team.IResourceTree; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| |
| /** |
| * Example implementation of <code>IMoveDeleteHook</code> illustrating |
| * how this hook should be used. |
| * <p> |
| * A word on terminology. The code in this hook lives in a world where it |
| * must deal with two parallel notions of "file" that must not be confused. |
| * The terminology we use to keep these straight: |
| * <ul> |
| * <li><b>local file system</b> - We use "local file system" (abbreviated "lfs") |
| * when talking about files and folders (directories) on disk; these are |
| * accessed through <code>java.io.File</code> protocol. |
| * </li> |
| * <li><b>workspace resource tree</b> - We use "workspace resource tree" |
| * when talking about in the Eclipse Platform's in-memory representation of |
| * files and folders; these are ordinarily accessed through |
| * <code>org.eclipse.core.resources.IResource</code> protocol. |
| * </li> |
| * </ul> |
| * The general game being played is simple: The hook implementation deals |
| * directly with the files in the local file system using whatever means it |
| * has at its disposal, and then directs the Eclipse platform how to update the |
| * workspace resource tree to match using methods on the |
| * <code>org.eclipse.core.resources.team.IResourceTree</code> |
| * object passed to the hook on each occasion. |
| * </p> |
| * <p> |
| * N.B. It is important for all core methods to update the progress monitor so |
| * that the user knows that long-running operations are making progress |
| * (and can cancel if required). Progress monitoring was omitted here only |
| * because it made the code more difficult to read without shedding much light |
| * on how to use this hook correctly. Please do not follow our bad example. |
| * </p> |
| * |
| * @see org.eclipse.core.resources.IResource |
| * @see org.eclipse.core.resources.team.IMoveDeleteHook |
| * @see org.eclipse.core.resources.team.IResourceTree |
| */ |
| public class MoveDeleteHookExample1 implements IMoveDeleteHook { |
| |
| /** |
| * Creates a new hook instance. |
| */ |
| public MoveDeleteHookExample1() { |
| } |
| |
| /** |
| * This <code>IMoveDeleteHook</code> method implements |
| * <code>IResource.delete(int,IProgressMonitor)</code> where the receiver is |
| * a file. This example implementation illustrates the steps involved |
| * (except for progress monitoring). |
| * |
| * @param tree the workspace resource tree; this object is only valid |
| * for the duration of the invocation of this method, and must not be |
| * used after this method has completed |
| * @param file the handle of the file to delete; the receiver of |
| * <code>IResource.delete(int,IProgressMonitor)</code> |
| * @param updateFlags bit-wise or of update flag constants as per |
| * <code>IResource.delete(int,IProgressMonitor)</code> |
| * @param monitor the progress monitor, or <code>null</code> as per |
| * <code>IResource.delete(int,IProgressMonitor)</code> |
| * @return <code>false</code> if this method declined to assume |
| * responsibility for this operation, and <code>true</code> if this method |
| * attempted to carry out the operation |
| * @see IResource#delete(int,IProgressMonitor) |
| * @see IMoveDeleteHook#deleteFile(IResourceTree,IFile,int,IProgressMonitor) |
| */ |
| public boolean deleteFile( |
| IResourceTree tree, |
| IFile file, |
| int updateFlags, |
| IProgressMonitor monitor) { |
| |
| // do nothing if file does not exist in the workspace resource tree |
| if (!file.exists()) { |
| // return true anyway to say that the operation has been done |
| return true; |
| } |
| |
| // succeed immediately if file does not exist in the local file system |
| java.io.File lfsFile = file.getLocation().toFile(); |
| if (!lfsFile.exists()) { |
| // update the workspace resource tree to match |
| tree.deletedFile(file); |
| // return true to say that the operation has been done |
| return true; |
| } |
| |
| // if FORCE is not specified, fail if the workspace resource tree is |
| // not in sync with file in the local file system |
| boolean force = (updateFlags & IResource.FORCE) != 0; |
| if (!force) { |
| boolean inSync = tree.isSynchronized(file, IResource.DEPTH_ZERO); |
| if (!inSync) { |
| // report failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "File " + file.getFullPath() + " is out of sync with the local file system", |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| // capture the current state of the file in the local history if |
| // KEEP_HISTORY is specified |
| boolean keepHistory = (updateFlags & IResource.KEEP_HISTORY) != 0; |
| if (keepHistory) { |
| tree.addToLocalHistory(file); |
| } |
| |
| // try to delete the file from the local file system |
| boolean success = lfsFile.delete(); |
| if (success) { |
| // update the workspace resource tree to match |
| tree.deletedFile(file); |
| // return true to say that the operation has been done |
| return true; |
| } else { |
| // report an unexpected failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Unable to delete file " + file.getFullPath() + " from the local file system", |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| /** |
| * This <code>IMoveDeleteHook</code> method implements |
| * <code>IResource.delete(int,IProgressMonitor)</code> where the receiver is |
| * a folder. This example implementation illustrates the steps involved |
| * (except for progress monitoring). The general approach illustrated here |
| * first deletes the entire subtree in the local file system and then fixes |
| * up the workspace resource tree to match. (There other approach is to |
| * carry out the deletion in step.) |
| * |
| * @param tree the workspace resource tree; this object is only valid |
| * for the duration of the invocation of this method, and must not be |
| * used after this method has completed |
| * @param folder the handle of the folder to delete; the receiver of |
| * <code>IResource.delete(int,IProgressMonitor)</code> |
| * @param updateFlags bit-wise or of update flag constants as per |
| * <code>IResource.delete(int,IProgressMonitor)</code> |
| * @param monitor the progress monitor, or <code>null</code> as per |
| * <code>IResource.delete(int,IProgressMonitor)</code> |
| * @return <code>false</code> if this method declined to assume |
| * responsibility for this operation, and <code>true</code> if this |
| * method attempted to carry out the operation |
| * @see IResource#delete(int,IProgressMonitor) |
| * @see IMoveDeleteHook#deleteFolder(IResourceTree,IFolder,int,IProgressMonitor) |
| */ |
| public boolean deleteFolder( |
| IResourceTree tree, |
| IFolder folder, |
| int updateFlags, |
| IProgressMonitor monitor) { |
| |
| // do nothing if folder does not exist in the workspace resource tree |
| if (!folder.exists()) { |
| // return true to say that the operation has been done |
| return true; |
| } |
| |
| // succeed immediately if folder does not exist in the local file system |
| java.io.File lfsFolder = folder.getLocation().toFile(); |
| if (!lfsFolder.exists()) { |
| // update the workspace resource tree to match |
| tree.deletedFolder(folder); |
| // return true to say that the operation has been done |
| return true; |
| } |
| |
| // if FORCE is not specified, fail if the workspace resource tree is |
| // not in sync with the directory subtree in the local file |
| // system (depth infinity) |
| boolean force = (updateFlags & IResource.FORCE) != 0; |
| if (!force) { |
| boolean inSync = tree.isSynchronized(folder, IResource.DEPTH_INFINITE); |
| if (!inSync) { |
| // report failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Folder " |
| + folder.getFullPath() |
| + " or one of its descendents is out of sync with the local file system", |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| // capture the current state of all files in the local history if |
| // KEEP_HISTORY is specified |
| boolean keepHistory = (updateFlags & IResource.KEEP_HISTORY) != 0; |
| if (keepHistory) { |
| addAllFilesToHistory(tree, folder); |
| } |
| |
| // try to delete the subtree in the local file system |
| boolean lfsSuccess = deleteLocalSubtree(lfsFolder); |
| if (lfsSuccess) { |
| // update the workspace resource tree to match |
| tree.deletedFolder(folder); |
| // return true to say that the operation has been done |
| return true; |
| } else { |
| // report an unexpected failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Unable to delete folder " |
| + folder.getFullPath() |
| + " from the local file system", |
| null); |
| tree.failed(status); |
| // prune out any resources in the workspace resource tree that are gone |
| pruneMissingResources(tree, folder); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| /** |
| * Deletes resources from the workspace resource tree below the given |
| * container if they do not exist in the local file system. The given |
| * container itself is not affected. |
| * |
| * @param tree the workspace resource tree |
| * @param container the root container (a folder or project) |
| */ |
| private void pruneMissingResources(IResourceTree tree, IContainer container) { |
| IResource[] children; |
| try { |
| // we're interested in all members, including team-private ones |
| children = container.members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS); |
| } catch (CoreException e) { |
| // bail quietly if there are problem with accessing its members |
| return; |
| } |
| // iterate over children looking for ones to prune |
| for (int i = 0; i < children.length; i++) { |
| IResource child = children[i]; |
| // does child exist in the local file system? |
| java.io.File lfsChild = child.getLocation().toFile(); |
| if (!lfsChild.exists()) { |
| // child no longer exists in local file system |
| switch (child.getType()) { |
| case IResource.FILE : |
| // update workspace resource tree to say we deleted it |
| tree.deletedFile((IFile) child); |
| break; |
| case IResource.FOLDER : |
| // update workspace resource tree to say we deleted it |
| tree.deletedFolder((IFolder) child); |
| break; |
| case IResource.PROJECT : |
| // can't happen since container is a folder or project |
| break; |
| case IResource.ROOT : |
| // can't happen since container is a folder or project |
| break; |
| } |
| } else { |
| // child exists in local file system |
| if (child.getType() == IResource.FOLDER) { |
| // recurse to prune inside a child subfolder |
| pruneMissingResources(tree, (IFolder) child); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This <code>IMoveDeleteHook</code> method implements |
| * <code>IResource.delete(int,IProgressMonitor)</code> where the receiver is |
| * a project. This example implementation illustrates the steps involved |
| * (except for progress monitoring). |
| * |
| * @param tree the workspace resource tree; this object is only valid |
| * for the duration of the invocation of this method, and must not be |
| * used after this method has completed |
| * @param project the handle of the project to delete; the receiver of |
| * <code>IResource.delete(int,IProgressMonitor)</code> |
| * @param updateFlags bit-wise or of update flag constants as per |
| * <code>IResource.delete(int,IProgressMonitor)</code> |
| * @param monitor the progress monitor, or <code>null</code> as per |
| * <code>IResource.delete(int,IProgressMonitor)</code> |
| * @return <code>false</code> if this method declined to assume |
| * responsibility for this operation, and <code>true</code> if this |
| * method attempted to carry out the operation |
| * @see IResource#delete(int,IProgressMonitor) |
| * @see IMoveDeleteHook#deleteProject(IResourceTree, IProject, int, IProgressMonitor) |
| */ |
| public boolean deleteProject( |
| IResourceTree tree, |
| IProject project, |
| int updateFlags, |
| IProgressMonitor monitor) { |
| |
| // do nothing if the project does not exist in the workspace resource tree |
| if (!project.exists()) { |
| // return true to say that the operation has been done |
| return true; |
| } |
| |
| // succeed immediately if project content area does not exist in the local file system |
| java.io.File lfsProjectContentArea = project.getLocation().toFile(); |
| if (!lfsProjectContentArea.exists()) { |
| // update the workspace resource tree to match |
| tree.deletedProject(project); |
| // return true to say that the operation has been done |
| return true; |
| } |
| |
| // decide if files in project content area are supposed to get deleted |
| boolean alwaysDelete = |
| (updateFlags & IResource.ALWAYS_DELETE_PROJECT_CONTENT) != 0; |
| boolean neverDelete = |
| (updateFlags & IResource.NEVER_DELETE_PROJECT_CONTENT) != 0; |
| boolean force = (updateFlags & IResource.FORCE) != 0; |
| boolean deletingContent; |
| if (neverDelete) { |
| deletingContent = false; |
| } else if (alwaysDelete) { |
| // ALWAYS_DELETE_PROJECT_CONTENT implies FORCE |
| force = true; |
| deletingContent = true; |
| } else { |
| // default: delete content area for open projects but not closed ones |
| deletingContent = project.isOpen(); |
| } |
| |
| // if deleting the project content area and FORCE is not specified |
| // (and ALWAYS_DELETE_PROJECT_CONTENT, which implies FORCE) |
| // fail if the workspace resource tree is not in sync with the |
| // directory subtree in the local file system (depth infinity) |
| if (deletingContent & !force) { |
| boolean inSync = tree.isSynchronized(project, IResource.DEPTH_INFINITE); |
| if (!inSync) { |
| // report failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Project " |
| + project.getFullPath() |
| + " is out of sync with the local file system", |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| // n.b. never capture local history when a project is being deleted |
| // because history is kept with the project and gets deleted too |
| |
| if (!deletingContent) { |
| // delete the project from the workspace resource tree |
| tree.deletedProject(project); |
| // return true to say that the operation has been done |
| return true; |
| } |
| |
| // try to delete the project content area in the local file system |
| boolean lfsSuccess = deleteLocalSubtree(lfsProjectContentArea); |
| if (lfsSuccess) { |
| // if successful delete the project from the workspace resource tree |
| tree.deletedProject(project); |
| // return true to say that the operation has been done |
| return true; |
| } else { |
| // report an unexpected failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Unable to delete project " |
| + project.getFullPath() |
| + " from the local file system", |
| null); |
| tree.failed(status); |
| // prune out any resources in the workspace resource tree that are gone |
| pruneMissingResources(tree, project); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| /** |
| * This <code>IMoveDeleteHook</code> method implements |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code> where the receiver |
| * is a file. This example implementation illustrates the steps involved |
| * (except for progress monitoring). |
| * |
| * @param tree the workspace resource tree; this object is only valid |
| * for the duration of the invocation of this method, and must not be |
| * used after this method has completed |
| * @param source the handle of the file to move; the receiver of |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code>; |
| * guaranteed to exist in the workspace resource tree |
| * @param destination the handle of where the file will move to; the handle |
| * equivalent of the first parameter to |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code>; |
| * guaranteed to not exist in the workspace resource tree; |
| * parent container guaranteed to exist and be open |
| * @param updateFlags bit-wise or of update flag constants as per |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code> |
| * @param monitor the progress monitor, or <code>null</code> as per |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code> |
| * @return <code>false</code> if this method declined to assume |
| * responsibility for this operation, and <code>true</code> if this |
| * method attempted to carry out the operation |
| * @see IResource#move(IPath,int,IProgressMonitor) |
| * @see IMoveDeleteHook#moveFile(IResourceTree,IFile,IFile,int,IProgressMonitor) |
| */ |
| public boolean moveFile( |
| IResourceTree tree, |
| IFile source, |
| IFile destination, |
| int updateFlags, |
| IProgressMonitor monitor) { |
| |
| // Given: source exists in workspace resource tree |
| // Given: destination does not exist in workspace resource tree |
| // Given: destination parent exists and is open in workspace resource tree |
| if (!source.exists() |
| || destination.exists() |
| || !destination.getParent().isAccessible()) { |
| throw new IllegalArgumentException(); |
| } |
| |
| // if FORCE is not specified, fail if the workspace resource tree is |
| // not in sync at source file in the local file system |
| boolean force = (updateFlags & IResource.FORCE) != 0; |
| if (!force) { |
| boolean inSync = tree.isSynchronized(source, IResource.DEPTH_ZERO); |
| if (!inSync) { |
| // report failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "File " + source.getFullPath() + " is out of sync with the local file system", |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| // capture the current state of the source file in the local history if |
| // KEEP_HISTORY is specified |
| boolean keepHistory = (updateFlags & IResource.KEEP_HISTORY) != 0; |
| if (keepHistory) { |
| tree.addToLocalHistory(source); |
| } |
| |
| // try to move the source file in the local file system |
| java.io.File lfsSource = source.getLocation().toFile(); |
| java.io.File lfsDestination = destination.getLocation().toFile(); |
| boolean moveSuccess = moveLocalFile(lfsSource, lfsDestination); |
| if (moveSuccess) { |
| // update the workspace resource tree to match |
| tree.movedFile(source, destination); |
| // moveLocalFile may have affected timestamp |
| // update timestamp to avoid having out of sync destination |
| tree.updateMovedFileTimestamp(destination, tree.computeTimestamp(destination)); |
| // return true to say that the operation has been done |
| return true; |
| } else { |
| // report an unexpected failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Unable to move file " + source.getFullPath() + " in the local file system", |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| /** |
| * This <code>IMoveDeleteHook</code> method implements |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code> where the receiver |
| * is a folder. This example implementation illustrates the steps involved |
| * (except for progress monitoring). The general approach illustrated here |
| * moves the entire subtree in the local file system and then fixes |
| * up the workspace resource tree to match using |
| * <code>IResourceTree.movedFolder</code>. |
| * |
| * @param tree the workspace resource tree; this object is only valid |
| * for the duration of the invocation of this method, and must not be |
| * used after this method has completed |
| * @param source the handle of the folder to move; the receiver of |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code>; |
| * guaranteed to exist in the workspace resource tree |
| * @param destination the handle of where the folder will move to; the |
| * handle equivalent of the first parameter to |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code> |
| * guaranteed to not exist in the workspace resource tree; |
| * parent container guaranteed to exist and be open |
| * @param updateFlags bit-wise or of update flag constants as per |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code> |
| * @param monitor the progress monitor, or <code>null</code> as per |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code> |
| * @return <code>false</code> if this method declined to assume |
| * responsibility for this operation, and <code>true</code> if this |
| * method attempted to carry out the operation |
| * @see IResource#move(IPath,int,IProgressMonitor) |
| * @see IMoveDeleteHook#moveFolder(IResourceTree,IFolder,IFolder,int,IProgressMonitor) |
| */ |
| public boolean moveFolder( |
| IResourceTree tree, |
| IFolder source, |
| IFolder destination, |
| int updateFlags, |
| IProgressMonitor monitor) { |
| |
| // Given: source exists in workspace resource tree |
| // Given: destination does not exist in workspace resource tree |
| // Given: destination parent exists and is open in workspace resource tree |
| if (!source.exists() |
| || destination.exists() |
| || !destination.getParent().isAccessible()) { |
| throw new IllegalArgumentException(); |
| } |
| |
| // if FORCE is not specified, fail if the workspace resource tree is |
| // not in sync at source folder and its descendents in the local file system |
| boolean force = (updateFlags & IResource.FORCE) != 0; |
| if (!force) { |
| boolean inSync = tree.isSynchronized(source, IResource.DEPTH_INFINITE); |
| if (!inSync) { |
| // report failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Folder " + source.getFullPath() + " is out of sync with the local file system", |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| // capture the current state of all files in the local history if |
| // KEEP_HISTORY is specified |
| boolean keepHistory = (updateFlags & IResource.KEEP_HISTORY) != 0; |
| if (keepHistory) { |
| addAllFilesToHistory(tree, source); |
| } |
| |
| // try to move the subtree in the local file system |
| java.io.File lfsSource = source.getLocation().toFile(); |
| java.io.File lfsDestination = destination.getLocation().toFile(); |
| boolean lfsSuccess = moveLocalSubtree(lfsSource, lfsDestination); |
| if (lfsSuccess) { |
| // update the workspace resource tree to match |
| tree.movedFolderSubtree(source, destination); |
| // moveLocalSubtree may have affected file timestamps |
| // update file timestamps to avoid having out of sync destination |
| updateTimestamps(tree, destination); |
| // return true to say that the operation has been done |
| return true; |
| } else { |
| // report an unexpected failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Unable to move folder " + source.getFullPath() + " in the local file system", |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| /** |
| * Updates timestamps for all files in the workspace resource subtree rooted |
| * at the given container. The given container must exist and be accessible. |
| * <p> |
| * Note that this is <b>not</b> the same thing as <code>refreshLocal</code>, |
| * because (a) it only updates file timestamps for file known to the |
| * workspace resource tree, and (b) the updated timestamps are not |
| * considered to be changes. |
| * |
| * @param tree the workspace resource tree |
| * @param container the root container |
| */ |
| private void updateTimestamps(final IResourceTree tree, IContainer container) { |
| |
| // Resource visitor for updtating timestamp for files it visits |
| class UpdateVisitor implements IResourceVisitor { |
| public boolean visit(IResource resource) { |
| if (resource.getType() == IResource.FILE) { |
| IFile file = (IFile) resource; |
| tree.updateMovedFileTimestamp(file, tree.computeTimestamp(file)); |
| } |
| return true; |
| } |
| } |
| |
| try { |
| // update timestamps for both regular and team-private members |
| container.accept( |
| new UpdateVisitor(), |
| IResource.DEPTH_INFINITE, |
| IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS); |
| } catch (CoreException e) { |
| // visitor does not throw CoreException |
| // container is known to be accessible |
| // exception should not happen |
| throw new RuntimeException(); |
| } |
| } |
| |
| /** |
| * This <code>IMoveDeleteHook</code> method implements |
| * <code>IResource.move(IProjectDescrition,int,IProgressMonitor)</code> |
| * where the receiver is a project. This example implementation illustrates |
| * the steps involved (except for progress monitoring). The general approach |
| * illustrated here relocates the entire project content are in the local |
| * file system and then fixes up the workspace resource tree to match using |
| * <code>IResourceTree.movedProject</code>. |
| * |
| * @param tree the workspace resource tree; this object is only valid |
| * for the duration of the invocation of this method, and must not be |
| * used after this method has completed |
| * @param source the handle of the project to move; the receiver of |
| * <code>IResource.move(IProjectDescription,int,IProgressMonitor)</code> |
| * or <code>IResource.move(IPath,int,IProgressMonitor)</code>; |
| * guaranteed to exist and be open in the workspace resource tree |
| * @param description the new description of the project; the first |
| * parameter to |
| * <code>IResource.move(IProjectDescription,int,IProgressMonitor)</code>, or |
| * a copy of the project's description with the location changed to the |
| * path given in the first parameter to |
| * <code>IResource.move(IPath,int,IProgressMonitor)</code> |
| * @param updateFlags bit-wise or of update flag constants as per |
| * <code>IResource.move(IProjectDescription,int,IProgressMonitor)</code> |
| * or <code>IResource.move(IPath,int,IProgressMonitor)</code> |
| * @param monitor the progress monitor, or <code>null</code> as per |
| * <code>IResource.move(IProjectDescription,int,IProgressMonitor)</code> |
| * or <code>IResource.move(IPath,int,IProgressMonitor)</code> |
| * @return <code>false</code> if this method declined to assume |
| * responsibility for this operation, and <code>true</code> if this method |
| * attempted to carry out the operation |
| * @see IResource#move(IPath,int,IProgressMonitor) |
| * @see IResource#move(IProjectDescription,int,IProgressMonitor) |
| * @see IMoveDeleteHook#moveProject(IResourceTree,IProject,IProjectDescription,int,IProgressMonitor) |
| */ |
| public boolean moveProject( |
| IResourceTree tree, |
| IProject project, |
| IProjectDescription description, |
| int updateFlags, |
| IProgressMonitor monitor) { |
| |
| // Given: source exists and is open in workspace resource tree |
| if (!project.exists() || !project.isOpen()) { |
| throw new IllegalArgumentException(); |
| } |
| |
| // are we changing the name of the project? |
| // this affects the workspace resource tree |
| String oldProjectName = project.getName(); |
| String newProjectName = description.getName(); |
| boolean renaming = !newProjectName.equals(oldProjectName); |
| |
| // are we changing the location of the project content area? |
| // this affects the files in the local file system |
| // and the workspace resource tree |
| IPath oldProjectContentArea = project.getLocation(); |
| IPath newProjectContentArea = description.getLocation(); |
| if (newProjectContentArea == null) { |
| // compute path of new default project content area |
| newProjectContentArea = Platform.getLocation().append(newProjectName); |
| } |
| boolean relocating = !newProjectContentArea.equals(oldProjectContentArea); |
| |
| IProject newProject = |
| project.getWorkspace().getRoot().getProject(newProjectName); |
| |
| if (!renaming && !relocating) { |
| // the hook method should never have been called |
| throw new IllegalArgumentException(); |
| } |
| |
| // fail we are renaming to project that exists in workspace resource tree |
| if (renaming && newProject.exists()) { |
| // report failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Project " + newProject.getFullPath() + " already exists", |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| |
| // relocate the project content area |
| if (relocating) { |
| // need to move files in local file system |
| boolean lfsMoveSuccess = |
| moveLocalSubtree( |
| oldProjectContentArea.toFile(), |
| newProjectContentArea.toFile()); |
| if (!lfsMoveSuccess) { |
| // report failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Unable to move project contents for " + newProject.getFullPath(), |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| } |
| |
| // update project in workspace resource tree |
| boolean treeMoveSuccess = tree.movedProjectSubtree(project, description); |
| if (!treeMoveSuccess) { |
| // report failure |
| Status status = |
| new Status( |
| Status.ERROR, |
| "com.example.movedeletehook", |
| 0, |
| "Unable to move project " + project.getFullPath(), |
| null); |
| tree.failed(status); |
| // return true to say that the operation has been done |
| return true; |
| } |
| |
| if (relocating) { |
| // moveLocalSubtree may have affected file timestamps |
| // update file timestamps to avoid having out of sync destination |
| updateTimestamps(tree, newProject); |
| } |
| // return true to say that the operation has been done |
| return true; |
| } |
| |
| /** |
| * Captures local history for all files in the subtree rooted at the |
| * given container. The given container must exist and be accessible. |
| * |
| * @param tree the workspace resource tree |
| * @param container the root container |
| */ |
| private void addAllFilesToHistory( |
| final IResourceTree tree, |
| IContainer container) { |
| |
| // Resource visitor for keeping local listory for files it visits |
| class KeepVisitor implements IResourceVisitor { |
| public boolean visit(IResource resource) { |
| if (resource.getType() == IResource.FILE) { |
| // capture current state of file in local history |
| tree.addToLocalHistory((IFile) resource); |
| } |
| return true; |
| } |
| } |
| |
| try { |
| // only save history for regular members as there is little point |
| // in saving history for team-private members |
| container.accept(new KeepVisitor(), IResource.DEPTH_INFINITE, IResource.NONE); |
| } catch (CoreException e) { |
| // visitor does not throw CoreException |
| // container is known to be accessible |
| // exception should not happen |
| throw new RuntimeException(); |
| } |
| } |
| |
| /** |
| * Deletes the given directory subtree in the local file system. |
| * Returns <code>true</code> immediately if the given folder does |
| * not exist. |
| * |
| * @param lfsFolder the folder in the local file system |
| * @return <code>true</code> if the given folder no longer exists, |
| * and <code>false</code> if it was not deleted |
| */ |
| private boolean deleteLocalSubtree(java.io.File lfsFolder) { |
| java.io.File[] lfsChildren = lfsFolder.listFiles(); |
| if (lfsChildren == null) { |
| if (lfsFolder.exists()) { |
| // folder does exists (I/O error or not a directory) |
| return false; |
| } else { |
| // folder does not exist |
| return true; |
| } |
| } |
| // delete all children first |
| for (int i = 0; i < lfsChildren.length; i++) { |
| java.io.File lfsChild = lfsChildren[i]; |
| if (lfsChild.isFile()) { |
| // attempt to delete the file |
| boolean childSuccess = lfsChild.delete(); |
| // don't worry if we could not delete the child file |
| // we will be unable to delete the parent folder if it still |
| // has children |
| } else if (lfsChild.isDirectory()) { |
| // otherwise recurse over the child subtree |
| boolean childSuccess = deleteLocalSubtree(lfsChild); |
| // don't worry if we could not delete the child folder |
| // we will be unable to delete the parent folder if it still |
| // has children |
| } |
| } |
| // delete folder now that children should be gone |
| boolean success = lfsFolder.delete(); |
| // we're happy as long as folder is gone |
| return !lfsFolder.exists(); |
| } |
| |
| /** |
| * Copies the given source file in the local file system to the given |
| * destination file. The operation fails rather than overwriting an |
| * existing destination file. The destination parent folder will be |
| * created if required. |
| * |
| * @param lfsSource the source file in the local file system; this is |
| * the file to be copied |
| * @param lfsDestination the destination file in the local file system; |
| * this is where the file is to be copied to |
| * @return <code>true</code> if the copy was successful, |
| * and <code>false</code> if the copy failed |
| */ |
| private boolean copyLocalFile( |
| java.io.File lfsSource, |
| java.io.File lfsDestination) { |
| |
| // create the destination parent folder if required |
| java.io.File lfsDestinationParent = lfsDestination.getParentFile(); |
| if (lfsDestinationParent != null && !lfsDestinationParent.exists()) { |
| boolean mkdirSuccess = lfsDestinationParent.mkdirs(); |
| if (!mkdirSuccess) { |
| // fail if unable to create destination parent folder |
| return false; |
| } |
| } |
| |
| if (lfsDestination.exists()) { |
| // so that we do not overwrite an existing file |
| return false; |
| } |
| |
| InputStream in = null; |
| OutputStream out = null; |
| try { |
| in = new BufferedInputStream(new FileInputStream(lfsSource)); |
| out = new BufferedOutputStream(new FileOutputStream(lfsDestination)); |
| long fileSize = lfsSource.length(); |
| // use 10KB buffer for small files |
| int bufferSize = 10 * 1024; |
| if (fileSize > 100 * 1024) { |
| // use 100KB buffer for medium-sized files |
| bufferSize = 100 * 1024; |
| } |
| if (fileSize > 1000 * 1024) { |
| // use 1MB buffer for large files |
| bufferSize = 1000 * 1024; |
| } |
| byte[] buffer = new byte[bufferSize]; |
| while (true) { |
| int bytesRead = in.read(buffer); |
| if (bytesRead < 0) { |
| break; |
| } |
| out.write(buffer, 0, bytesRead); |
| } |
| return true; |
| } catch (FileNotFoundException e) { |
| // unable to open the source file for input |
| // or unable to open the destination file for output |
| // fail in either case |
| return false; |
| } catch (IOException e) { |
| // fail |
| return false; |
| } finally { |
| // close both streams in all cases |
| try { |
| if (in != null) { |
| in.close(); |
| } |
| } catch (IOException closeException) { |
| // ignore |
| } |
| try { |
| if (out != null) { |
| out.close(); |
| } |
| } catch (IOException closeException) { |
| // better safe than sorry |
| // fail if we have problems closing the output stream |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * Copies the given folder subtree in the local file system to the given |
| * destination folder. The operation fails rather than overwriting an |
| * existing file. The destination parent folder will be created if required. |
| * |
| * @param lfsSource the source folder in the local file system; this is |
| * the folder to be copied |
| * @param lfsDestination the destination folder in the local file system; |
| * this is where the folder is to be copied to |
| * @return <code>true</code> if the copy was successful, |
| * and <code>false</code> if the copy failed |
| */ |
| private boolean copyLocalSubtree( |
| java.io.File lfsSource, |
| java.io.File lfsDestination) { |
| |
| java.io.File[] lfsChildren = lfsSource.listFiles(); |
| if (lfsChildren == null) { |
| // folder does not exist, I/O error, or not a directory |
| // fail since no source to copy |
| return false; |
| } |
| |
| // create the destination folder if required |
| if (!lfsDestination.exists()) { |
| boolean mkdirSuccess = lfsDestination.mkdirs(); |
| if (!mkdirSuccess) { |
| // fail if unable to create destination folder |
| return false; |
| } |
| } |
| |
| // copy all children |
| for (int i = 0; i < lfsChildren.length; i++) { |
| java.io.File lfsChild = lfsChildren[i]; |
| java.io.File lfsDestinationChild = |
| new java.io.File(lfsDestination, lfsChild.getName()); |
| boolean childSuccess = false; |
| if (lfsChild.isFile()) { |
| // attempt to copy the file |
| childSuccess = copyLocalFile(lfsChild, lfsDestinationChild); |
| } else if (lfsChild.isDirectory()) { |
| // otherwise recursively copy the child folder |
| childSuccess = copyLocalSubtree(lfsChild, lfsDestinationChild); |
| } |
| if (!childSuccess) { |
| // fail immediately if unable to successfully copy a child |
| return false; |
| } |
| } |
| // succeed since all children were successfully copied |
| return true; |
| } |
| |
| /** |
| * Moves the given source file in the local file system to the given |
| * destination file. The destination's parent folder will be created |
| * if required. The timestamp of the file may change in the process. |
| * |
| * @param lfsSource the source file in the local file system; this is |
| * the file to be moved |
| * @param lfsDestination the destination file in the local file system; |
| * this is where the file is to be moved to |
| * @return <code>true</code> if the move was successful, |
| * and <code>false</code> if the move failed |
| */ |
| private boolean moveLocalFile( |
| java.io.File lfsSource, |
| java.io.File lfsDestination) { |
| |
| // create the destination parent (and ancestors) if required |
| java.io.File lfsDestinationParent = lfsDestination.getParentFile(); |
| if (lfsDestinationParent != null && !lfsDestinationParent.exists()) { |
| boolean mkdirsSuccess = lfsDestinationParent.mkdirs(); |
| if (!mkdirsSuccess) { |
| // fail because destination parent cannot be created |
| return false; |
| } |
| } |
| |
| // attempt to rename the file |
| boolean renameSuccess = lfsSource.renameTo(lfsDestination); |
| if (renameSuccess) { |
| // that was easy |
| return true; |
| } |
| |
| // plan B: copy the file and delete the original |
| boolean copySuccess = copyLocalFile(lfsSource, lfsDestination); |
| if (!copySuccess) { |
| // fail if unable to make copy |
| return false; |
| } |
| |
| // delete the source file |
| boolean deleteSuccess = lfsSource.delete(); |
| // operation succeeds iff we copied source and successfully deleted it |
| return deleteSuccess; |
| } |
| |
| /** |
| * Moves the given subtree in the local file system to the given |
| * destination. The destination's parent folder will be created |
| * if required. |
| * |
| * @param lfsSource the source folder in the local file system; this is |
| * the folder to be moved |
| * @param lfsDestination the destination folder in the local file system; |
| * this is where the folder is to be moved to |
| * @return <code>true</code> if the move was successful, |
| * and <code>false</code> if the move failed |
| */ |
| private boolean moveLocalSubtree( |
| java.io.File lfsSource, |
| java.io.File lfsDestination) { |
| |
| // create the destination parent (and ancestors) if required |
| java.io.File lfsDestinationParent = lfsDestination.getParentFile(); |
| if (lfsDestinationParent != null && !lfsDestinationParent.exists()) { |
| boolean mkdirsSuccess = lfsDestinationParent.mkdirs(); |
| if (!mkdirsSuccess) { |
| // fail because destination parent cannot be created |
| return false; |
| } |
| } |
| |
| // attempt to rename the folder |
| boolean renameSuccess = lfsSource.renameTo(lfsDestination); |
| if (renameSuccess) { |
| // that was easy |
| return true; |
| } |
| |
| // plan B: copy the folder and then delete the original |
| boolean copySuccess = copyLocalSubtree(lfsSource, lfsDestination); |
| if (!copySuccess) { |
| // fail if unable to make copy |
| return false; |
| } |
| |
| // delete the source folder |
| boolean deleteSuccess = deleteLocalSubtree(lfsSource); |
| // operation succeeds iff we copied source and successfully deleted it |
| return deleteSuccess; |
| } |
| } |