| <!doctype html public "-//w3c//dtd html 4.0 transitional//en"> |
| <html> |
| <head> |
| <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> |
| <meta name="Author" content="Eclipse"> |
| <meta name="GENERATOR" content="Microsoft FrontPage 4.0"> |
| <title>Eclipse Platform Core RFC 0008 - Resource Moves, Renames, and Deletes</title> |
| </head> |
| <body> |
| |
| <h1> |
| Resource Moves, Renames, and Deletes</h1> |
| <i><font size=-1>Last revised 15:00 Thursday February 14, 2002</font></i> |
| <p>This proposal deals with improving the Eclipse Platform's support for |
| allowing a Team provider to control the movement of existing resources under its |
| control. The proposal would give Team providers a (headless) way of doing |
| this sort of thing. |
| <h2> |
| Work Item</h2> |
| Ref: uncommitted work item from early 2.0 plan. |
| <blockquote><b>Allow pre-validation of rename/move/delete.</b> VCM providers that need to manage a project's namespace would like advance notification |
| of impending resource moves, renames, and deletes. (Other clients would |
| like a similar opportunity to veto inappropriate name changes to their |
| resources; this is a different concern.) We will add a callback so that |
| the relevant VCM provider will be able to register for advance notification |
| with an opportunity to veto. These changes will affect the UI and Core |
| components.</blockquote> |
| The title and description for this item is not quite right. The work item |
| would be better described as: |
| <blockquote><b>Allow VCM control over rename/move/delete.</b> Team providers |
| sometimes need tighter control over how project resources are manipulated |
| in the local file system. For instance, a project directory might be a |
| specially mounted remote file system located on a Team server, and require |
| special server communication in order to delete, move, or change the name |
| of a resource. Or the Team provider may track version history across move/renames. |
| (Other clients would like a similar opportunity to veto inappropriate name |
| changes to their resources; this is a different concern.) We will add a |
| headless callback so that the relevant Team provider will be able to control |
| moves, renames, and deletes. These changes will affect the Core and Team components.</blockquote> |
| |
| <h2> |
| Problem</h2> |
| Some VCM systems must keep close tabs on the files under their control. |
| <ul> |
| <li>Some Team providers use pessimistic locking, and need to be involved early |
| so that they can determine whether a move/rename is likely to fail because |
| either the source or target resource is check out by another.</li> |
| <li>Some Team providers may materialize "managed" files in a project by mounting |
| or mapping server-side files into the local file system. These client-side |
| directories can also hold other "unmanaged" files; files newly created |
| on the client side files are considered "unmanaged" until the user explicitly |
| places them under management. Under this kind of arrangement, it may be |
| the case that deleting, renaming, or moving a "managed" file cannot be |
| accomplished by direct manipulations of the local file system - it instead |
| requires communication with the Team provider's server to effect such changes. |
| In other words, the Team provider could only support these operations if |
| it can intercept and perform all the local file system manipulations itself.</li> |
| <li>Some Team providers endeavor to maintain a file's version history across |
| namespace changes. In some cases, it may give a Team provider more options |
| if it can get advance notice of moves and renames, rather than having to |
| rely entirely on after-the-fact resource change notifications (in which |
| moves are recorded).</li> |
| <li>Some Team provider require additional repository-specific validation.</li> |
| </ul> |
| <p>(Note that newly-created files are a separate matter; until they exist |
| and have been placed under VCM control, changes to their names are not |
| of interest. This is why create and copy operations are not included |
| in this discussion.) |
| <h2> |
| Background - support available in Eclipse 1.0</h2> |
| There is no support available in Eclipse 1.0 for any facet of this problem. |
| <p>The workspace operations for moving resources are as follows: |
| <ul> |
| <li> |
| IResource.move(IPath,boolean,IProgressMonitor)</li> |
| |
| <ul> |
| <li> |
| IFile.move(IPath,boolean,boolean,IProgressMonitor)</li> |
| |
| <li> |
| IFolder.move(IPath,boolean,boolean,IProgressMonitor)</li> |
| </ul> |
| |
| <li> |
| IResource.move(IProjectDescription,boolean,boolean,IProgressMonitor)</li> |
| |
| <li> |
| IProject.move(IProjectDescription,boolean,IProgressMonitor)</li> |
| |
| <li> |
| IWorkspace.move(IResource[],IPath,boolean,IProgressMonitor)</li> |
| </ul> |
| The workspace operations for deleting resources are: |
| <ul> |
| <li> |
| IResource.delete(boolean,IProgressMonitor)</li> |
| |
| <ul> |
| <li> |
| IWorkspaceRoot.delete(boolean,boolean,IProgressMonitor)</li> |
| |
| <li> |
| IProject.delete(boolean,boolean,IProgressMonitor)</li> |
| |
| <li> |
| IFile.delete(boolean,boolean,IProgressMonitor)</li> |
| |
| <li> |
| IFolder.delete(boolean,boolean,IProgressMonitor)</li> |
| </ul> |
| |
| <li> |
| IWorkspace.delete(IResource[],boolean,IProgressMonitor)</li> |
| </ul> |
| All operations for moving and deleting resources are implemented entirely |
| by the core resource plug-in; there is currently no mechanisms by which |
| another party could participate. |
| <h2> |
| Design Constraints</h2> |
| All of the operations for moving and deleting resources are available to |
| arbitrary clients and are billed as "core" operations; i.e., operations |
| that run headless. Callers may be holding locks at the time of call, or |
| making the call in a non-UI thread. Consequently, it would be problematic |
| if existing uses were to attempt to reacquire locks or pop up dialogs to |
| interact with the user. |
| <p>This means that none of the existing operations can ever bring up a |
| UI - all must run headless. If we add hooks to these operations, the hooks |
| must run headless. |
| <p>The alternative is to add a parallel set of operations that would allow |
| a UI-aware client to pass in a UI context in which to converse with the |
| user (by employing exactly the same design as the recently-added validate-edit |
| support). These new API methods could have more stringent contracts that |
| spell out the conditions which UIs would need. However, this does nothing |
| for existing plug-ins that manipulate resources with the exisiting API |
| methods; these would still need to run headless. |
| <h2> |
| Proposal</h2> |
| The move and delete operations have several facets: |
| <ul> |
| <li> |
| manipulating the local file system</li> |
| |
| <ul> |
| <li> |
| capture "before" file states in local history</li> |
| |
| <li> |
| move or delete files and folders</li> |
| </ul> |
| |
| <li> |
| manipulating the workspace resource tree</li> |
| |
| <ul> |
| <li> |
| move or delete file and folder resources from in-memory tree, including |
| markers and resource properties</li> |
| |
| <li> |
| generate resource deltas</li> |
| </ul> |
| |
| <li> |
| reporting status back to the client</li> |
| </ul> |
| The proposal is that Core would provide a headless hook that would give |
| the hook overall responsibility for carrying out these operations. The hook |
| would have control over the local file system facet of the operation, and would |
| call back into the Core |
| to capture local file history, update the workspace resource tree, report |
| progress, and report status back to the user. The hook would also be furnished |
| with standard building block sub-operations, so that it is possible for a |
| particular hook implementation to embellish the standard behavior without having to |
| implement everything from scratch. |
| <p>This hook would be filled by the Team component, which would delegate |
| the request to the Team provider for the project that owns the resource. |
| The Team provider would not be obliged to participate in these operations; |
| if it decided to pass, the Core would fall back on its own internal implementation |
| the operation much as it does currently. |
| <p>Newly-created files and folders are not under version control automatically. |
| Adding a file or folder to version control is a separate follow-on operation |
| that each Team provider will give the user. This is why create and copy |
| operations are not included in this hook. |
| <h3> |
| Support in Core Component</h3> |
| Here is a sketch of how the move and delete resource operations in the |
| API would be implemented (important details, such as batching, have been |
| omitted in the interest of brevity): |
| <p><tt>Resource</tt> |
| <br><tt> public IStatus move(</tt> |
| <br><tt> IPath destination,</tt> |
| <br><tt> boolean force,</tt> |
| <br><tt> boolean keepHistory,</tt> |
| <br><tt> IProgressMonitor monitor) |
| throws CoreException {</tt> |
| <br><tt> // do easy precondition checks before calling |
| hook</tt> |
| <br><tt> if (!this.exists()) throw new CoreException(...);</tt> |
| <br><tt> if (findResource(destination) != null) throw |
| new CoreException(...);</tt> |
| <br><tt> IResourceTree rto = ...;</tt> |
| <br><tt> INamespaceControlHook hook = ...;</tt> |
| <br><tt> boolean done = false;</tt> |
| <br><tt> if (hook != null) {</tt> |
| <br><tt> done = hook.move(this, |
| destination, force, keepHistory, monitor, rto);</tt> |
| <br><tt> }</tt> |
| <br><tt> if (!done) {</tt> |
| <br><tt> internalMove(this, destination, |
| force, keepHistory, monitor, rto);</tt> |
| <br><tt> }</tt> |
| <br><tt> if (rto.getStatus().isOK()) {</tt> |
| <br><tt> return rto.getStatus();</tt> |
| <br><tt> } else {</tt> |
| <br><tt> throw new CoreException(rto.getStatus());</tt> |
| <br><tt> }</tt> |
| <br><tt>}</tt> |
| <p><tt>IResource</tt> |
| <br><tt> public IStatus delete(</tt> |
| <br><tt> boolean force,</tt> |
| <br><tt> boolean keepHistory,</tt> |
| <br><tt> IProgressMonitor monitor) |
| throws CoreException {</tt> |
| <br><tt> // do easy precondition checks before calling |
| hook</tt> |
| <br><tt> if (!this.exists()) throw new CoreException(...);</tt> |
| <br><tt> IResourceTree rto = ...;</tt> |
| <br><tt> INamespaceControlHook hook = ...;</tt> |
| <br><tt> boolean done = false;</tt> |
| <br><tt> if (hook != null) {</tt> |
| <br><tt> done = hook.delete(this, |
| force, keepHistory, monitor, rto);</tt> |
| <br><tt> }</tt> |
| <br><tt> if (!done) {</tt> |
| <br><tt> ops = internalDelete(this, |
| force, keepHistory, monitor, rto);</tt> |
| <br><tt> }</tt> |
| <br><tt> if (rto.getStatus().isOK()) {</tt> |
| <br><tt> return rto.getStatus();</tt> |
| <br><tt> } else {</tt> |
| <br><tt> throw new CoreException(rto.getStatus());</tt> |
| <br><tt> }</tt> |
| <br><tt>}</tt> |
| <p>(The API operations for moving or deleting a project have a slightly |
| extended contract, but the general flavor of the hook is the same.) |
| <h4> |
| Hook Semantics</h4> |
| The hook is bound by the terms of the API contract of the resource operation, |
| including: |
| <ul> |
| <li> |
| capturing the "before" state of resources in the local file history (when |
| keepHistory parameter is specified)</li> |
| |
| <li> |
| detecting and failing in cases where the resource tree is out of sync with |
| the local file system (when force parameter is specified)</li> |
| |
| <li> |
| reporting progress</li> |
| |
| <li>reporting status to caller</li> |
| </ul> |
| |
| <h4> |
| New Core API</h4> |
| The new core API is provided for the Team component and its Team providers. |
| Rather than clutter up the main resources API package with this special-purpose |
| API, we propose adding the new core API in a new <tt>org.eclipse.core.resources.</tt><tt>team</tt> |
| package. (The question of whether to use a separate API package is clearly |
| a separable issue. If we do go for a separate package, we should consider |
| moving the hook interface for validateEdit/validateSave into this package |
| too.) |
| <p>The <tt>INamespaceControlHook</tt> interface appears in the new core |
| extension point, and would be implemented by the Team component, and likely |
| by each Team provider.<p><tt>package org.eclipse.core.resources.</tt><tt>team</tt> |
| <br><tt>public interface INamespaceControlHook {</tt> |
| <br><tt> public boolean delete(IResource resource,</tt> |
| <br><tt> |
| boolean force,</tt> |
| <br><tt> |
| boolean keepHistory,</tt> |
| <br><tt> |
| IProgressMonitor monitor,</tt> |
| <br><tt> |
| IResourceTree rto);</tt> |
| <br><tt> public boolean move(IResource resource,</tt> |
| <br><tt> |
| IResource destination,</tt> |
| <br><tt> |
| boolean force,</tt> |
| <br><tt> |
| boolean keepHistory,</tt> |
| <br><tt> |
| IProgressMonitor monitor,</tt> |
| <br><tt> |
| IResourceTree rto);</tt> |
| <br><tt> //... others for project move, project delete</tt> |
| <br><tt>}</tt> |
| <p>The <tt>IResourceTree</tt> interface provides the operations that manipulate |
| the resource tree (move markers, properties, collect result statuses) without |
| touching the local file system. The sole implementation would be internal |
| to core, and would be passed as a parameter to the <tt>INamespaceControlHook</tt> |
| implementation. |
| <p><tt>package org.eclipse.core.resources.</tt><tt>team</tt> |
| <br><tt>public interface IResourceTree {</tt> |
| <br><tt> public void deletedFile(IResource file, IStatus status);</tt> |
| <br><tt> public void deletedFolder(IResource folder, IStatus |
| status);</tt> |
| <p><tt> public void movedFile(IResource source, IResource dest,<br> |
| long |
| destTimestamp, IStatus status);</tt> |
| <br><tt> public void movedFolder(IResource source, IResource |
| dest, IStatus status);</tt> |
| <br><tt> public void movedEmptyFolder(IResource source, |
| IResource dest, IStatus status);</tt> |
| <br><tt> public void moved(IResource source, IResource |
| dest);</tt> |
| <p><tt> public void createdFile(IResource file, long timestamp, |
| IStatus status);</tt> |
| <br><tt> public void createdFolder(IResource folder, IStatus |
| status);</tt> |
| <p><tt> public boolean copyToLocalHistory(IResource |
| file, boolean stealable);</tt> |
| <p><tt> // standard building blocks<br> |
| public void standardDeleteResource(IResource resource,<br> |
| boolean force, boolean keepHistory, |
| IProgressMonitor monitor);<br> |
| public void standardMoveResource(IResource resource, |
| IResource dest,<br> |
| boolean force, boolean keepHistory, |
| IProgressMonitor monitor);<br> |
| ...<br> |
| </tt> |
| <br><tt> //... plus others for moving or deleting a project</tt> |
| <br><tt>}</tt> |
| <h4> |
| New Core Extension Point</h4> |
| The Core resources plug-in would provide a new namespaceControl extension |
| point intended to be extended by none other than the Team component. |
| <p><b><i>Identifier:</i></b> <tt>org.eclipse.core.resources.namespaceControl</tt> |
| <br><b><i>Description:</i></b> For providing an implementation of an <tt>INamespaceControlHook</tt> |
| to be used in implementing resource moves and deletes. This extension point |
| tolerates at most one extension. |
| <p><b><i>Configuration Markup:</i></b> |
| <p><tt> <!ELEMENT namespaceControl EMPTY></tt> |
| <br><tt> <!ATTLIST namespaceControl class CDATA #REQUIRED></tt> |
| <p>class - a fully qualified name of a class which implements <tt>org.eclipse.core.resource.team.INamespaceControlHook.</tt> |
| <p><b><i>Examples:</i></b> |
| <br>The following is an example of using the <tt>namespaceControl </tt>extension |
| point. |
| <p>(in file <tt>plugin.xml</tt>) |
| <p><tt> <extension point="org.eclipse.core.resources.namespaceControl"></tt> |
| <br><tt> <namespaceControl class="org.eclipse.team.internal.VCMNamespaceControlHook"/></tt> |
| <br><tt> </extension></tt> |
| <h4> |
| API Compatibility</h4> |
| In summary, the changes affect the Eclipse Platform Core component API |
| in the following ways: |
| <ul> |
| <li> |
| Adding a new extension point.</li> |
| |
| <li> |
| Adding a new API package: <tt>org.eclipse.core.resource.team</tt></li> |
| |
| <li> |
| Adding 2 new API interfaces.</li> |
| </ul> |
| The changes maintain full API compatibility with existing plug-ins in compliance |
| with the 1.0 API. |
| <h4> |
| Local History</h4> |
| The hook implementation calls <tt>copyToLocalHistory</tt> when appropriate |
| to copy the state of a file to the local history before it is changed (or |
| deleted). Bearing in mind that the purpose of the local history is to preserve |
| intermediate file states so that the user has some options for recovering |
| from mistakes, etc., the Team provider should consider whether anything |
| is at risk before adding states to the local history. For instance, the |
| state of a file that the user has neither checked out nor modified is likely |
| not at risk since it exists as a version in the repository; deleting such |
| a file would not deserve a local history entry. |
| <h4> |
| Resource Tree Operations</h4> |
| The hook may need to access the workspace resource tree as well as the |
| local file system. Since the hook is in the midst of a workspace operation, |
| they must not call any of the normal resource API operations that modify |
| resources. However, they must be able to at least walk the resource tree. |
| This they should be able to do through the resource API. |
| <p>In order to modify the resource tree, they call <tt>IResourceTree</tt> |
| methods. These operations affect the resource tree (immediately). If the hook |
| passes the timestamp for a file, it is used; if no timestamp is passed, the local file |
| system is queried for the value. |
| <h4> |
| Timestamping</h4> |
| The Core and the hook must employ the same file timestamp scheme; otherwise, |
| the core will fail to correctly identify when files in the local file system |
| change. Currently, the Core uses an internal native mechanism that is more |
| accurate than java.io.File.lastModified(). We should review whether this |
| is still needed; if it is, we need to add an API method (to <tt>IResourceTree</tt>) |
| so that the Team provider can use it. |
| <h4> |
| Multiple API Entry Points</h4> |
| There are several move methods in the API. All single resource moves should |
| funnel through a single move operation taking a source resource, a target |
| resource, a force flag, a keepHistory flag, and a progress monitor. IWorkspace.move |
| moves multiple siblings to a single target folder. These would be passed |
| through the move pinch point one at a time. |
| <p>Likewise for delete methods. All single resource delete should funnel |
| through a single delete operation taking a source resource, a force flag, |
| a keepHistory flag, and a progress monitor. IWorkspace.delete deletes multiple |
| independent resources, and would pass them through the delete pinch point |
| one at a time. Resources scattered across different projects will get routed |
| automatically to the appropriate project-specific Team provider. |
| <h4> |
| Inter-project Moves</h4> |
| A move operation may move resources from one project into a different project. |
| The move request will be dispatched to the Team provider of the project |
| containing the source resource. The Team provider may decide to be clever |
| if it also manages the destination project; otherwise, it should just use |
| local file system copy operation to copy the files to the destination, |
| and then delete the originals when done. |
| <p>Note that each project in the workspace could have a different Team provider |
| (or none at all), and each could map the project root directory to a different |
| drive or OS file system. |
| <h4> |
| Projects</h4> |
| The API operations for moving or deleting a project have a slightly extended |
| contract, but the general flavor of the hook is the same. The hook is responsible |
| for the files in the project's root directory in the local file system. |
| Project properties, markers, and other project metadata are still a Core |
| responsibility. |
| <h4>Other Useful Building Blocks</h4> |
| <p>The IResourceTree API should also include standard implementations of the |
| operations as additional building blocks. This would make it simpler for a Team |
| provider to embellish the standard behavior instead of having to reimplement it |
| from scratch.</p> |
| <h4> |
| Example</h4> |
| Assumptions: |
| <ul> |
| <li>Project contains /P/f/a.txt(t1),b.txt(t2).</li> |
| <li>Resource tree is in perfect sync with local file system.</li> |
| <li>Operation is move folder /P/f to /P/g force=false, keepHistory=true</li> |
| <li>All local file system manipulation done "by hand" (i.e., |
| avoiding standard building blocks).</li> |
| </ul> |
| <p>Sequence of calls:</p> |
| <p><code>Folder[/P/f].move(dest=Path[/P/g],force=false,keepHistory=true,monitor) |
| <br>- VCMNSHook.move(source=Folder[/P/f],dest=Folder[/P/g],force,keepHistory,monitor,rto) |
| <br>-- VCMProvider1.move(source,dest,force,keepHistory,monitor,rto) |
| <br>--- java.io creates folder /P/g |
| <br>--- rto.createdFolder(dest, OK) |
| <br>---- Folder[/P/g] created in resource tree |
| <br>--- rto.copyToLocalHistory(File[P/f/a.txt],steal=false) |
| <br>--- check timestamp on /P/f/a.txt is t1 |
| <br>--- java.io rename file /P/f/a.txt to /P/g/a.txt |
| <br>--- new timestamp of /P/g/a.txt is t1' |
| <br>--- rto.movedFile(File[/P/f/a.txt], t1, File[/P/g/a.txt], t1', OK) |
| <br>---- File[/P/g/a.txt] created in resource tree with timestamp t1' |
| <br>---- markers and properties copied from File[/P/f/a.txt] to File[/P/g/a.txt] |
| <br>---- File[/P/f/a.txt] deleted from resource tree |
| <br>---- delta for File[/P/g/a.txt] records moved from File[/P/f/a.txt] |
| <br>---- delta for File[/P/f/a.txt] records moved to File[/P/g/a.txt] |
| <br>--- check timestamp on /P/f/b.txt is t2 |
| <br>--- java.io rename file /P/f/b.txt to /P/g/b.txt |
| <br>--- new timestamp of /P/g/b.txt is t2' |
| <br>--- rto.movedFile(File[/P/f/b.txt], t2, File[/P/g/b.txt], t2', OK) |
| <br>---- File[/P/g/b.txt] created in resource tree with timestamp t2' |
| <br>---- markers and properties copied from File[/P/f/b.txt] to File[/P/g/b.txt] |
| <br>---- File[/P/f/b.txt] deleted from resource tree |
| <br>---- delta for File[/P/g/b.txt] records moved from File[/P/f/b.txt] |
| <br>---- delta for File[/P/f/b.txt] records moved to File[/P/g/b.txt] |
| <br>--- java.io deletes folder /P/f |
| <br>--- rto.moved(source,dest) |
| <br>---- markers and properties copied from Folder[/P/f] to Folder[/P/g] |
| <br>---- delta for Folder[/P/g] records moved from Folder[/P/f] |
| <br>---- delta for Folder[/P/f] records moved to Folder[/P/g] |
| <br>--- return true |
| <br>-- return true |
| <br>- return OK</code> |
| <h3> |
| Support in Team Component</h3> |
| |
| <ul> |
| <li> |
| Team component contributes implementation to <tt>namespaceControl </tt>extension |
| point.</li> |
| |
| <li> |
| Team component publishes its own API so that Team providers can get involved.</li> |
| |
| <li> |
| Team component routes requests to the Team provider for the relevant project.</li> |
| |
| <li> |
| Particular Team providers will orchestrate moving or deleting |
| files in a particular project.</li> |
| </ul> |
| |
| <h3> |
| Support in Other Components</h3> |
| None required. These hooks are activated whenever a client calls a core |
| API operation to delete, move, or rename a file or folder. |
| <br> |
| </body> |
| </html> |