Этим топиком я хотел бы начать диалог о синхронизации файлов. На Хабре были уже топики и про Дропбокс и про альтернативные сервисы (раз, два). Сервисы отличные, но меня интересовал вопрос: а как же я могу организовать синхронизацию файлов на своем собственном железе? Я начал искать уже готовую программу, но нашел немногое. Похоже, что большинство людей просто вызывают rsync/unison из cron'а. Написание клиент–серверной программы, которая бы отслеживала изменения файлов в папке, казалось делом несложным, и я решил попробовать.
Писать я решил с использованием Qt. Во–первых, богатый набор классов, во–вторых, поддержка разных платформ, а в–третьих — наличие класса QFileSystemWatcher. Именно с помощью этого класса, можно получать уведомления об изменениях в директории. А в качестве «движка» синхронизации был выбран unison. По сути все приложение это всего лишь надстройка над утилитой unison.
Серверная часть заведует пользователями и клиентами. Клиент — это конкретный компьютер пользователя. Например, есть пользователь Вася, а у него есть два компьютера. Эти два компьютера и будут клиентами. Клиенты различаются на основе имени хоста. Для авторизации я использую только имя пользователя. Регистрация клиентов происходит автоматически, если подключившийся клиент передал существующее имя пользователя (авторизацию по паролю собираюсь добавить позже). Информация о пользователях и клиентах хранится в SQLITE базе данных.
Основное предназначение клиентской части — запускать процесс синхронизации в связи с изменениями в наблюдаемой директории. После синхронизации клиент просит сервер уведомить всех остальных клиентов о необходимости синхронизации. Подключенные к серверу клиенты, сразу же получают уведомление, а не подключенные получат его при следующем подключении.
Поскольку основная рабочая машина у меня под управлением Windows, то эксперименты с клиентской частью я начал именно в этой системе. И тут обнаружилась одна особенность Qt. Класс QFileSystemWatcher немного странно, на мой взгляд, следит за директориями. Во–первых, не поддерживаются уведомления об изменениях в поддиректориях, а во–вторых, если изменение произойдет с содержимым какого–нибудь файла, то о нем то же ничего не будет сообщено. Я начал разбираться в реализации класса QFileSystemWatcher, он использует функцию FindFirstChangeNotification:
Сразу видно, что она поддерживает наблюдение за деревом директорий. Конечно же, Qt вызывает эту функцию со значением FALSE для параметра bWatchSubtree. Последний параметры был мной так же изменен, чтобы получать уведомления об изменениях содержимого файлов. Было:
А что под другими системами? В Линуксе Qt использует либо Inotify, либо Dnotify. Они уведомляют об изменениях содержимого файла внутри директории, но так же не работают с поддиректориями. Нужно следить за каждой директорией отдельно. Причина, по которой я полез изучать реализацию класса QFileSystemWatcher, в том, что мне не хотелось следить за каждой директорией в дереве директорий. Но это тема для дискуссий и мне будет интересно услышать соображения Хабровчан на этот счет.
В деталях реализации QFileSystemWatcher'а под Mac OS X я уже не разбирался. Оставил использование оригинального класса. Так что в результате под Windows я использую измененную версию класса, а под всеми остальными системами — оригинальную.
В результате получились две программы Easysync-server и Easysync-client. Я потестировал сервер под Ubuntu, а работу клиентской части проверил под Windows, Ubuntu и Mac OS X. Под Маком мне не удалось установить нужную мне версию Unison в консольном исполнении. Если это сделать, то клиент будет работать и под Маком.
Исходный код выложен на Гитхабе https://github.com/fralik/Easysync под лицензией GPL. Там же более детальные инструкции на англ. по установке программ. Если конкретно вам, Хаброчеловек, хочется, чтобы инструкции были на русском, то вы можете их а) перевести или б) прислать мне личное сообщение. При накоплении таких сообщений я обещаю заняться переводом.
Писать я решил с использованием Qt. Во–первых, богатый набор классов, во–вторых, поддержка разных платформ, а в–третьих — наличие класса QFileSystemWatcher. Именно с помощью этого класса, можно получать уведомления об изменениях в директории. А в качестве «движка» синхронизации был выбран unison. По сути все приложение это всего лишь надстройка над утилитой unison.
Сервер
Серверная часть заведует пользователями и клиентами. Клиент — это конкретный компьютер пользователя. Например, есть пользователь Вася, а у него есть два компьютера. Эти два компьютера и будут клиентами. Клиенты различаются на основе имени хоста. Для авторизации я использую только имя пользователя. Регистрация клиентов происходит автоматически, если подключившийся клиент передал существующее имя пользователя (авторизацию по паролю собираюсь добавить позже). Информация о пользователях и клиентах хранится в SQLITE базе данных.
Клиент
Основное предназначение клиентской части — запускать процесс синхронизации в связи с изменениями в наблюдаемой директории. После синхронизации клиент просит сервер уведомить всех остальных клиентов о необходимости синхронизации. Подключенные к серверу клиенты, сразу же получают уведомление, а не подключенные получат его при следующем подключении.
Поскольку основная рабочая машина у меня под управлением Windows, то эксперименты с клиентской частью я начал именно в этой системе. И тут обнаружилась одна особенность Qt. Класс QFileSystemWatcher немного странно, на мой взгляд, следит за директориями. Во–первых, не поддерживаются уведомления об изменениях в поддиректориях, а во–вторых, если изменение произойдет с содержимым какого–нибудь файла, то о нем то же ничего не будет сообщено. Я начал разбираться в реализации класса QFileSystemWatcher, он использует функцию FindFirstChangeNotification:
HANDLE WINAPI FindFirstChangeNotification(
__in LPCTSTR lpPathName,
__in BOOL bWatchSubtree,
__in DWORD dwNotifyFilter
);
Сразу видно, что она поддерживает наблюдение за деревом директорий. Конечно же, Qt вызывает эту функцию со значением FALSE для параметра bWatchSubtree. Последний параметры был мной так же изменен, чтобы получать уведомления об изменениях содержимого файлов. Было:
const uint flags = isDir
? (FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_FILE_NAME)
: (FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_ATTRIBUTES
| FILE_NOTIFY_CHANGE_SIZE
| FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_SECURITY);
, стало: const uint flags = isDir
? (FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_SIZE)
: (FILE_NOTIFY_CHANGE_DIR_NAME
| FILE_NOTIFY_CHANGE_FILE_NAME
| FILE_NOTIFY_CHANGE_ATTRIBUTES
| FILE_NOTIFY_CHANGE_SIZE
| FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_SECURITY);
А что под другими системами? В Линуксе Qt использует либо Inotify, либо Dnotify. Они уведомляют об изменениях содержимого файла внутри директории, но так же не работают с поддиректориями. Нужно следить за каждой директорией отдельно. Причина, по которой я полез изучать реализацию класса QFileSystemWatcher, в том, что мне не хотелось следить за каждой директорией в дереве директорий. Но это тема для дискуссий и мне будет интересно услышать соображения Хабровчан на этот счет.
В деталях реализации QFileSystemWatcher'а под Mac OS X я уже не разбирался. Оставил использование оригинального класса. Так что в результате под Windows я использую измененную версию класса, а под всеми остальными системами — оригинальную.
Результаты
В результате получились две программы Easysync-server и Easysync-client. Я потестировал сервер под Ubuntu, а работу клиентской части проверил под Windows, Ubuntu и Mac OS X. Под Маком мне не удалось установить нужную мне версию Unison в консольном исполнении. Если это сделать, то клиент будет работать и под Маком.
Исходный код выложен на Гитхабе https://github.com/fralik/Easysync под лицензией GPL. Там же более детальные инструкции на англ. по установке программ. Если конкретно вам, Хаброчеловек, хочется, чтобы инструкции были на русском, то вы можете их а) перевести или б) прислать мне личное сообщение. При накоплении таких сообщений я обещаю заняться переводом.