Uczestnictwo w zapisywaniu obszaru roboczego

Proces zapisywania obszaru roboczego jest wyzwalany okresowo przez platformę oraz w momencie zamykania środowiska roboczego przez użytkownika. Moduły dodatkowe mogą uczestniczyć w procesie zapisywania obszaru roboczego, aby dane krytyczne modułu dodatkowego były zapisywane na dysku razem z resztą trwałych danych obszaru roboczego.

Proces zapisywania obszaru roboczego może być używany do śledzenia zmian pojawiających się między aktywacjami danego modułu dodatkowego.

Implementowanie uczestnika procesu zapisywania

Aby uczestniczyć w procesie zapisywania obszaru roboczego, należy dodać do obszaru roboczego uczestnika procesu zapisywania. Ta czynność jest zwykle wykonywana w trakcie działania metody uruchamiania modułu dodatkowego. W tym czasie odczytywany jest również stan, który mógł zostać zapisany w momencie ostatniego zamykania modułu dodatkowego.

Przyjrzyjmy się prostemu modułowi dodatkowemu, który demonstruje proces zapisywania.

   package com.example.saveparticipant;

   import org.eclipse.core.runtime.*;
   import org.eclipse.core.resources.*;
   import java.io.File;
   import java.util.*;

   public class MyPlugin extends Plugin {
      private static MyPlugin plugin;

      public MyPlugin(IPluginDescriptor descriptor) {
         super(descriptor);
         plugin = this;
      }

      public static MyPlugin getDefault() {
         return plugin;
      }

      protected void readStateFrom(File target) {
      }

      public void startup() throws CoreException {
         super.startup();
   ISaveParticipant saveParticipant = new MyWorkspaceSaveParticipant();
   ISavedState lastState =
            ResourcesPlugin.getWorkspace().addSaveParticipant(this, saveParticipant);
         if (lastState == null)
            return;
         IPath location = lastState.lookup(new Path("save"));
         if (location == null)
            return;
      // Instancja modułu dodatkowego powinna odczytać wszystkie ważne stany z pliku.
         File f = getStateLocation().append(location).toFile();
         readStateFrom(f);
      }

      protected void writeImportantState(File target) {
      }
   }

Interfejs ISaveParticipant definiuje protokół dla uczestnika procesu zapisywania. Obiekty implementujące ten interfejs mogą udostępniać zachowania dla różnych etapów procesu zapisywania. Przyjrzyjmy się poszczególnym etapom i sposobowi, w jaki klasa WorkspaceSaveParticipant implementuje każdy z tych kroków.

      public void prepareToSave(ISaveContext context) throws CoreException {
      }
   public void saving(ISaveContext context) throws CoreException {
         switch (context.getKind()) {
            case ISaveContext.FULL_SAVE:
         MyPlugin myPluginInstance = MyPlugin.getDefault();
               // Zapisywanie stanu modułu dodatkowego.
               int saveNumber = context.getSaveNumber();
         String saveFileName = "save-" + Integer.toString(saveNumber);
      File f = myPluginInstance.getStateLocation().append(saveFileName).toFile();
               // jeśli zapisywanie nie powiedzie się, zostanie zgłoszony wyjątek i ścieżka nie zostanie zaktualizowana
               myPluginInstance.writeImportantState(f);
               context.map(new Path("save"), new Path(saveFileName));
               context.needSaveNumber();
      break;
            case ISaveContext.PROJECT_SAVE:
               // Pobieranie projektu dotyczącego tej operacji zapisywania.
               IProject project = context.getProject();
               // Zapisywanie niezbędnych informacji.
      break;
            case ISaveContext.SNAPSHOT:
               // Ta operacja musi zostać wykonana bardzo szybko, ponieważ
               // obszar roboczy może żądać obrazu stanu
               // bardzo często.
      break;
         }
      }

Interfejs ISaveContext opisuje informacje dotyczące operacji zapisywania. Występują trzy rodzaje operacji zapisywania: FULL_SAVE, SNAPSHOT i PROJECT_SAVE. Uczestnicy procesu zapisywania powinni ostrożnie wykonywać przetwarzanie odpowiednio do otrzymanego zdarzenia związanego z zapisywaniem. Na przykład zdarzenia związane z zapisywaniem stanu (SNAPSHOT) mogą się pojawiać dosyć często i mają umożliwiać modułom dodatkowym zapisywanie ich krytycznego stanu. Przeznaczenie długiego okresu czasu na zapisanie stanu, który może zostać ponownie ustalony po wystąpieniu awarii, spowolni pracę platformy.

Nazwy kolejnych plików zapisu danych są tworzone przy użyciu kolejnych numerów zapisu (save-1, save-2 itd.). Każdy plik zapisu jest odwzorowywany na logiczną nazwę pliku (save), która jest niezależna od numeru zapisu. Dane modułu dodatkowego są zapisywane w odpowiednim pliku i mogą zostać później pobrane bez znajomości konkretnego numeru ostatniej pomyślnej operacji zapisywania. Przypomnijmy, że już widzieliśmy tę technikę w kodzie startowym modułu dodatkowego:

IPath location = lastState.lookup(new Path("save"));

Po zapisaniu danych i odwzorowaniu nazwy pliku, wywołujemy metodę needSaveNumber, aby wskazać, że aktywnie uczestniczyliśmy w procesie zapisywania obszaru roboczego i chcemy przypisać numer do czynności zapisywania. Numery zapisu mogą być używane do tworzenia plików danych, jak opisano powyżej. 

      public void doneSaving(ISaveContext context) {
         MyPlugin myPluginInstance = MyPlugin.getDefault();

         // Usuwanie niepotrzebnego starego zapisanego stanu.
         int previousSaveNumber = context.getPreviousSaveNumber();
         String oldFileName = "save-" + Integer.toString(previousSaveNumber);
         File f = myPluginInstance.getStateLocation().append(oldFileName).toFile();
         f.delete();
      }

W tym miejscu czyszczone są informacje z poprzedniej operacji zapisu. Aby uzyskać numer zapisu przypisany do poprzedniej operacji zapisu (nie do tej dopiero zakończonej), używana jest metoda getPreviousSaveNumber. Ten numer jest używany do skonstruowania nazwy pliku, który ma zostać usunięty. Należy zauważyć, że ponieważ numer bieżącego pliku zapisu został właśnie odwzorowany, nie jest używane logiczne odwzorowanie pliku stanu zapisu. 

      public void rollback(ISaveContext context) {
         MyPlugin myPluginInstance = MyPlugin.getDefault();

         // Ponieważ operacja zapisywania nie powiodła się, zapis stanu zostaje usunięty
         int saveNumber = context.getSaveNumber();
         String saveFileName = "save-" + Integer.toString(saveNumber);
      File f = myPluginInstance.getStateLocation().append(saveFileName).toFile();
         f.delete();
      }

W tym miejscu usuwany jest stan zapisany przed chwilą. Należy zauważyć, że do skonstruowania nazwy zapisanego przed chwilą pliku używany jest bieżący numer zapisu. Nie należy się martwić o odwzorowanie nazwy tego pliku na interfejs ISaveContext. Platforma usunie kontekst w przypadku niepowodzenia operacji zapisu.

Jeśli moduł dodatkowy zgłosi wyjątek w dowolnym momencie procesu zapisywania, zostanie usunięty z operacji zapisu i żadna z pozostałych metod cyklu zapisywania nie zostanie pobrana. Jeśli na przykład metoda saving nie powiedzie się, nie zostanie wysłany ani komunikat rollback, ani doneSaving

Używanie poprzednio zapisanego stanu

Jeśli do obszaru roboczego zostanie dodany uczestnik procesu zapisywania, zwrócony zostanie obiekt ISavedState opisujący elementy modułu dodatkowego zapisane w trakcie ostatniej operacji zapisywania (lub wartość null, jeśli moduł dodatkowy nie zapisał poprzednio żadnego stanu). Tego obiektu można użyć do uzyskania dostępu do informacji z pliku poprzedniego zapisu (przy użyciu numeru zapisu i odwzorowania pliku) lub do przetworzenia zmian, które pojawiły się między aktywacjami modułu dodatkowego.

Uzyskiwanie dostępu do zapisanych plików

Jeśli do zapisania plików nazwanych logicznie według numeru zapisu zostało użyte odwzorowanie pliku, to samo odwzorowanie może zostać użyte do wydobycia danych z ostatniego znanego zapisanego stanu.

   ISaveParticipant saveParticipant = new MyWorkspaceSaveParticipant();
   ISavedState lastState =
      ResourcesPlugin.getWorkspace().addSaveParticipant(myPluginInstance, saveParticipant);

   if (lastState != null) {
      String saveFileName = lastState.lookup(new Path("save")).toString();
      File f = myPluginInstance.getStateLocation().append(saveFileName).toFile();
      // Instancja modułu dodatkowego powinna odczytać wszystkie ważne stany z pliku.
      myPluginInstance.readStateFrom(f);
   }

Przetwarzanie delt zasobów między aktywacjami

Należy pamiętać, że przed aktywowaniem modułu dodatkowego w obszarze roboczym mogło wystąpić wiele zdarzeń związanych ze zmianami zasobów. Aby dowiedzieć się, jakie zmiany zaszły od czasu dezaktywacji modułu dodatkowego, można użyć mechanizmu zapisywania, nawet jeśli nie ma potrzeby zapisywania żadnych danych.

Uczestnik musi zażądać, aby platforma przechowywała delty zasobów zamiast niego. Ta czynność jest wykonywana jako część operacji zapisywania.

   public void saving(ISaveContext context) throws CoreException {
      // Nie ma stanu do zapisania przez moduł dodatkowy, ale jest żądanie
      // użycia delt zasobów przy kolejnej aktywacji.
      context.needDelta();
   }

W trakcie uruchamiania modułu dodatkowego można uzyskać dostęp do poprzednio zapisanego stanu i dla wszystkich zmian dokonanych od czasu ostatniego zapisu zostaną utworzone zdarzenia.

   ISaveParticipant saveParticipant = new MyWorkspaceSaveParticipant();
   ISavedState lastState =
      ResourcesPlugin.getWorkspace().addSaveParticipant(myPluginInstance, saveParticipant);
   if (lastState != null) {
      lastState.processResourceChangeEvents(new MyResourceChangeReporter());
   }

Zgodnie z opisem w sekcji Śledzenie zmian zasobów dostarczona klasa musi implementować interfejs IResourceChangeListener. Zmiany dokonane od czasu ostatniego zapisu są zgłaszane jako część zdarzenia związanego ze zmianą zasobu POST_AUTO_BUILD.

Uwaga:  Zmiany znaczników nie są zgłaszane w zdarzeniach zmian przechowywanych w interfejsie ISavedState. Należy zakładać, że od czasu ostatniego zapisu stanu wszystkie znaczniki uległy zmianie.