Exemplo do Histórico Local

A melhor maneira de compreender as APIs de Sincronização consiste em criar um simples exemplo que funcione mesmo. Neste exemplo criamos uma página na vista Sincronizar que apresenta o último estado do histórico local para todos os ficheiros no espaço de trabalho. A sincronização do histórico local actualiza-se automaticamente quando são feitas alterações ao espaço de trabalho e pode abrir-se um editor de comparações para procurar, intercalar as alterações. Também iremos adicionar um decorador personalizado para mostrar a última marca de hora do elemento do histórico local e uma acção para reverter os ficheiros do espaço de trabalho até ao último estado guardado do histórico local. Trata-se de um excelente exemplo porque já temos um arquivo de variantes de recursos disponíveis e não temos de o gerir.

Passamos a ilustrar com um exemplo. Muito do código fonte, mas não todo, será incluído nesta página. O código fonte completo encontra-se no pacote de histórico local do plug-in org.eclipse.team.examples.filesystem. Poderá dar saída ao projecto do repositório CVS e utilizá-lo como referência para ler este guia de iniciação. Renúncia de Responsabilidade: O código fonte nos plug-ins exemplo poderá ser alterado com o tempo. Para obter uma cópia que corresponda à utilizada neste exemplo, poderá dar saída ao projecto com o tag da versão 3.0 (muito provavelmente R3_0) ou um tag de data de 28 de Junho de 2004.

descrição geral do histórico local

Esta captura de ecrã mostra a sincronização do histórico local na vista Sincronizar. Com ela poderá procurar as alterações entre o recurso local e o último estado no histórico. Tem um decorador personalizado para apresentar a marca de hora associada à entrada do histórico local e uma acção personalizada para reverter o ficheiro até ao conteúdo constante do histórico local. Repare também que é utilizada a apresentação da vista Sincronizar padrão, a qual faculta anotações de problemas, disposições de pastas comprimidas e botões de navegação.

Definir as variantes para o histórico local

O primeiro passo consiste em definir uma variante que represente os elementos do histórico local. Assim permite às APIs de sincronização acederem ao conteúdo do histórico local para o comparar com o actual conteúdo e apresentá-lo ao utilizador.

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 a interface IFileState já dá acesso ao conteúdo do ficheiro do histórico local (ou seja, implementa a interface IStorage), foi mesmo fácil. Regra geral, ao criar uma variante é necessário facultar maneira de aceder ao conteúdo, a um identificador de conteúdo que será apresentado ao utilizador para identificar esta variante e um nome. O método asBytes() só é necessário se se quiser tornar a variante persistente entre sessões.

Em seguida, criamos um comparador de variantes que permita ao cálculo SyncInfo comparar recursos locais com as respectivas variantes. Mais uma vez, é fácil porque a existência de um estado de histórico local implica que o conteúdo deste último difere do actual conteúdo do ficheiro. Isto porque a especificação do histórico local indica que não irá criar um estado de histórico local se o ficheiro não tiver sido alterado.

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

Dado que sabemos que a existência de um estado de histórico local implica que este é diferente do local, podemos simplesmente devolver false ao comparar o ficheiro com o seu estado de histórico local. Além disso, a sincronização com o histórico local é somente bidireccional porque não temos acesso a um recurso base, de modo que não é usado o método para comparar duas variantes de recursos.

Repare que o cálculo de sincronização não chama o método de comparação do comparador se a variante não existir (ou seja, se for nula). Só é chamado se ambos os elementos existirem. No nosso exemplo, isto ocorre tanto para ficheiros que não têm um histórico local como para todas as pastas (que nunca têm um histórico local). Para obviar a isto, temos de definir as nossas próprias subclasses de SyncInfo para poder modificar o estado de sincronização calculado destes 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();
  }
}

Sobrepusemos o construtor para facultar sempre uma base que seja null (dado que só usamos comparação bidireccional) e modificámos o cálculo de tipo de sincronização para devolver IN_SYNC se não houver remoto (dado que só nos importam os casos em que há um ficheiro local e um estado de ficheiro no histórico local.

Criar o Subscritor

Agora criamos um Subscritor que faculte acesso às variantes de recursos no histórico local. Dado que o histórico local pode ser guardado relativamente a qualquer ficheiro no espaço de trabalho, o Subscritor de histórico local irá supervisionar cada recurso e o conjunto de raízes será todos os projectos no espaço de trabalho. Além disso, não há necessidade de facultar a capacidade de renovar o subscritor dado que o histórico local só se altera quando o conteúdo de um ficheiro local se altera. Por conseguinte, podemos actualizar o nosso estado sempre que ocorrer um delta de recursos. Isto deixa-nos somente dois métodos interessantes no nosso subscritor de histórico local: obter SyncInfo e atravessar o espaço de trabalho.

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

O Subscritor devolve nova instância SyncInfo que contém o último estado do ficheiro no histórico local. As SyncInfo são criadas com a variante de histórico local para o elemento remoto. No caso de projectos, pastas e ficheiros sem histórico local, não é facultada nenhuma variante de recurso remota, o que faz com que o recurso seja considerado em sincronia devido ao método calculateKind nas nossas LocalHistorySyncInfo.

O código restante no subscritor de histórico local é a implementação do 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);
  }
}

O pormenor interessante deste método é que devolve descendentes não existentes se um recurso eliminado tiver histórico local. Isto permite ao nosso Subscritor devolver SyncInfo para elementos que só existam no histórico local e já não estejam no espaço de trabalho.

Adicionar um Participante na Sincronização do Histórico Local

Até agora criámos as classes que facultam acesso às SyncInfo de elementos no histórico local. Em seguida, criaremos os elementos da UI que nos permitem ter uma página na vista Sincronizar para apresentar o último estado de histórico para cada elemento no histórico local. Dado que temos um Subscritor, é fácil adicionar isto à vista Sincronizar. Comecemos por adicionar um ponto de extensão de participante na sincronização:

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

Em seguida temos de implementar o LocalHistoryParticipant. Irá constituir uma subclasse do SubscriberParticipant que irá facultar todo o comportamento predefinido para recolher SyncInfo junto do subscritor e actualizar estados de sincronização quando ocorrerem alterações ao espaço de trabalho. Além disso, adicionamos uma acção para reverter os recursos de espaço de trabalho até aos mais recentes no histórico local.

Primeiro, vejamos como se adiciona uma acção personalizada ao participante.

public static final String CONTEXT_MENU_CONTRIBUTION_GROUP = "grupo_contexto_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("Reverter até mais recente no histórico local", configuration) { //$NON-NLS-1$
        protected SynchronizeModelOperation getSubscriberOperation(ISynchronizePageConfiguration configuration, IDiffElement[] elements) {
          return new RevertAllOperation(configuration, elements);
        }
      });
  }
}

Aqui adicionamos uma SynchronizeMoidelAction e operação específicas. O comportamento que obtemos aqui é a capacidade de executar em segundo plano e mostrar estados ocupados para nós onde se esteja a trabalhar. A acção reverte todos os recursos no espaço de trabalho até ao seu último estado no histórico local. A acção é adicionada através de um contributo de acção para a configuração dos participantes. A configuração é usada para descrever as propriedades usadas para construir a página do participante que irá apresentar a verdadeira UI de sincronização.

O participante irá inicializar a configuração como se segue, para poder adicionar o grupo de acções do histórico local ao menu 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());
}

Vejamos agora como podemos facultar uma decoração personalizada. A última linha do método supra regista o seguinte decorador junto da configuração da 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;
}
}

O decorador extrai o recurso do elemento modelo que aparece na vista Sincronizar e anexa o identificador de conteúdo da variante de recursos do histórico local à etiqueta de texto que aparece na vista.

A peça final consiste em facultar um assistente que irá criar o participante no histórico local. A perspectiva de Sincronização global define uma acção de sincronização global que permite aos utilizadores criarem rapidamente uma sincronização. Além disso, a capacidade de criar sincronizações está disponível na barra de ferramentas da vista Sincronizar. Para começar, criamos um ponto de extensão synchronizeWizards:

<extension
point="org.eclipse.team.ui.synchronizeWizards">
<wizard
class="org.eclipse.team.synchronize.example.LocalHistorySynchronizeWizard"
icon="synced.png"
description="Cria uma sincronização do último estado do histórico local de todos os recursos no espaço de trabalho
name="Último da Sincronização do Histórico Local"
id="ExampleSynchronizeSupport.wizard1"/>
</extension>

Isto adiciona o nosso assistente à lista e no método finish() dos assistentes criamos simplesmente o nosso participante e adicionamo-lo ao gestor de sincronizações.

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

Conclusão

Trata-se de um simples exemplo da utilização das APIs de sincronização, pelo que saltámos alguns detalhes para tornar o exemplo mais fácil de abarcar. Não é linear compor um suporte de sincronização interactivo e exacto, sendo que a parte mais difícil é a gestão das informações de sincronização e a notificação de alterações ao estado de sincronização. A interface de utilizador, se a que estiver associada aos SubscriberParticipants for suficiente, é a parte fácil assim que estiver concluída a implementação do Subscritor. Para mais exemplos consulte o plug-in org.eclipse.team.example.filesystem e procure as subclasses no espaço de trabalho do Subscritor e ISynchronizeParticipant.

A secção seguinte descreve classes e interfaces que podem ajudar a compor um Subscritor de raiz, incluindo como colocar em memória cache estados de sincronização entre sessões da área de trabalho.