/******************************************************************************* * 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 IMoveDeleteHook illustrating * how this hook should be used. *

* 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: *

* 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 * org.eclipse.core.resources.team.IResourceTree * object passed to the hook on each occasion. *

*

* 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. *

* * @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 IMoveDeleteHook method implements * IResource.delete(int,IProgressMonitor) 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 * IResource.delete(int,IProgressMonitor) * @param updateFlags bit-wise or of update flag constants as per * IResource.delete(int,IProgressMonitor) * @param monitor the progress monitor, or null as per * IResource.delete(int,IProgressMonitor) * @return false if this method declined to assume * responsibility for this operation, and true 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 IMoveDeleteHook method implements * IResource.delete(int,IProgressMonitor) 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 * IResource.delete(int,IProgressMonitor) * @param updateFlags bit-wise or of update flag constants as per * IResource.delete(int,IProgressMonitor) * @param monitor the progress monitor, or null as per * IResource.delete(int,IProgressMonitor) * @return false if this method declined to assume * responsibility for this operation, and true 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 IMoveDeleteHook method implements * IResource.delete(int,IProgressMonitor) 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 * IResource.delete(int,IProgressMonitor) * @param updateFlags bit-wise or of update flag constants as per * IResource.delete(int,IProgressMonitor) * @param monitor the progress monitor, or null as per * IResource.delete(int,IProgressMonitor) * @return false if this method declined to assume * responsibility for this operation, and true 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 IMoveDeleteHook method implements * IResource.move(IPath,int,IProgressMonitor) 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 * IResource.move(IPath,int,IProgressMonitor); * 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 * IResource.move(IPath,int,IProgressMonitor); * 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 * IResource.move(IPath,int,IProgressMonitor) * @param monitor the progress monitor, or null as per * IResource.move(IPath,int,IProgressMonitor) * @return false if this method declined to assume * responsibility for this operation, and true 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 IMoveDeleteHook method implements * IResource.move(IPath,int,IProgressMonitor) 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 * IResourceTree.movedFolder. * * @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 * IResource.move(IPath,int,IProgressMonitor); * 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 * IResource.move(IPath,int,IProgressMonitor) * 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 * IResource.move(IPath,int,IProgressMonitor) * @param monitor the progress monitor, or null as per * IResource.move(IPath,int,IProgressMonitor) * @return false if this method declined to assume * responsibility for this operation, and true 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. *

* Note that this is not the same thing as refreshLocal, * 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 IMoveDeleteHook method implements * IResource.move(IProjectDescrition,int,IProgressMonitor) * 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 * IResourceTree.movedProject. * * @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 * IResource.move(IProjectDescription,int,IProgressMonitor) * or IResource.move(IPath,int,IProgressMonitor); * guaranteed to exist and be open in the workspace resource tree * @param description the new description of the project; the first * parameter to * IResource.move(IProjectDescription,int,IProgressMonitor), or * a copy of the project's description with the location changed to the * path given in the first parameter to * IResource.move(IPath,int,IProgressMonitor) * @param updateFlags bit-wise or of update flag constants as per * IResource.move(IProjectDescription,int,IProgressMonitor) * or IResource.move(IPath,int,IProgressMonitor) * @param monitor the progress monitor, or null as per * IResource.move(IProjectDescription,int,IProgressMonitor) * or IResource.move(IPath,int,IProgressMonitor) * @return false if this method declined to assume * responsibility for this operation, and true 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 true immediately if the given folder does * not exist. * * @param lfsFolder the folder in the local file system * @return true if the given folder no longer exists, * and false 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 true if the copy was successful, * and false 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 true if the copy was successful, * and false 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 true if the move was successful, * and false 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 true if the move was successful, * and false 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; } }