Exemple d'historique local

Le meilleur moyen de comprendre les API de synchronisation est de créer un exemple simple qui fonctionne vraiment. Dans cet exemple, vous allez créer une page dans la vue de synchronisation pour afficher le dernier état de l'historique local pour tous les fichiers de l'espace de travail. La synchronisation de l'historique local est automatiquement mise à jour en cas de modifications dans l'espace de travail et un éditeur de comparaison peut s'afficher pour permettre la navigation, la fusion et les modifications. Nous allons également ajouter un décorateur personnalisé pour afficher le dernier horodatage de l'élément de l'historique local et une action pour faire revenir les fichiers de l'espace de travail à leur dernier état d'historique local. Il s'agit d'un exemple excellent car nous disposons déjà d'une mémoire de variantes de ressource et nous n'avons pas besoin de la gérer.

Dans le reste de cet exemple, nous utiliserons un exemple fonctionnel. Une grand partie, mais pas l'intégralité, du code source est intégrée dans cette page. Le code source dans son intégralité est disponible dans le package de l'historique local du plug-in org.eclipse.team.examples.filesystem. Vous pouvez réserver le projet du référentiel CVS et l'utiliser comme référence lorsque vous lisez de didacticiel. Clause de protection : Le code source dans l'exemple de plug-in peut varier dans le temps. Pour obtenir une copie qui correspond à ce qui est utilisé dans cet exemple, vous pouvez réserver le projet à l'aide de la balise de version 3.0 (R3_0, le plus souvent) ou une balise de date du 28 juin 2004.

présentation de l'historique local

Cette capture s'écran présente la synchronisation de l'historique local dans la vue de synchronisation. Grâce à celle-ci, vous pouvez parcourir les modifications entre la ressource locale et le dernier état de l'historique. Elle possède un décorateur personnalisé pour afficher l'horodatage associé à l'entrée de l'historique local et une action personnalisée pour faire revenir le fichier au contenu de l'historique local. Notez également que la présentation standard de la vue de synchronisation permet d'insérer des annotations en cas de problème, de présenter des dossiers compactés et d'utiliser des boutons de navigation.

Définition des variantes pour l'historique local

La première étape consiste à définir une variante pour représenter les éléments à partir de l'historique local. Cela permet aux API de synchronisation d'accéder au contenu à partir de l'historique local afin de pouvoir comparer le contenu actuel et l'afficher à l'attention de l'utilisateur.

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

Dans la mesure où l'interface IFileState permet déjà d'accéder au contenu du fichier à partir de l'historique local (c'est-à-dire, d'implémenter l'interface IStorage), l'opération était aisée. En général, lorsque vous créez une variante, vous devez indiquer un moyen d'accéder au contenu, un identifiant de contenu qui sera affiché à l'attention de l'utilisateur afin d'identifier cette variante, ainsi qu'un nom. La méthode asBytes() n'est nécessaire que si la variante est conservé d'une session à l'autre.

Ensuite, créez un comparateur de variantes qui permet au calcul de la classe SyncInfo de comparer les ressources locales avec leurs variantes. Là encore, cette opération est aisée car l'existence d'un état d'historique local diffère du contenu actuel du fichier. Cela s'explique par le fait que la spécification de l'historique local indique qu'il ne sera pas créé d'état d'historique local si le fichier n'a pas été modifié.

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

Comme nous savons que l'existence de l'état d'historique local implique qu'il soit différent de la version locale, nous pouvons renvoyez false lors de la comparaison du fichier et de son état d'historique local. De même, la synchronisation avec l'historique local n'est que bidirectionnelle car nous n'avons pas accès à une ressource standard donc la méthode comparant deux variantes de ressource n'est pas utilisée.

Notez que le calcul de la synchronisation n'appelle pas la méthode de comparaison du comparateur si la variante n'existe pas (c'est-à-dire, si elle prend la valeur null). La méthode n'est appelée que si les deux éléments existent. Dans l'exemple, cela se produirait deux fois pour les fichiers qui n'ont pas d'historique local et pour tous les dossiers (qui n'ont jamais d'historique local). Pour gérer cet aspect, vous devez définir votre propre sous-classe de la classe SyncInfo pour modifier l'état de synchronisation calculé pour ces cas.

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

Nous avons supplanté le constructeur afin de toujours fournir une base qui prend la valeur null (puisque nous n'utilisons que la comparaison bidirectionnelle) et nous avons modifié le calcul du type de synchronisation afin de renvoyer IN_SYNC en l'absence d'élément distant car nous ne nous soucions que des cas dans lesquels il y a un fichier local et un état de fichier dans l'historique local.

Création de l'abonné

A présent, nous allons créer un abonné qui permet d'accéder aux variantes de ressource dans l'historique local. Dans la mesure où l'historique local peut être enregistré pour n'importe quel fichier dans l'espace de travail, l'abonné de l'historique local supervise toutes les ressources et l'ensemble des racines correspondra à tous les projets de l'espace de travail. De même, il n'est pas nécessaire de prévoir la possibilité de régénérer l'abonné car l'historique local ne varie que lorsque le contenu d'un fichier local varie. Par conséquent, vous pouvez mettre à jour l'état à chaque fois qu'un delta est rencontré au niveau d'une ressource. Il ne reste plus que deux méthodes intéressantes dans l'abonné d'historique local : l'obtention d'une classe SyncInfo et le balayage de l'espace de travail.

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) {
        // dernier état uniquement
        variant = new LocalHistoryVariant(states[0]);
      } 
    }
    SyncInfo info = new LocalHistorySyncInfo(resource, variant, comparator);
    info.init();
    return info;
  } catch (CoreException e) {
    throw TeamException.asTeamException(e);
  }
}

L'abonné renvoie une nouvelle instance de la classe SyncInfo qui contient le dernier état du fichier dans l'historique local. La classe SyncInfo est créée avec une variante d'historique local pour l'élément distant. Pour les projets, les dossiers et les fichiers sans historique local, aucune variante de ressource distante n'est indiquée, et de ce fait la ressource sera considérée comme étant en cours de synchronisation en raison de la méthode calculateKind dans LocalHistorySyncInfo.

Le reste du code de l'abonné de l'historique local correspond à l'implémentation de la méthode 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);
  }
}

Le détail intéressant au sujet de cette méthode est qu'elle renvoie des enfants inexistants si une ressource supprimée possède un historique local. Cela permet à l'abonné de renvoyer la classe SyncInfo pour les éléments qui n'existent que dans l'historique local et ne se trouvent plus dans l'espace de travail.

Ajout d'un participant à la synchronisation d'historique local

Jusque-là, vous avez créé les classes qui permettent d'accéder à SyncInfo pour les éléments de l'historique local. Ensuite, vous allez créer les éléments de l'interface utilisateur qui permettent de disposer d'une page dans la vue de synchronisation pour afficher le dernier état d'historique pour chaque élément de l'historique local. Dans la mesure où vous utilisez un abonné, il est facile d'ajouter cet élément à la vue de synchronisation. Commençons par ajouter un point d'extension pour le participant à la synchronisation :

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

Ensuite, vous devez implémenter LocalHistoryParticipant, qui définit la sous-classe SubscriberParticipant pour détermine, dans son intégralité, le comportement par défaut pour recueillir des informations de synchronisation à partir de l'abonné et de mettre à jour les états de synchronisation en cas de variation de l'espace de travail. En outre, vous allez ajouter une action afin de faire revenir les ressources de l'espace de travail à la dernière version dans l'historique local.

Tout d'abord, vous allez examiner la manière dont une action personnalisée est ajoutée au participant.

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

Ici, vous ajoutez une version spécifique de SynchronizeMoidelAction, ainsi qu'une opération. Le comportement obtenu se traduit par la possibilité d'exécution en tâche de fond et affiche le statut occupé pour les noeuds utilisés. L'action fait revenir toutes les ressources de l'espace de travail au dernier état dans l'historique local. L'action est ajoutée par le biais d'une contribution à la configuration des participants. La configuration est utilisée pour décrire les propriétés utilisées pour créer la page du participant qui va afficher la véritable interface utilisateur de synchronisation.

Pour ajouter le groupe d'actions de l'historique local au menu contextuel, le participant initialise la configuration de la manière suivante :

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

A présent, étudions comment définir une décoration personnalisée. La dernière ligne de la méthode ci-dessus enregistre le décorateur suivant avec la configuration de la page.

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

Le décorateur extrait la ressource de l'élément modèle qui s'affiche dans la vue de synchronisation et ajoute l'identifiant de contenu de la variante de ressource de l'historique local à l'étiquette de texte qui s'affiche dans la vue.

La toute dernière partie permet de définir un assistant permettant de créer un participant à l'historique local. La perspective de la synchronisation d'équipe définit une action de synchronisation globale qui permet aux utilisateurs de créer rapidement une synchronisation. Par ailleurs, la possibilité de créer la synchronisation est possible dans la barre d'outils de la vue de synchronisation. Commencez par créer un point d'extension 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>

Cette opération ajoute l'assistant à la liste. Dans la méthode finish() des assistants, nous créons seulement le participant et nous l'ajoutons au gestionnaire de synchronisation.

LocalHistoryPartipant participant = new LocalHistoryPartipant();
ISynchronizeManager manager = TeamUI.getSynchronizeManager();
manager.addSynchronizeParticipants(new ISynchronizeParticipant[] {participant});
ISynchronizeView view = manager.showSynchronizeViewInActivePage();
view.display(participant);

Conclusion

Cet exemple simple présente l'utilisation des API de synchronisation. Certains détails ont été mis de côté par souci de simplification de l'exemple. Le développement de la prise en charge de synchronisation réactive et précise n'est pas évident, la partie la plus complexe étant la gestion des informations de synchronisation et la notification des modifications d'état de synchronisation. L'interface utilisateur, si celle qui est associée à SubscriberParticipants est adéquate, est simple une fois que l'implémentation de l'abonné est terminée. Pour découvrir d'autres exemples, reportez-vous au plug-in org.eclipse.team.example.filesystem et accédez aux sous-classes Subscriber et ISynchronizeParticipant de l'espace de travail.

La section suivante décrit des classes et des interfaces qui peuvent vous aider à développer un abonné à partir de zéro et traite notamment de la manière de mettre en cache les états de synchronisation entre les sessions du plan de travail.