Односторонняя синхронизация каталогов

Дата публикации: 05.03.2011
Дата изменения: 06.03.2011
Состояние: завершена

В этой статье рассматривается, как содержимое одного каталога привести в соответствие другому, исключив из процесса синхронизации (в случае необходимости) ряд подкаталогов и файлов. Эта ситуация схожа с той, когда требуется локальный репозиторий привести в полное соответствие серверному. При этом, зачастую копирование должно производиться не бездумно, а в соответствии с некоторыми правилами (например - не нужно с сервера копировать файлы исходников, а так же обучающее видео). 

Общий алгоритм односторонней синхронизации двух каталогов таков:
  1. Создаётся файл журнала, в котором описывается содержимое серверного каталога
  2. Создаётся файл журнала, в котором описывается содержимое локального каталога
  3. На основании содержимого двух журналов создаётся сценарий обновления локального каталога
  4. Сценарий обновления запускается на исполнение

Все файлы настроек, журналов и сценариев обновлений являются зашифрованными xml-файлами. Это сделано для того, чтобы чрезмерно любопытные пользователи не могли вносить в них изменения. Для того, чтобы просматривать зашифрованные файлы - имеется специальное приложение XML Crypto Editor For AdminCAD. На данный момент выглядит это приложение так (щёлкните мышью по рисунку, чтобы увеличить его):


К сожалению, пока в редакторе зашифрованных xml-файлов не реализована подсветка синтаксиса, нумерация строк и автоматическое сворачивание/разворачивание блоков. Однако для начала сойдёт и такой вариант.

Объяснять лучше на наглядном примере, поэтому мы создадим на локальной машине два каталога: "D:\Локальный каталог" и "D:\Серверный каталог", содержимое которых и будем синхронизировать. Визуально эти каталоги до синхронизации выглядят так  (щёлкните мышью по рисунку, чтобы увеличить его):


Зелёным цветом выделено то, что добавлено в серверный репозиторий, а значит должно появиться и в локальном.
Желным цветом выделено то, что присутствует на сервере, но не должно участвовать в процессе синхронизации согласно правилам, которые будут  указаны нами нами ниже в коде через регулярные выражения.
Синим цветом выделен тот файл, который был изменён, т.е. его хеш не совпадёт с хешем локального аналога, а значит локальный файл нужно обновить.
Красным цветом выделено то, что необходимо удалить из локального репозитория, т.к. оно не имеет никакого аналога на сервере. Т.е. получается, что если в локальный репозиторий юзер добавит какую-нибуть "отсебятину" - она будет безжалостно уничтожена, ибо НЕФИГ - есть офиц. серверный репозиторий. Если хочешь юзать свою библиотеку - отдай её админу CAD - тот посмотрит её и, возможно, добавит в общий репозиторий на сервере, если сочтёт её полезной и безопасной).
 
Исходный код (C# 3.5):

   1:  using System;
   2:  using System.Text;
   3:  using System.Text.RegularExpressions;
   4:  using System.IO;
   5:  using AdminCAD.Configuration.Journaling;
   6:  using AdminCAD.Security.Cryptography;
   7:   
   8:  namespace ConsoleApplication1
   9:  {
  10:      class Program
  11:      {
  12:          static void Main(string[] args)
  13:          {
  14:              //Вектор инициализации
  15:              byte[] iv = new byte[] { 100, 149, 158, 255, 68, 25, 176, 203, 162, 203, 171, 160, 19, 74, 119, 34 };
  16:              //Ключ шифрования/дешифровки
  17:              byte[] key = XSecurity.GetBytesFromFile(@"D:\SERVER\AdminCAD\Settings\Key.key");
  18:              //Кодировка символов
  19:              Encoding encoding = Encoding.UTF8;
  20:              //Правило, согласно которому должны из набора исключаться ненужные каталоги - исключаются те, которые соответствуют правилу.
  21:              string excludeDirRule = @"^(Video|Sources)$";
  22:              //Правило, согласно которому должны из набора исключаться ненужные файлы - исключаются те, которые соответствуют правилу.
  23:              string excludeFileRule = @"\.txt$";
  24:              //Продемонстрируем различные операции: сначала создадим журналы и сценарии, затем запишем их в файлы, а после - прочитаем из файла:
  25:              //Создаём журнал для серверного каталога
  26:              DirectoryJournal dj = new DirectoryJournal(new DirectoryInfo(@"D:\Серверный каталог"), excludeDirRule, excludeFileRule);
  27:              //Имя файла серверного журнала
  28:              string sjName = @"D:\Журналы и сценарии обновлений\Серверный журнал.xdc";
  29:              //Сохраняем журнал в файл
  30:              dj.Save(sjName, key, iv, encoding);
  31:              //Создаём журнал для локального каталога
  32:              dj = new DirectoryJournal(new DirectoryInfo(@"D:\Локальный каталог"), "^$", "^$");
  33:              //Имя файла локального журнала
  34:              string ljName = @"D:\Журналы и сценарии обновлений\Локальный журнал.xdc";
  35:              //Сохраняем журнал в файл
  36:              dj.Save(ljName, key, iv, encoding);
  37:              //Читаем серверный журнал
  38:              DirectoryJournal serverJournal = DirectoryJournal.Load(sjName, key, iv, encoding);
  39:              //Читаем локальный журнал
  40:              DirectoryJournal localJournal = DirectoryJournal.Load(ljName, key, iv, encoding);
  41:              //Имея на руках два журнала, мы можем сравнить их и сгенерировать сценарий односторонней синхронизации (приведение локального каталога в соответствие серверному, с учётом исключения из процесса синхронизации ненужных каталогов и файлов):
  42:              UpdatingScript upScr = new UpdatingScript(serverJournal, localJournal);
  43:              //Имя файла сценария обновлений
  44:              string upScrName = @"D:\Журналы и сценарии обновлений\Сценарий обновления.tsk";
  45:              //Сохраняем сценарий в файл
  46:              upScr.Save(upScrName, key, iv, encoding);
  47:              //Выполняем обратное действие - считываем сценарий обновлений из файла
  48:              upScr = UpdatingScript.Load(upScrName, key, iv, encoding);
  49:              //Запускаем команду синхронизации каталогов
  50:              upScr.Run();
  51:              //Всё, теперь содержимое локального каталога соответствует серверному, с учётом тех исключений, которые мы назначили с помощью регулярных выражений
  52:          }
  53:      }
  54:  }

Далее представлены сгенерированные журналы (серверный и локальный), а так же файл сценария обновлений.

Серверный журнал:

   1:  <DirectoryJournal DateTime="06.03.2011 16:18:35" TargetDir="D:\Серверный каталог" ExcludeDirectoryRule="^(Video|Sources)$" ExcludeFileRule="\.txt$">
   2:    <Directory DirName="Серверный каталог">
   3:      <Directory DirName="Книги">
   4:        <Directory DirName="Компьютерная литература">
   5:          <Directory DirName="Базы данных" />
   6:          <Directory DirName="Операционные системы">
   7:            <File FileName="Linux.accdb" MD5Hashe="09c91d776e3d225c49065d6e526727bc" FileSizeKb="484" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:12:56" />
   8:          </Directory>
   9:          <Directory DirName="Языки программирования" />
  10:        </Directory>
  11:        <Directory DirName="Научная литература" />
  12:        <Directory DirName="Поэзия">
  13:          <File FileName="Есенин.xlsx" MD5Hashe="3cb3ceed24385d6da05adb5a8077c1f1" FileSizeKb="8,65" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:11:12" />
  14:        </Directory>
  15:      </Directory>
  16:      <Directory DirName="Космос" />
  17:      <Directory DirName="Птицы">
  18:        <Directory DirName="Дикая птица">
  19:          <Directory DirName="Дичь">
  20:            <Directory DirName="Гуси" />
  21:            <Directory DirName="Тетерева" />
  22:            <Directory DirName="Утки" />
  23:          </Directory>
  24:          <Directory DirName="Хищные птицы">
  25:            <Directory DirName="Орлы" />
  26:            <Directory DirName="Совы" />
  27:            <Directory DirName="Соколы" />
  28:            <Directory DirName="Ястребы" />
  29:          </Directory>
  30:        </Directory>
  31:        <Directory DirName="Домашняя птица">
  32:          <Directory DirName="Домашние гуси">
  33:            <File FileName="Ландские гуси.xlsx" MD5Hashe="3cb3ceed24385d6da05adb5a8077c1f1" FileSizeKb="8,65" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:08:20" />
  34:            <File FileName="Тузульские гуси.docx" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:08:05" />
  35:          </Directory>
  36:          <Directory DirName="Домашние утки">
  37:            <File FileName="Мускусная утка.pptx" MD5Hashe="9aa6dedaab71b16e0cda8534ed5823e3" FileSizeKb="29,086" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:04:43" />
  38:          </Directory>
  39:          <Directory DirName="Куры">
  40:            <File FileName="Брама.xlsx" MD5Hashe="3cb3ceed24385d6da05adb5a8077c1f1" FileSizeKb="8,65" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:03:37" />
  41:            <File FileName="Корниш.docx" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:03:28" />
  42:          </Directory>
  43:          <File FileName="Новый документ Журнала.jnt" MD5Hashe="4ec402cab77c45fc1b95e1565955e745" FileSizeKb="10,305" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:07:05" />
  44:        </Directory>
  45:        <File FileName="Microsoft Access База данных.accdb" MD5Hashe="09c91d776e3d225c49065d6e526727bc" FileSizeKb="484" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:09:21" />
  46:        <File FileName="Документ Microsoft Publisher.pub" MD5Hashe="0627b4727e2bfe1d1cb7f06b82bfcc5c" FileSizeKb="58,5" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:09:11" />
  47:        <File FileName="Новый точечный рисунок.bmp" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:09:17" />
  48:      </Directory>
  49:      <File FileName="Некий журнал.jnt" MD5Hashe="5b944604be1cc246c826a7f4bd59f4e1" FileSizeKb="4,438" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:13:07" />
  50:    </Directory>
  51:  </DirectoryJournal>

Локальный журнал:

   1:  <DirectoryJournal DateTime="06.03.2011 16:18:35" TargetDir="D:\Локальный каталог" ExcludeDirectoryRule="^$" ExcludeFileRule="^$">
   2:    <Directory DirName="Локальный каталог">
   3:      <Directory DirName="Книги">
   4:        <Directory DirName="Компьютерная литература">
   5:          <Directory DirName="Базы данных" />
   6:          <Directory DirName="Операционные системы">
   7:            <File FileName="Linux.accdb" MD5Hashe="09c91d776e3d225c49065d6e526727bc" FileSizeKb="484" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:12:56" />
   8:            <File FileName="Windows.txt" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:12:46" />
   9:          </Directory>
  10:          <Directory DirName="Языки программирования" />
  11:        </Directory>
  12:        <Directory DirName="Научная литература" />
  13:        <Directory DirName="Поэзия">
  14:          <File FileName="Есенин.xlsx" MD5Hashe="3cb3ceed24385d6da05adb5a8077c1f1" FileSizeKb="8,65" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:11:12" />
  15:          <File FileName="Лермонтов.txt" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:11:19" />
  16:          <File FileName="Пушкин.txt" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:10:42" />
  17:        </Directory>
  18:      </Directory>
  19:      <Directory DirName="Птицы">
  20:        <Directory DirName="Дикая птица">
  21:          <Directory DirName="Дичь">
  22:            <Directory DirName="Гуси" />
  23:            <Directory DirName="Тетерева" />
  24:            <Directory DirName="Утки" />
  25:          </Directory>
  26:          <Directory DirName="Хищные птицы">
  27:            <Directory DirName="Орлы" />
  28:            <Directory DirName="Совы" />
  29:            <Directory DirName="Соколы" />
  30:            <Directory DirName="Ястребы" />
  31:          </Directory>
  32:        </Directory>
  33:        <Directory DirName="Домашняя птица">
  34:          <Directory DirName="Домашние гуси">
  35:            <File FileName="Кубанские гуси.txt" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:17:46" />
  36:            <File FileName="Ландские гуси.xlsx" MD5Hashe="3cb3ceed24385d6da05adb5a8077c1f1" FileSizeKb="8,65" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:08:20" />
  37:            <File FileName="Тузульские гуси.docx" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:08:05" />
  38:            <File FileName="Уральские гуси.docx" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:17:31" />
  39:          </Directory>
  40:          <Directory DirName="Куры">
  41:            <File FileName="Брама.xlsx" MD5Hashe="3cb3ceed24385d6da05adb5a8077c1f1" FileSizeKb="8,65" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:03:37" />
  42:            <File FileName="Бройлеры.txt" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:02:51" />
  43:            <File FileName="Корниш.docx" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:03:28" />
  44:          </Directory>
  45:          <File FileName="ReadMe.txt" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:05:02" />
  46:          <File FileName="Некоторый документ.pptx" MD5Hashe="5a319f3350e051a355a6c36cf1bf4248" FileSizeKb="29,086" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:17:58" />
  47:          <File FileName="Новый документ Журнала.jnt" MD5Hashe="4ec402cab77c45fc1b95e1565955e745" FileSizeKb="10,305" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:07:05" />
  48:        </Directory>
  49:        <File FileName="Microsoft Access База данных.accdb" MD5Hashe="09c91d776e3d225c49065d6e526727bc" FileSizeKb="484" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:09:21" />
  50:        <File FileName="Песни и стихи.txt" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:18:17" />
  51:      </Directory>
  52:      <Directory DirName="Фильмы">
  53:        <Directory DirName="Документальные">
  54:          <File FileName="Документальный фильм 1.txt" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:14:49" />
  55:          <File FileName="Документальный фильм 2.docx" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:15:00" />
  56:          <File FileName="Документальный фильм 3.accdb" MD5Hashe="09c91d776e3d225c49065d6e526727bc" FileSizeKb="484" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:15:18" />
  57:        </Directory>
  58:        <Directory DirName="Художественные">
  59:          <File FileName="Художественный фильм 1.txt" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:15:37" />
  60:          <File FileName="Художественный фильм 2.accdb" MD5Hashe="09c91d776e3d225c49065d6e526727bc" FileSizeKb="484" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:16:28" />
  61:        </Directory>
  62:      </Directory>
  63:      <File FileName="Некий журнал.jnt" MD5Hashe="5b944604be1cc246c826a7f4bd59f4e1" FileSizeKb="4,438" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:13:07" />
  64:      <File FileName="Текст.txt" MD5Hashe="d41d8cd98f00b204e9800998ecf8427e" FileSizeKb="0" CreationTime="06.03.2011 16:18:27" LastWriteTime="05.03.2011 20:13:23" />
  65:    </Directory>
  66:  </DirectoryJournal>

Сценарий обновлений:

   1:  <UpdatingScript SourceDir="D:\Серверный каталог" TargetDir="D:\Локальный каталог" DateTime="06.03.2011 16:23:46" ServerExcludeDirectoryRule="^(Video|Sources)$" LocalExcludeDirectoryRule="^$" ServerExcludeFileRule="\.txt$" LocalExcludeFileRule="^$">
   2:    <!--Каталоги, которые необходимо удалить с локальной машины-->
   3:    <DeletingDirectories>
   4:      <DeletingDirectory>D:\Локальный каталог\Фильмы</DeletingDirectory>
   5:    </DeletingDirectories>
   6:    <!--Новые каталоги, которые необходимо копировать на локальную машину с сервера-->
   7:    <CopyDirectories>
   8:      <CopyDirectory SourceDir="D:\Серверный каталог\Космос" TargetDir="D:\Локальный каталог\Космос" />
   9:      <CopyDirectory SourceDir="D:\Серверный каталог\Птицы\Домашняя птица\Домашние утки" TargetDir="D:\Локальный каталог\Птицы\Домашняя птица\Домашние утки" />
  10:    </CopyDirectories>
  11:    <!--Файлы, которые необходимо удалить с локальной машины-->
  12:    <DeletingFiles>
  13:      <DeletingFile>D:\Локальный каталог\Книги\Компьютерная литература\Операционные системы\Windows.txt</DeletingFile>
  14:      <DeletingFile>D:\Локальный каталог\Книги\Поэзия\Лермонтов.txt</DeletingFile>
  15:      <DeletingFile>D:\Локальный каталог\Книги\Поэзия\Пушкин.txt</DeletingFile>
  16:      <DeletingFile>D:\Локальный каталог\Птицы\Домашняя птица\Домашние гуси\Кубанские гуси.txt</DeletingFile>
  17:      <DeletingFile>D:\Локальный каталог\Птицы\Домашняя птица\Домашние гуси\Уральские гуси.docx</DeletingFile>
  18:      <DeletingFile>D:\Локальный каталог\Птицы\Домашняя птица\Куры\Бройлеры.txt</DeletingFile>
  19:      <DeletingFile>D:\Локальный каталог\Птицы\Домашняя птица\ReadMe.txt</DeletingFile>
  20:      <DeletingFile>D:\Локальный каталог\Птицы\Домашняя птица\Некоторый документ.pptx</DeletingFile>
  21:      <DeletingFile>D:\Локальный каталог\Птицы\Песни и стихи.txt</DeletingFile>
  22:      <DeletingFile>D:\Локальный каталог\Текст.txt</DeletingFile>
  23:    </DeletingFiles>
  24:    <!--Новые файлы, которые необходимо копировать на локальную машину с сервера-->
  25:    <CopyFiles>
  26:      <CopyFile SourceFile="D:\Серверный каталог\Птицы\Документ Microsoft Publisher.pub" TargetFile="D:\Локальный каталог\Птицы\Документ Microsoft Publisher.pub" />
  27:      <CopyFile SourceFile="D:\Серверный каталог\Птицы\Новый точечный рисунок.bmp" TargetFile="D:\Локальный каталог\Птицы\Новый точечный рисунок.bmp" />
  28:    </CopyFiles>
  29:    <!--Существующие на локальной машине файлы, которые необходимо обновить-->
  30:    <UpdateFiles>
  31:      <UpdateFile SourceFile="D:\Серверный каталог\Птицы\Домашняя птица\Домашние гуси\Ландские гуси.xlsx" TargetFile="D:\Локальный каталог\Птицы\Домашняя птица\Домашние гуси\Ландские гуси.xlsx" />
  32:    </UpdateFiles>
  33:  </UpdatingScript>

После работы кода содержимое локального каталога будет выглядеть так  (щёлкните мышью по рисунку, чтобы увеличить его):






Теперь, если мы повторно запустим выше приведённый выше код на исполнение, содержимое файлов серверного и локального журналов уже будет идентичным, а значит содержание сценария обновлений получится таким:

   1:  <UpdatingScript SourceDir="D:\Серверный каталог" TargetDir="D:\Локальный каталог" DateTime="05.03.2011 22:36:24" ServerExcludeDirectoryRule="^(Video|Sources)$" LocalExcludeDirectoryRule="^(Video|Sources)$" ServerExcludeFileRule="\.txt$" LocalExcludeFileRule="\.txt$">
   2:    <!--Каталоги, которые необходимо удалить с локальной машины-->
   3:    <DeletingDirectories />
   4:    <!--Новые каталоги, которые необходимо копировать на локальную машину с сервера-->
   5:    <CopyDirectories />
   6:    <!--Файлы, которые необходимо удалить с локальной машины-->
   7:    <DeletingFiles />
   8:    <!--Новые файлы, которые необходимо копировать на локальную машину с сервера-->
   9:    <CopyFiles />
  10:    <!--Существующие на локальной машине файлы, которые необходимо обновить-->
  11:    <UpdateFiles />
  12:  </UpdatingScript>

Как видим - обновлять больше нечего, т.к. содержимое локального каталога теперь соответствует содержимому серверного (с учётом правил, указанных в регулярных выражениях).


На практике, журналы серверных каталогов будет выполнять администратор CAD, а локальные журналы со сценариями обновлений могут формироваться как по требованию пользователей, так и в автоматическом режиме.

Comments