| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> |
| <html lang="en"> |
| <head> |
| |
| <meta name="copyright" content="Copyright (c) IBM Corporation and others 2000, 2016. This page is made available under license. For full details see the LEGAL in the documentation book that contains this page." > |
| |
| <meta content="text/html; charset=ISO-8859-1" |
| http-equiv="Content-Type"> |
| <meta content="text/css" http-equiv="Content-Style-Type"> |
| <link type="text/css" charset="ISO-8859-1" href="../book.css" |
| rel="STYLESHEET"> |
| <title>Synchronization Support - Local History Example</title> |
| <link href="../book.css" type="text/css" rel="stylesheet"> |
| </head> |
| <body style="background-color: rgb(255, 255, 255);"> |
| <h2>Local History Example</h2> |
| <p>The best way to understand the Synchronize APIs is to create a simple example |
| that actually works. In this example we will be creating a page in the Synchronize |
| View that will display the latest local history state for all files in the workspace. |
| The local history synchronization will update automatically when changes are |
| made to the workspace, and a compare editor can open to browse, merge, then |
| changes. We will also add a custom decorator to show the last timestamp of the |
| local history element and an action to revert the workspace files to their latest |
| saved local history state. This is an excellent example because we already have |
| a store of resource variants available and we don't have to manage it.</p> |
| <p>For the remainder of this example we will make use of a running example. Much, |
| but not all, of the source code will be included on this page. The full source |
| code can be found in the local history package of the <a |
| href="http://git.eclipse.org/c/www.eclipse.org/eclipse.git/tree/platform-ui/plugins/org.eclipse.ui.examples.filesystem/">org.eclipse.ui.examples.filesystem</a> |
| plug-in. You can check the project out from the Git repository and use it as |
| a reference while you are reading this tutorial. <em>Disclaimer</em>: The source |
| code in the example plug-ins may change over time. To get a copy that matches |
| what is used in this example, you can check out the project using the 3.3.2 version |
| tag (most likely R3_3_2) or a date tag of June 10, 2007.<br> |
| <br> |
| <img src="images/team_synchronize_example_localhistory_overview.png" alt="local history overview"><br> |
| <br> |
| This screen shot shows the local history synchronization in the Synchronize |
| View. With it you can browse the changes between the local resource and the |
| latest state in history. It has a custom decorator for displaying the timestamp |
| associated with the local history entry and a custom action to revert your file |
| to the contents in the local history. Notice also that the standard Synchronize |
| View presentation is used which provide problem annotations, compressed folder |
| layout, and navigation buttons.</p> |
| <h3>Defining the variants for local history</h3> |
| <p>The first step is to define a variant to represent the elements from local |
| history. This will allow the synchronize APIs to access the contents from the |
| local history so it can be compared with the current contents and displayed |
| to the user.</p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| public class LocalHistoryVariant implements IResourceVariant { |
| private final IFileState state; |
| public LocalHistoryVariant(IFileState state) { |
| this.state = state; |
| } |
| public String getName() { |
| return state.getName(); |
| } |
| public boolean isContainer() { |
| return false; |
| } |
| public IStorage getStorage(IProgressMonitor monitor) throws TeamException { |
| return state; |
| } |
| public String getContentIdentifier() { |
| return DateFormat.getDateTimeInstance().format(new Date(state.getModificationTime())); |
| } |
| public byte[] asBytes() { |
| return null; |
| } |
| public IFileState getFileState() { |
| return state; |
| } |
| } |
| </span></pre> |
| |
| <p>Since the IFileState interface already provides access to the contents of the |
| file from local history (i.e. implements the IStorage interface), this was easy. |
| Generally, when creating a variant you have to provide a way of accessing the |
| content, a content identifier that will be displayed to the user to identify |
| this variant, and a name. The asBytes() method is only required if persisting |
| the variant between sessions. <br> |
| <br> |
| Next, let's create a variant comparator that allows the SyncInfo calculation |
| to compare local resources with their variants. Again, this is easy because |
| the existence of a local history state implies that the content of the local |
| history state differs from the current contents of the file. This is because |
| the specification for local history says that it won't create a local history |
| state if the file hasn't changed. </p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| public class LocalHistoryVariantComparator implements IResourceVariantComparator { |
| public boolean compare(IResource local, IResourceVariant remote) { |
| return false; |
| } |
| public boolean compare(IResourceVariant base, IResourceVariant remote) { |
| return false; |
| } |
| public boolean isThreeWay() { |
| return false; |
| } |
| } |
| </span></pre> |
| |
| <p>Because we know that the existence of the local history state implies that |
| it is different from the local, we can simply return <strong>false</strong> |
| when comparing the file to its local history state. Also, synchronization with |
| the local history is only two-way because we don't have access to a base resource |
| so the method for comparing two resource variants is not used.</p> |
| <p> Note that the synchronize calculation won't call the compare method of the |
| comparator if the variant doesn't exist (i.e. is null). It is only called if |
| both elements exist. In our example, this would occur both for files that don't |
| have a local history and for all folders (which never have a local history). |
| To deal with this, we need to define our own subclass of SyncInfo in order to |
| modify the calculated synchronization state for these cases.</p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| public class LocalHistorySyncInfo extends SyncInfo { |
| public LocalHistorySyncInfo(IResource local, IResourceVariant remote, IResourceVariantComparator comparator) { |
| super(local, null, remote, comparator); |
| } |
| protected int calculateKind() throws TeamException { |
| if (getRemote() == null) |
| return IN_SYNC; |
| else |
| return super.calculateKind(); |
| } |
| } |
| </span></pre> |
| |
| <p>We have overridden the constructor to always provide a base that is <em>null</em> |
| (since we are only using two-way comparison) and we have modified the synchronization |
| kind calculation to return <em>IN_SYNC</em> if there is no remote (since we |
| only care about the cases where there is a local file and a file state in the |
| local history.</p> |
| <h3>Creating the Subscriber</h3> |
| <p>Now we will create a Subscriber that will provide access to the resource variants |
| in the local history. Since local history can be saved for any file in the workspace, |
| the local history Subscriber will supervise every resource and the set of roots |
| will be all projects in the workspace. Also, there is no need to provide the |
| ability to refresh the subscriber since the local history changes only when |
| the contents of a local file changes. Therefore, we can update our state whenever |
| a resource delta occurs. That leaves only two interesting method on our local |
| history subscriber: obtaining a SyncInfo and traversing the workspace. </p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| public SyncInfo getSyncInfo(IResource resource) throws TeamException { |
| try { |
| IResourceVariant variant = null; |
| if(resource.getType() == IResource.FILE) { |
| IFile file = (IFile)resource; |
| IFileState[] states = file.getHistory(null); |
| if(states.length > 0) { |
| // last state only |
| variant = new LocalHistoryVariant(states[0]); |
| } |
| } |
| SyncInfo info = new LocalHistorySyncInfo(resource, variant, comparator); |
| info.init(); |
| return info; |
| } catch (CoreException e) { |
| throw TeamException.asTeamException(e); |
| } |
| } |
| </span></pre> |
| |
| <p>The Subscriber will return a new SyncInfo instance that will contain the latest |
| state of the file in local history. The SyncInfo is created with a local history |
| variant for the remote element. For projects, folders and files with no local |
| history, no remote resource variant is provided, which will result in the resource |
| being considered in-sync due to the <em>calculateKind</em> method in our LocalHistorySyncInfo. |
| </p> |
| <p>The remaining code in the local history subscriber is the implementation of |
| the <em>members</em> method:</p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| public IResource[] members(IResource resource) throws TeamException { |
| try { |
| if(resource.getType() == IResource.FILE) |
| return new IResource[0]; |
| IContainer container = (IContainer)resource; |
| List existingChildren = new ArrayList(Arrays.asList(container.members())); |
| existingChildren.addAll(Arrays.asList(container.findDeletedMembersWithHistory(IResource.DEPTH_INFINITE, null))); |
| return (IResource[]) existingChildren.toArray(new IResource[existingChildren.size()]); |
| } catch (CoreException e) { |
| throw TeamException.asTeamException(e); |
| } |
| } |
| </span></pre> |
| |
| <p>The interesting detail of this method is that it will return non-existing children |
| if a deleted resource has local history. This will allow our Subscriber to return |
| SyncInfo for elements that only exist in local history and are no longer in |
| the workspace.</p> |
| <h3>Adding a Local History Synchronize Participant</h3> |
| <p>So far we have created the classes which provide access to SyncInfo for elements |
| in local history. Next, we will create the UI elements that will allow us to |
| have a page in the Synchronize View to display the last history state for every |
| element in local history. Since we have a Subscriber, adding this to the Synchronize |
| View is easy. Let's start by adding an synchronize participant extension point:</p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| <extension |
| point="org.eclipse.team.ui.synchronizeParticipants"> |
| <participant |
| persistent="false" |
| icon="icons/full/wizards/synced.gif" |
| class="org.eclipse.team.examples.localhistory.LocalHistoryParticipant" |
| name="Latest From Local History" |
| id="org.eclipse.team.synchronize.example"/> |
| </extension> |
| </span></pre> |
| |
| <p>Next we have to implement the LocalHistoryParticipant. It will subclass SubscriberParticipant |
| which will provide all the default behavior for collecting SyncInfo from the |
| subscriber and updating sync states when workspace changes occur. In addition, |
| we will add an action to revert the workspace resources to the latest in local |
| history.</p> |
| <p>First, we will look at how a custom action is added to the participant.</p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| public static final String CONTEXT_MENU_CONTRIBUTION_GROUP = "context_group_1"; //$NON-NLS-1$ |
| |
| private class LocalHistoryActionContribution extends SynchronizePageActionGroup { |
| public void initialize(ISynchronizePageConfiguration configuration) { |
| super.initialize(configuration); |
| appendToGroup( |
| ISynchronizePageConfiguration.P_CONTEXT_MENU, CONTEXT_MENU_CONTRIBUTION_GROUP, |
| new SynchronizeModelAction("Revert to latest in local history", configuration) { //$NON-NLS-1$ |
| protected SynchronizeModelOperation getSubscriberOperation(ISynchronizePageConfiguration configuration, IDiffElement[] elements) { |
| return new RevertAllOperation(configuration, elements); |
| } |
| }); |
| } |
| } |
| </span></pre> |
| |
| <p>Here we are adding a specific SynchronizeMoidelAction and operation. The behavior |
| we get for free here is the ability to run in the background and show busy status |
| for the nodes that are being worked on. The action reverts all resources in |
| the workspace to their latest state in local history. The action is added by |
| adding an action contribution to the participants configuration. The configuration |
| is used to describe the properties used to build the participant page that will |
| display the actual synchronize UI. </p> |
| <p>The participant will initialize the configuration as follows in order to add |
| the local history action group to the context menu:</p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| protected void initializeConfiguration(ISynchronizePageConfiguration configuration) { |
| super.initializeConfiguration(configuration); |
| configuration.addMenuGroup( |
| ISynchronizePageConfiguration.P_CONTEXT_MENU, |
| CONTEXT_MENU_CONTRIBUTION_GROUP); |
| configuration.addActionContribution(new LocalHistoryActionContribution()); |
| configuration.addLabelDecorator(new LocalHistoryDecorator()); |
| } |
| </span></pre> |
| |
| <p>Now lets look at how we can provide a custom decoration. The last line of the |
| above method registers the following decorator with the page's configuration.</p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| private class LocalHistoryDecorator extends LabelProvider implements ILabelDecorator { |
| public String decorateText(String text, Object element) { |
| if(element instanceof ISynchronizeModelElement) { |
| ISynchronizeModelElement node = (ISynchronizeModelElement)element; |
| if(node instanceof IAdaptable) { |
| SyncInfo info = (SyncInfo)((IAdaptable)node).getAdapter(SyncInfo.class); |
| if(info != null) { |
| LocalHistoryVariant state = (LocalHistoryVariant)info.getRemote(); |
| return text+ " ("+ state.getContentIdentifier() + ")"; |
| } |
| } |
| } |
| return text; |
| } |
| |
| public Image decorateImage(Image image, Object element) { |
| return null; |
| } |
| } |
| </span></pre> |
| |
| <p>The decorator extracts the resource from the model element that appears in |
| the synchronize view and appends the content identifier of the local history |
| resource variant to the text label that appears in the view.</p> |
| <p>The last and final piece is to provide a wizard that will create the local |
| history participant. The Team Synchronizing perspective defines a global synchronize |
| action that allows users to quickly create a synchronization. In addition, the |
| ability to create synchronizations in available from the Synchronize view toolbar. |
| To start, create a synchronizeWizards extension point:</p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| <extension |
| point="org.eclipse.team.ui.synchronizeWizards"> |
| <wizard |
| class="org.eclipse.team.examples.localhistory.LocalHistorySynchronizeWizard" |
| icon="icons/full/wizards/synced.gif" |
| description="Synchronize resources with their previous contents in the local history" |
| name="Synchronize with Latest From Local History" |
| id="ExampleSynchronizeSupport.wizard1"/> |
| </extension> |
| </span></pre> |
| |
| <p>This will add our wizard to the list and in the wizards performFinish() method we |
| will simply create our participant and add it to the synchronize manager.</p> |
| |
| <pre><span style="color: rgb(68, 68, 204);"> |
| LocalHistoryParticipant participant = new LocalHistoryParticipant(); |
| ISynchronizeManager manager = TeamUI.getSynchronizeManager(); |
| manager.addSynchronizeParticipants(new ISynchronizeParticipant[] {participant}); |
| ISynchronizeView view = manager.showSynchronizeViewInActivePage(); |
| view.display(participant); |
| </span></pre> |
| |
| <h3>Conclusion</h3> |
| <p>This is a simple example of using the synchronize APIs and we have glossed |
| over some of the details in order to make the example easier to understand. |
| Writing responsive and accurate synchronization support is non-trivial, the |
| hardest part being the management of synchronization information and the notification |
| of synchronization state changes. The user interface, if the one associated |
| with SubscriberParticipants is adequate, is the easy part once the Subscriber |
| implementation is complete. For more examples please refer to the org.eclipse.team.example.filesystem |
| plug-in and browse the subclasses in the workspace of Subscriber and ISynchronizeParticipant.<br> |
| <br> |
| The next section describes some class and interfaces that can help you write |
| a Subscriber from scratch including how to cache synchronization states between |
| workbench sessions.</p> |
| |
| </body> |
| </html> |