了解“同步 API”的最佳方法是创建一个真正起作用的简单示例。在此示例中,我们将在“同步视图”中创建一个页面,该页面将显示工作空间中的所有文件的最新本地历史状态。当更改工作空间时,将自动更新本地历史同步,并且可以打开比较编辑器来进行浏览、合并然后再进行更改。还将添加一个定制修饰符来显示本地历史元素的最新时间戳记,还将添加一个操作来将工作空间文件还原为它们的最新保存的本地历史状态。这是一个极好的示例,因为我们已经提供了资源变体库,并且无需管理它。
对于此示例的其余部分,我们将利用正在运行的示例。大部分(但不是全部)源代码将包括在此页面中。在 org.eclipse.team.examples.filesystem 插件的本地历史包中可以找到完整的源代码。当您阅读本教程时,可以从 CVS 存储库中检出该项目并将它用作参考。免责:示例插件中的源代码可能会随时间而更改。要获得与本示例中使用的源代码相匹配的副本,可以使用 3.0 版本标记(很可能是 R3_0)或者日期标记 June 28, 2004 来检出该项目。
此屏幕快照显示“同步视图”中的本地历史同步。您可以利用它来浏览本地资源与历史中的最新状态之间的更改。它具有一个定制修饰符,用来显示与本地历史条目相关联的时间戳记,它还具有一个定制操作,可以用来将文件还原为本地历史中的内容。还要注意,使用了标准“同步视图”表示,它提供了问题注释、经过压缩的文件夹布局以及导航按钮。
第一步是定义一个变体来表示本地历史中的元素。这将允许同步 API 访问本地历史中的内容,以便可以将它与当前内容进行比较,并将它显示给用户。
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 Long.toString(state.getModificationTime());
}
public byte[] asBytes() {
return null;
}
}
因为 IFileState 接口已经提供了对本地历史中的文件内容的访问权(即,实现了 IStorage 接口),所以这是很容易的。通常,创建变体时,您必须提供访问内容的方法、将向用户显示的用来标识此变体的内容标识以及名称。仅当要在会话之间持久保存变体时才需要 asBytes() 方法。
接下来,让我们创建一个变体比较器,它允许 SyncInfo 计算将本地资源与它们的变体进行比较。这也是很容易的,因为存在本地历史状态就意味着本地历史状态的内容与文件的当前内容不同。这是因为本地历史的规范声明:如果尚未更改文件,就不会创建本地历史状态。
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;
}
}
因为我们知道存在本地历史状态就意味着它与本地内容不同,所以,当将文件与它的本地历史状态进行比较时,只能返回 false。另外,与本地历史的同步只是两方的,因为我们不能访问基本资源,所以未使用比较两个资源变体的方法。
注意,如果变体不存在(即是空的),则同步计算将不会调用比较器的比较方法。仅当两个元素都存在时才会调用比较方法。在我们的示例中,对于没有本地历史的文件和所有文件夹(它们决不会有本地历史)都会发生这种情况。为此,需要定义我们自己的 SyncInfo 子类,以便修改这些情况下的经过计算的同步状态。
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();
}
}
我们已经覆盖了构造函数以始终提供一个空的库(因为我们只使用两方比较),并且我们已经修改了同步种类计算以在没有远程内容的情况下返回 IN_SYNC(因为我们只关心在本地历史中具有本地文件和文件状态的那些情况)。
现在,我们将创建一个 Subscriber,它将提供对本地历史中的资源变体的访问权。因为可以为工作空间中的任何文件保存本地历史,所以本地历史订户将监视每个资源,并且这组根将是工作空间中的所有项目。另外,不需要提供刷新订户的功能,这是因为仅当本地文件的内容发生更改时才会更改本地历史。因此,每当发生资源变化时,我们就可以更新我们的状态。这对我们的本地历史订户只保留了两个有用的方法:获取 SyncInfo 和遍历工作空间。
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);
}
}
该订户将返回一个新的 SyncInfo 实例,该实例将包含本地历史中的文件的最新状态。SyncInfo 是使用远程元素的本地历史变体创建的。对于没有本地历史的项目、文件夹和文件,没有提供远程资源变体,这将导致由于 LocalHistorySyncInfo 中的 calculateKind 方法而将资源认为是同步的。
本地历史订户中的其余代码是 members 方法的实现:
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);
}
}
此方法的有用的详细信息是,如果已删除的资源具有本地历史,则它将返回不存在的子代。这将允许订户为只存在于本地历史中而不再存在于工作空间中的元素返回 SyncInfo。
到目前为止,我们已经创建了一些类,这些类提供了对本地历史中的元素的 SyncInfo 的访问权。接下来,我们将创建一些用户界面元素,这些元素将允许我们在“同步视图”中具有一个页面来显示本地历史中的每个元素的最新历史状态。因为我们有订户,所以将此页面添加至“同步视图”是很容易的。让我们从添加同步参与者扩展点开始:
<extension point="org.eclipse.team.ui.synchronizeParticipants">
<participant
persistent="false"
icon="synced.png"
class="org.eclipse.team.synchronize.example.LocalHistoryParticipant"
name="Latest From Local History"
id="org.eclipse.team.synchronize.example"/>
</extension>
接下来,我们必须实现 LocalHistoryParticipant。它将成为 SubscriberParticipant 的子类,SubscriberParticipant 将提供用来从订户收集 SyncInfo 以及用来在工作空间发生更改时更新同步状态的所有缺省行为。另外,我们将添加一个操作来将工作空间资源还原为本地历史中的最新状态。
首先,我们将查看如何将定制操作添加至参与者。
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);
}
});
}
}
现在,我们将添加特定的 SynchronizeMoidelAction 和操作。我们在此处免费获得的行为是能够在后台运行并且对正在处理的节点显示繁忙状态的能力。该操作将工作空间中的所有资源还原为它们在本地历史中的最新状态。通过将操作添加项添加至参与者配置来添加该操作。配置用来描述一些属性,这些属性用来构建将显示实际同步用户界面的参与者页面。
参与者将按如下所示初始化配置,以便将本地历史操作组添加至上下文菜单:
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());
}
现在,让我们考虑可以如何提供定制修饰。以上方法的最后一行将向页面的配置注册以下修饰符。
public 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;
}
}
修饰符从显示在同步视图中的模型元素中抽取资源,并将本地历史资源变体的内容标识追加至显示在视图中的文本标签。
最后一部分是提供一个向导,该向导将创建本地历史参与者。“小组同步”透视图定义了一个全局同步操作,该操作使用户能够快速创建同步。另外,还可以从“同步”视图工具栏中创建同步。首先创建 synchronizeWizards 扩展点:
<extension
point="org.eclipse.team.ui.synchronizeWizards">
<wizard
class="org.eclipse.team.synchronize.example.LocalHistorySynchronizeWizard"
icon="synced.png"
description="Creates a synchronization against the latest local history state of all resources in the workspace"
name="Latest From Local History Synchronize"
id="ExampleSynchronizeSupport.wizard1"/>
</extension>
这将把向导添加至列表,在向导 finish() 方法中,我们将只创建参与者并将它添加至同步管理器。
LocalHistoryPartipant participant = new LocalHistoryPartipant();
ISynchronizeManager manager = TeamUI.getSynchronizeManager();
manager.addSynchronizeParticipants(new ISynchronizeParticipant[] {participant});
ISynchronizeView view = manager.showSynchronizeViewInActivePage();
view.display(participant);
这是一个使用同步 API 的简单示例,我们省略了某些详细信息,以使该示例更容易理解。编写能够回答问题并且准确的同步支持并不是一件容易的事情,最难的部分就是管理同步信息和通知同步状态更改。一旦完成了 Subscriber 实现,用户界面(如果与 SubscriberParticipants 相关联的用户界面就足够)就是比较容易的部分。要获取更多示例,请参阅 org.eclipse.team.example.filesystem 插件,并浏览 Subscriber 和 ISynchronizeParticipant 的工作空间中的子类。
下一节描述可以帮助您从头开始编写订户的一些类和接口,包括如何高速缓存工作台会话之间的同步状态。