本端歷程範例

瞭解 Synchronize API 的最佳方式是建立一個能夠實際運作的簡單範例。 在這個範例中,我們將在同步化視圖中建立一個頁面來顯示工作區中所有檔案的最新本端歷程狀態。 當工作區有所改變時,本端歷程同步化會自動更新,您可以開啟比較編輯器來進行瀏覽、合併和改變。 我們也將新增一個自訂裝飾元來顯示本端歷程元素的最新時間戳記,以及新增一個動作來將工作區檔案回復成最新儲存的本端歷程狀態。 這是一個很好的範例,因為我們已有一個資源變式的儲存庫,我們不需要管理它。

在這個範例的其餘部分,我們將使用一個執行中的範例。 大部分程式碼都在這個頁面中,但不是全部。 您可以在 org.eclipse.team.examples.filesystem 外掛程式的本端歷程套件中找到完整的程式碼。 您可以從 CVS 儲存庫移出專案,用它來作為閱讀這份指導教學時的參考資料。免責聲明:這個範例外掛程式中的程式碼可能會在一段時間之後有所改變。 如果要取得符合這個範例使用內容的複本,您可以利用 3.0 版標示(很可能是 R3_0)或 2004 年 6 月 28 日的日期標示來移出專案。

本端歷程概觀

這個畫面顯示同步化視圖中的本端歷程同步化。 您可以利用它來瀏覽本端資源和歷程中的最新狀態之間的變更。 它有一個自訂裝飾元,用來顯示本端歷程項目的相關時間戳記, 還有一個自訂動作,用來將檔案回復成本端歷程中的內容。 另外也請注意,它使用標準同步化視圖呈現方式,這個呈現方式會提供問題註釋、壓縮的資料夾佈置以及導覽按鈕。

定義本端歷程的變式

第一步驟定義一個變式來代表本端歷程中的元素。 這可讓同步化 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 介面),因此,這很簡單。 一般而言,當建立變式時,您必須提供一個存取內容的方法、一個供使用者識別這個變式的內容 ID,以及一個名稱。 只有在階段作業之間持續保存變式時,才會需要 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。 另外,與本端歷程的同步化也只是雙向作業,因為我們不能存取基礎資源,因此,不會使用比較兩個資源變式的方法。

請注意,如果變式不存在(也就是說,它是空值),同步化計算就不會呼叫比較器的 compare 方法。 只有當兩個元素都存在時,才會呼叫它。 在我們的範例中,沒有本端歷程的檔案和(不曾有本端歷程的)所有資料夾都會如此。 為了處理這個情況,我們必須自己定義 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();
  }
}

我們已將建構子置換成一律提供 null 基礎(因為我們只用雙向比較), 我們也已將同步化種類計算修改成在沒有遠端項目的情況下傳回 IN_SYNC (因為我們只在乎本端歷程中有本端檔案和檔案狀態的情況)。

建立 Subscriber

現在,我們要建立一個 Subscriber,用來存取本端歷程中的資源變式。 由於工作區中的任何檔案都能夠儲存本端歷程,因此,本端歷程 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) {
        // 只有最後一個狀態
        variant = new LocalHistoryVariant(states[0]);
      } 
    }
    SyncInfo info = new LocalHistorySyncInfo(resource, variant, comparator);
    info.init();
    return info;
  } catch (CoreException e) {
    throw TeamException.asTeamException(e);
  }
}

Subscriber 將傳回新的 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);
  }
}

這個方法的有趣細節在於,如果刪除的資源有本端歷程,它會傳回不存在的子項。 結果我們的 Subscriber 便能夠傳回只在本端歷程中、且已不再工作區中的元素之 SyncInfo。

新增本端歷程同步化參與者

目前,我們已建立了用來存取本端歷程中的元素之 SyncInfo 的類別。 接下來,我們將建立若干 UI 元素,以便在同步化視圖中,有一個可用來顯示本端歷程中各個元素的最後歷程狀態的頁面。 由於我們有一個 Subscriber,因此,將這個加到同步化視圖非常簡單。 我們從新增一個同步化參與者延伸點開始:

<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 和作業。 我們在這裡免費得到的行為是在背景中執行以及顯示所處理的節點之忙碌狀態的能力。 這個動作會將工作區中的所有資源回復成它們在本端歷程中的最新狀態。 這個動作是藉由將動作構成要素加到參與者配置中而新增的。 這個配置用來說明建置顯示實際同步化 UI 的參與者頁面時所用的內容。

參與者將依照下列方式來初值設定配置,以將本端歷程動作群組加到快速功能表中:

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;
}
}

這個裝飾元會從同步化視圖所顯示的模型元素中擷取資源,且會將本端歷程資源變式的內容 ID 附加到出現在視圖中的文字標籤上。

最終片段用來提供建立本端歷程參與者的精靈。 「團隊同步化」視景會定義一個廣域同步化動作,讓使用者快速建立同步化。 另外,「同步化」視圖工具列也提供了建立同步化的能力。 如果要開始作業,請建立一個 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 的簡單範例,我們省略了一些細節,使範例更加容易瞭解。 撰寫既有回應能力又準確的同步化支援並不簡單,最難的部分是管理同步化資訊和同步化狀態變更的通知。 如果 SubscriberParticipants 所關聯者適合的話,一旦 Subscriber 完整地實作,則使用者介面是最簡單的部分。 如果需要更多範例,請參閱 org.eclipse.team.example.filesystem 外掛程式,並且瀏覽在 Subscriber 和 ISynchronizeParticipant 的工作區中的子類別。

下一節說明一些可協助您從頭撰寫 Subscriber 的類別和介面,其中包括如何在工作台階段作業之間快取同步化狀態。