La mejor forma de entender las API de sincronización es crear un ejemplo sencillo que funcione realmente. En este ejemplo, crearemos en la vista Sincronizar una página que visualizará el estado de historial local más reciente de todos los archivos del área de trabajo. La sincronización del historial local se actualizará automáticamente cuando se efectúen cambios en el área de trabajo, y podrá abrirse un editor de comparación para examinar y fusionar los cambios. También se añadirá un decorador personalizado para mostrar la última indicación de la hora del elemento de historial local y una acción para devolver los archivos del área de trabajo a su estado de historial local guardado más recientemente. Se trata de un ejemplo excelente, puesto que ya disponemos de un almacén de variantes de recurso disponible y no es necesario gestionarlo.
En el resto de este ejemplo, se utilizará un ejemplo de ejecución. En esta página se incluirá gran parte del código fuente, pero no todo. El código fuente completo puede encontrarse en el paquete de historial local del conector org.eclipse.team.examples.filesystem.
Puede reservar el proyecto en el repositorio CVS y utilizarlo como referencia durante la lectura
de esta guía de aprendizaje. Declaración de limitación de
responsabilidad: el código fuente de los conectores de ejemplo puede cambiar en
el futuro. Para obtener una copia que coincida con la utilizada en este ejemplo, puede reservar el proyecto mediante el código de la versión 3.0 (probablemente R3_0) o el código de fecha correspondiente a 28 de Junio de 2004.
Esta instantánea de pantalla muestra la sincronización de historial local en la vista Sincronizar. Con ella puede examinar los cambios existentes entre el recurso local y el estado más reciente del historial. Contiene un decorador personalizado para visualizar la indicación de la hora asociada con la entrada de historial local y una acción personalizada para revertir el archivo al contenido del historial local. Tenga en cuenta también que se utiliza la presentación estándar de la vista Sincronizar, que proporciona anotaciones de problemas, diseño de carpetas comprimidas y botones de navegación.
El primer paso conste en definir una variante que represente los elementos del historial local. Esto permitirá a las API de sincronización acceder al contenido del historial local para poder compararlo con el contenido actual y mostrarlo al usuario.
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;
}
}
Dado que la interfaz IFileState ya proporciona acceso al contenido del archivo del historial local (es decir, implementa la interfaz IStorage), este paso ha sido fácil.
Generalmente, al crear una variante es necesario suministrar una forma de acceder al contenido, un identificador de contenido que se mostrará al usuario para identificar este variante, y un nombre. El método asBytes() sólo es necesario si la variante persiste entre sesiones.
A continuación, crearemos un comparador de variantes que permite a la operación de cálculo
SyncInfo comparar los recursos locales con sus variantes. De nuevo, esta operación resulta fácil debido a que la existencia de un estado de historial local implica que el contenido del estado de historial local difiere del contenido actual del archivo. Esto se debe a que la especificación del historial local indica que no creará un estado de historial local si el archivo no ha cambiado.
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;
}
}
Puesto que sabemos que la existencia del estado de historial local implica que es diferente del local, podemos simplemente devolver false al comparar el archivo con su estado de historial local. Asimismo, la sincronización con el historial local es sólo de dos vías, ya que no tenemos acceso a un recurso base y el método de comparación de dos variantes de recurso no se utiliza.
Tenga en cuenta que el cálculo de sincronización no llamará al método compare del comparador si la variante no existe (es decir, es nula). Sólo se llamará si ambos elementos existen. En nuestro ejemplo, esto ocurrirá tanto en los archivos que no tengan un historial local como en todas las carpetas (que nunca tienen historial local). Para tratar esta particularidad, debemos definir nuestra propia subclase de SyncInfo para poder modificar el estado de sincronización calculado para estos casos.
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();
}
}
Hemos alterado temporalmente el constructor para que suministre siempre una base que sea null (ya que sólo utilizamos la comparación de dos vías) y hemos modificado el cálculo de tipo de sincronización para que devuelva IN_SYNC si no existe ningún remoto (ya que sólo nos interesan los casos en los que existe un archivo local y un estado de archivo en el historial local).
A continuación, crearemos un suscriptor que proporcionará acceso a las variantes de recurso del historial local. Dado que el historial local puede guardarse para cualquier archivo del área de trabajo, el suscriptor del historial local supervisará todos los recursos y el conjunto de directorios raíz corresponderá a todos los proyectos del área de trabajo. Tampoco es necesario proporcionar la posibilidad de renovar el suscriptor, ya que el historial local sólo cambia cuando lo hace el contenido de un archivo local. Por tanto, podemos actualizar el estado siempre que se produzca un delta de recursos. Eso deja sólo dos métodos de interés en nuestro suscriptor de historial local: los correspondientes a obtener una SyncInfo y a cruzar el área de trabajo.
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) {
// sólo último estado
variant = new LocalHistoryVariant(states[0]);
}
}
SyncInfo info = new LocalHistorySyncInfo(resource, variant, comparator);
info.init();
return info;
} catch (CoreException e) {
throw TeamException.asTeamException(e);
}
}
El suscriptor devolverá una nueva instancia de SyncInfo que contendrá el estado más reciente del archivo en el historial local. SyncInfo se crea con una variante de historial local para el elemento remoto. En el caso de los proyectos, carpetas y archivos sin historial local, no se suministra ninguna variante de recurso remoto, lo que provocará que el recurso se considere sincronizado debido al método calculateKind de LocalHistorySyncInfo.
El resto del código del suscriptor de historial local es la implementación del método 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);
}
}
El detalle interesante de este método es que devolverá hijos inexistentes si un recurso suprimido tiene historial local. Esto permitirá al suscriptor devolver SyncInfo para los elementos que sólo existen en el historial local y no ya en el área de trabajo.
Hasta ahora, hemos creado las clases que proporcionan acceso a SyncInfo para elementos del historial local. A continuación, crearemos los elementos de UI que nos permitirán tener en la vista Sincronizar una página destinada a visualizar el último estado del historial para todos los elementos del historial local. Puesto que disponemos de un suscriptor, añadirlo a la vista Sincronizar es fácil. Empezaremos por añadir un punto de extensión de participante de sincronización:
<extension point="org.eclipse.team.ui.synchronizeParticipants">
<participant
persistent="false"
icon="synced.png"
class="org.eclipse.team.synchronize.example.LocalHistoryParticipant"
name="Último del historial local"
id="org.eclipse.team.synchronize.example"/>
</extension>
A continuación, debemos implementar el LocalHistoryParticipant. Éste creará una subclase de SubscriberParticipant que proporcionará todo el comportamiento por omisión para recoger información de sincronización (SyncInfo) del suscriptor y actualizar estados de sincronización cuando se produzcan cambios en el área de trabajo. Además, añadiremos una acción para revertir los recursos del área de trabajo al último estado del historial local.
En primer lugar, observemos cómo se añade una acción personalizada al participante.
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("Revertir al último del historial local", configuration) { //$NON-NLS-1$
protected SynchronizeModelOperation getSubscriberOperation(ISynchronizePageConfiguration configuration, IDiffElement[] elements) {
return new RevertAllOperation(configuration, elements);
}
});
}
}
Aquí se añade una SynchronizeMoidelAction específica y una operación. El comportamiento que obtenemos aquí gratuitamente es la capacidad de ejecutar en segundo plano y mostrar el estado de ocupación de los nodos en los que se trabaja. La acción revierte todos los recursos del área de trabajo a su último estado en el historial local. La acción se añade mediante la adición de una contribución de acción a la configuración de participantes. La configuración se utiliza para describir las propiedades utilizadas para construir la página de participante que visualizará la UI de sincronización real.
El participante inicializará la configuración del modo siguiente para añadir el grupo de acciones de historial local al menú de contexto:
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 continuación, observaremos cómo proporcionar una decoración personalizada. La última línea del método anterior registra el siguiente decorador en la configuración de la página.
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;
}
}
El decorador extrae el recurso del elemento model que aparece en la vista Sincronizar y añade el identificador de contenido de la variante de recurso de historial local a la etiqueta de texto que aparece en la vista.
La parte final consiste en suministrar un asistente que creará el participante de historial local. La perspectiva Sincronización de equipo define una acción de sincronización global que permite a los usuarios crear rápidamente una sincronización. Además, la capacidad para crear sincronizaciones está disponible en la barra de herramientas de la vista Sincronizar. Para empezar, vamos a crear un punto de extensión synchronizeWizards:
<extension
point="org.eclipse.team.ui.synchronizeWizards">
<wizard
class="org.eclipse.team.synchronize.example.LocalHistorySynchronizeWizard"
icon="synced.png"
description="Crea una sincronización en el último estado de historial local de todos los recursos del área de trabajo
name="Último de sincronización del historial local"
id="ExampleSynchronizeSupport.wizard1"/>
</extension>
Así se añadirá el asistente a la lista y, en el método finish() de los asistentes, simplemente crearemos nuestro participante y lo añadiremos al gestor de sincronización.
LocalHistoryPartipant participant = new LocalHistoryPartipant();
ISynchronizeManager manager = TeamUI.getSynchronizeManager();
manager.addSynchronizeParticipants(new ISynchronizeParticipant[] {participant});
ISynchronizeView view = manager.showSynchronizeViewInActivePage();
view.display(participant);
Este es un ejemplo sencillo de utilización de las API de sincronización en el que se han descrito algunos de los detalles para facilitar su comprensión.
La escritura de soporte de sincronización exacto y con capacidad de respuesta no es un proceso trivial, cuya parte más ardua es la gestión de la información de sincronización y la notificación de los cambios de estado de la sincronización. La interfaz de usuario, si la que está asociada a SubscriberParticipants es adecuada, es la parte más fácil una vez que la implementación del suscriptor está completa.
Para ver más ejemplos, consulte el conector
org.eclipse.team.example.filesystem y examine las subclases del área de trabajo
de Subscriber y ISynchronizeParticipant.
La próxima sección describe algunas clases e interfaces que pueden ayudarle a escribir un suscriptor desde cero, incluyendo cómo almacenar en antememoria los estados de sincronización entre sesiones del entorno de trabajo.