Παράδειγμα τοπικού ιστορικού

Ο καλύτερος τρόπος για να κατανοήσετε τα API συγχρονισμού είναι να δημιουργήσετε ένα απλό παράδειγμα το οποίο να λειτουργεί στην πράξη. Στο παράδειγμα αυτό, θα δημιουργήσουμε μια σελίδα στην προβολή "Συγχρονισμός", στην οποία θα εμφανίζεται η πιο πρόσφατη κατάσταση του τοπικού ιστορικού για όλα τα αρχεία του χώρου εργασίας. Ο συγχρονισμός τοπικού ιστορικού θα ενημερώνεται αυτόματα, όταν πραγματοποιούνται αλλαγές στο χώρο εργασίας και μπορεί να ανοίγει μια λειτουργία επεξεργασίας σύγκρισης για την αναζήτηση και τη συγχώνευση των αλλαγών. Θα προσθέσουμε επίσης ένα προσαρμοσμένο διακριτικό που θα εμφανίζει το τελευταίο αποτύπωμα χρόνου για το στοιχείο του τοπικού ιστορικού, καθώς και μια ενέργεια για την επαναφορά των αρχείων του χώρου εργασίας στην τελευταία τους αποθηκευμένη κατάσταση του τοπικού ιστορικού. Αυτό είναι ένα ιδανικό παράδειγμα, καθώς ήδη έχουμε διαθέσιμο ένα χώρο αποθήκευσης παραλλαγών πόρων και δεν χρειάζεται να δημιουργήσουμε.

Στο υπόλοιπο μέρος αυτού του παραδείγματος, θα χρησιμοποιήσουμε ένα παράδειγμα εκτέλεσης. Στη σελίδα αυτή θα συμπεριληφθεί το μεγαλύτερο μέρος του πρωτογενούς κώδικα, αλλά όχι ολόκληρος ο κώδικας. Ο πλήρης πρωτογενής κώδικας βρίσκεται στο πακέτο τοπικού ιστορικού της πρόσθετης λειτουργίαςorg.eclipse.team.examples.filesystem. Μπορείτε να αναλάβετε τον έλεγχο του έργου από το χώρο αποθήκευσης CVS και να το χρησιμοποιήσετε ως αναφορά, ενώ διαβάζετε το παρόν πρόγραμμα εκμάθησης. Αποποίηση ευθύνης: Ο πρωτογενής κώδικας στις πρόσθετες λειτουργίες του παραδείγματος μπορεί να αλλάξει με το χρόνο. Για να λάβετε ένα αντίγραφο για το τι ταιριάζει με όσα χρησιμοποιούνται στο παράδειγμα αυτό, μπορείτε να αναλάβετε τον έλεγχο του έργου χρησιμοποιώντας το προσδιοριστικό εκδοχής 3.0 (πιθανότατα R3_0) ή ένα προσδιοριστικό ημερομηνίας της 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 όταν συγκρίνουμε το αρχείο με την κατάσταση τοπικού ιστορικού του. Επίσης, ο συγχρονισμός με το τοπικό ιστορικό είναι μόνο δύο εκδοχών, διότι δεν έχουμε πρόσβαση σε ένα βασικό πόρο, επομένως η μέθοδος για τη σύγκριση δύο παραλλαγών πόρου δεν χρησιμοποιείται.

Θα πρέπει να σημειωθεί ότι ο υπολογισμός συγχρονισμού δεν καλεί τη μέθοδο της ρουτίνας σύγκρισης, αν η παραλλαγή δεν υπάρχει (δηλαδή έχει την τιμή null). Αυτή καλείται μόνο αν και τα δύο στοιχεία υπάρχουν. Στο παράδειγμά μας, αυτό θα συμβαίνει τόσο για αρχεία που δεν έχουν τοπικό ιστορικό όσο και για όλους τους φακέλους (που δεν έχουν ποτέ τοπικό ιστορικό). Για τον σκοπό αυτό, πρέπει να ορίσουμε μια δική μας υποκλάση της 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) {
        // 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);
  }
}

Η Subscriber θα επιστρέφει μια νέα χρήση της SyncInfo, η οποία θα περιέχει την πιο πρόσφατη κατάσταση του αρχείου στο τοπικό ιστορικό. Η SyncInfo δημιουργείται με μια παραλλαγή τοπικού ιστορικού για το απομακρυσμένο στοιχείο. Για έργα, φακέλους και αρχεία χωρίς τοπικό ιστορικό, δεν παρέχεται καμία απομακρυσμένη παραλλαγή πόρου, με αποτέλεσμα ο πόρος να θεωρείται συγχρονισμένος, λόγω της μεθόδου calculateKind στη LocalHistorySyncInfo.

Το υπόλοιπο τμήμα κώδικα στο συνδρομητή τοπικού ιστορικού είναι η υλοποίηση της μεθόδου 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 για στοιχεία του τοπικού ιστορικού. Στη συνέχεια, θα δημιουργήσουμε στοιχεία του περιβάλλοντος χρήστη τα οποία θα μας επιτρέπουν να έχουμε μια σελίδα στην προβολή "Συγχρονισμός", όπου θα εμφανίζεται η τελευταία κατάσταση ιστορικού για κάθε στοιχείο που περιέχεται στο τοπικό ιστορικό. Αφού έχουμε μια κλάση 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, η οποία παρέχει όλη την προεπιλεγμένη συμπεριφορά για τη συλλογή της 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 συγχρονισμού και δώσαμε μια συνοπτική περιγραφή προκειμένου το παράδειγμα αυτό να γίνει πιο εύκολα κατανοητό. Η εγγραφή μιας αλληλεπιδραστικής και λεπτομερούς υποστήριξης συγχρονισμού δεν είναι εύκολη, και το πιο δύσκολο κομμάτι είναι η διαχείριση των πληροφοριών συγχρονισμού και η ειδοποίηση για τις αλλαγές της κατάστασης συγχρονισμού. Το περιβάλλον χρήστη, εάν αυτό που συσχετίζεται με τη SubscriberParticipants επαρκεί, είναι το εύκολο κομμάτι, μόλις η υλοποίηση της κλάσης Subscriber ολοκληρωθεί. Για περισσότερα παραδείγματα, ανατρέξτε στην πρόσθετη λειτουργία org.eclipse.team.example.filesystem και αναζητήστε τις υποκλάσεις στο χώρο εργασίας των Subscriber και ISynchronizeParticipant.

Στην επόμενη ενότητα περιγράφονται ορισμένες κλάσεις και διεπαφές, οι οποίες σας βοηθούν στην εγγραφή μιας κλάσης Subscriber από την αρχή, συμπεριλαμβανομένου του τρόπου αποθήκευσης στη λανθάνουσα μνήμη (cache) των καταστάσεων συγχρονισμού μεταξύ των συνεδριών του πάγκου εργασίας.