Obsługa synchronizacji

Nowością w wersji 3.0 platformy Eclipse są interfejsy API, które służą do zarządzania stanem synchronizacji zasobów obszaru roboczego i zasobów w innym położeniu oraz do wyświetlania tego stanu. Zasób poza obszarem roboczym jest nazywany wariantem. Synchronizowanie polega na wyświetlaniu różnic między zasobami w różnych położeniach oraz opcjonalnie umożliwieniu użytkownikowi zmiany stanu synchronizacji przez wykonanie akcji. Interfejsy API synchronizacji są ortogonalne względem interfejsów API klasy RepositoryProvider i mogą być używane bez dostawcy repozytorium. Celem interfejsu API synchronizacji jest ułatwienie implementacji różnych sposobów prezentowania stanu synchronizacji zasobów. Z tego powodu interfejs API wymaga środków do uzyskiwania informacji o stanie synchronizacji zasobów, ale nie wymaga środków do zmiany tego stanu. Udostępnienie środków do zmiany tego stanu pozostawia się osobie implementującej (chociaż w interfejsie użytkownika są dostępne haki umożliwiające dodawanie do menu opcji właściwych dla dostawcy).

Terminologia

Przed opisem interfejsu API synchronizacji przydatne może być przybliżenie terminologii i pojęć używanych przy omawianiu synchronizacji obszaru roboczego.

Wariant zasobu: lokalny zasób odwzorowany na zasób, który znajduje się w innym położeniu, nazywa się wariantem tego zasobu. Inaczej mówiąc, zasoby są zazwyczaj bardzo podobne, ale mogą się nieznacznie różnić (ze względu na modyfikacje zasobu lokalnego lub zmiany wprowadzane w zdalnej kopii przez innych użytkowników). Przyjmując punkt widzenia lokalnego obszaru roboczego, lokalna kopia będzie nazywana zasobem, a wszelkie kopie zdalne - wariantami zasobu.

Synchronizacja: pod pojęciem synchronizacji rozumie się akcję wyświetlania różnic między wariantami zasobów. Synchronizacja nie wpływa na stan wariantów, tylko pomaga użytkownikom poznać różnice między różnymi zbiorami wariantów. Powszechnie jednak umożliwia się użytkownikom zmienianie stanu wariantów (np. zwrócenie do repozytorium lub przywrócenie wersji z repozytorium) podczas synchronizacji.

Synchronizacja dwu- i trójstronna: wyróżnia się dwa podstawowe typy sposobu określania stanu synchronizacji: porównanie dwu- i trójstronne. W porównaniu dwustronnym uwzględnia się tylko zasób lokalny i pojedynczy wariant zasobu, nazywany zdalnym wariantem zasobu. Na podstawie porównania tego typu można wyświetlać tylko różnice między dwoma zasobami, nie można jednak wyciągać wniosków dotyczących wzajemnego powiązania zmian. Większość systemów repozytoriów kodu określa stan synchronizacji metodą porównania trójstronnego. Porównanie tego rodzaju obejmuje zasób lokalny, zdalny wariant zasobu i bazowy wariant zasobu. Bazowy wariant zasobu reprezentuje wspólnego przodka zasobów lokalnego i zdalnego. Umożliwia to bardziej zaawansowane określanie stanu synchronizacji ze wskazaniem kierunku zmian.

Tabela 1: Stany synchronizacji

Dwustronna Trójstronna
Zmieniony
Usunięty
Dodany
Zmiana wychodząca
Zmiana przychodząca
Usunięcie wychodzące
Usunięcie przychodzące
Dodanie wychodzące
Dodanie przychodzące
Konflikt zmiany
Konflikt usunięcia
Konflikt dodania

Podstawy - klasa SyncInfo

Do opisu stanu synchronizacji służą klasy z pakietu org.eclipse.team.core.synchronize. Najważniejsza z nich to klasa SyncInfo, ponieważ to ona rzeczywiście definiuje stan synchronizacji. Można jej użyć w następujący sposób:

SyncInfo info = getSyncInfo(resource); // to symulowana metoda uzyskiwania informacji o synchronizacji dla zasobu

int changekind = info.getKind();
if(info.getResourceComparator().isThreeWay()) {
if((changeKind & SyncInfo.DIRECTION_MASK) == SyncInfo.INCOMING) {
// zrób coś
}
} else if(changeKind == SyncInfo.CHANGE) {
// zrób coś innego
}

Klasa SyncInfo udostępnia algorytmy porównania zarówno dwu-, jak i trójstronnego, oraz klasę, która może porównywać zasoby (IResourceVariantComparator). Oto przykład modułu porównującego warianty:

public class TimestampVariantComparator implements IResourceVariantComparator {	
protected boolean compare(IResourceVariant e1, IResourceVariant e2) {
if(e1.isContainer()) {
if(e2.isContainer()) {
return true;
}
return false;
}
if(e1 instanceof MyResourceVariant && e2 instanceof MyResourceVariant) {
MyResourceVariant myE1 = (MyResourceVariant)e1;
MyResourceVariant myE2 = (MyResourceVariant)e2;
return myE1.getTimestamp().equals(myE2.getTimestamp());
}
return false;
}
protected boolean compare(IResource e1, IResourceVariant e2) {

}
public boolean isThreeWay() {
return true;
}
}

SyncInfo info = new SyncInfo(resource, variant1, variant2, new TimestampComparator());
info.init(); // oblicz informacje o synchronizacji

Pakiet ten zawiera także kolekcje, które zawierają klasę SyncInfo oraz filtry stosowane do instancji klasy SyncInfo.

Zarządzanie stanem synchronizacji

Jak widać w powyższym przykładzie, klasy SyncInfo i IResourceVariantComparator zapewniają dostęp do stanu synchronizacji zasobów. Nie wiadomo jednak na razie, w jaki sposób zarządza się stanem. Klasa Subscriber zapewnia dostęp do stanu synchronizacji między zasobami w lokalnym obszarze roboczym i zbiorem wariantów tych zasobów przy użyciu porównania dwu- lub trójstronnego, zależnie od natury subskrybenta. Subskrybent udostępnia następujące możliwości:

Interfejsy API nie definiują sposobu tworzenia subskrybenta, zależy to od konkretnej implementacji. Na przykład moduł dodatkowy CVS podczas operacji scalania tworzy subskrybenta, innego dla porównania i innego podczas synchronizowania lokalnego obszaru roboczego z bieżącą gałęzią.

Można teraz wrócić do pierwszego przykładu użycia klasy SyncInfo i zobaczyć, w jaki sposób można użyć subskrybenta w celu uzyskania dostępu do obiektu SyncInfo.

// Utwórz subskrybenta systemu plików i określ, że
// subskrybent będzie przeprowadzał synchronizację z podanym położeniem w systemie plików
Subscriber subscriber = new FileSystemSubscriber("c:\temp\repo");

// Zezwól subskrybentowi na odświeżanie swojego stanu
subscriber.refresh(subscriber.roots(), IResource.DEPTH_INFINITE, monitor);

// Zbierz wszystkie stany synchronizacji i wydrukuj
IResource[] children = subscriber.roots();
for(int i=0; i < children.length; i++) {
printSyncState(children[i]);
}

...

void printSyncState(Subscriber subscriber, IResource resource) {
System.out.println(subscriber.getSyncInfo(resource).toString());
IResource[] children = subscriber.members(resource);
for(int i=0; i < children.length; i++) {
IResource child = children[i];
if(! child.exists()) {
System.out.println(resource.getFullPath() + " nie istnieje w obszarze roboczym");
}
printSyncState(subscriber, children[i]);
}
}

Trzeba pamiętać, że subskrybent ma informacje o zasobach, które nie istnieją w obszarze roboczym, oraz że nieistniejące zasoby mogą być zwracane przez metody Subscriber#members() i SyncInfo#getLocal().

Wyświetlanie stanu synchronizacji w interfejsie użytkownika

Można by dłużej objaśniać sposób zarządzania stanem synchronizacji, zamiast tego warto natomiast zobaczyć, w jaki sposób rzeczywiście uzyskać stan, który ma zostać wyświetlony. ISynchronizeParticipant to komponent interfejsu użytkownika, który wyświetla stan synchronizacji i umożliwia użytkownikowi jego zmianę. W Widoku synchronizacji wyświetlane są elementy uczestniczące w synchronizacji, ale można je także wyświetlać w oknach dialogowych i w kreatorach. Na potrzeby wyświetlania dowolnego stanu synchronizacji, nawet takiego, który nie jest określany przy użyciu klasy SyncInfo i subskrybentów, element uczestniczący jest bardzo ogólnym komponentem.

Istnieje także punkt rozszerzeń o nazwie org.eclipse.team.ui.synchronizeWizards, umożliwiający dodanie kreatora synchronizacji. Pozwala on umieścić kreatora w globalnej akcji synchronizacji oraz w Widoku synchronizacji, dzięki czemu użytkownicy będą mogli w łatwy sposób utworzyć synchronizację danego typu.

Jeśli jednak zaimplementowano subskrybenta, można skorzystać z konkretnego elementu uczestniczącego o nazwie SubscriberParticipant, który udostępnia następujące funkcje:

Przedstawione pojęcia najłatwiej zrozumieć, gdy zobaczy się je w kontekście prostego przykładu. Aby dowiedzieć się, jak użyć wszystkich tych elementów, należy przejść do przykładu synchronizacji historii lokalnej. Natomiast wskazówki dotyczące użycia bardziej zaawansowanych interfejsów API zawiera sekcja Poza podstawy.