Синхронизация в стиле Dropbox на вашем собственном сервере

    Этим топиком я хотел бы начать диалог о синхронизации файлов. На Хабре были уже топики и про Дропбокс и про альтернативные сервисы (раз, два). Сервисы отличные, но меня интересовал вопрос: а как же я могу организовать синхронизацию файлов на своем собственном железе? Я начал искать уже готовую программу, но нашел немногое. Похоже, что большинство людей просто вызывают rsync/unison из cron'а. Написание клиент–серверной программы, которая бы отслеживала изменения файлов в папке, казалось делом несложным, и я решил попробовать.

    Писать я решил с использованием 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. Там же более детальные инструкции на англ. по установке программ. Если конкретно вам, Хаброчеловек, хочется, чтобы инструкции были на русском, то вы можете их а) перевести или б) прислать мне личное сообщение. При накоплении таких сообщений я обещаю заняться переводом.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 18

      0
      Весьма и весьма любопытно.
        +1
        Спасибо, мне и самому интересно, что из этого получится.
        0
        >мне не хотелось следить за каждой директорией в дереве директорий

        Смотрите, у вас есть есть очень разветвленная структура папок. Половина из них имеет тяжелое содержимое, которое вы не хотите синхронизировать. Структуру папок менять тоже не хотите. Мне кажется удобнее, отлавливать изменения для каждой конкретной директории.

        В том же Dropbox это нужно настраивать отдельно. И не особо удобно.

          0
          Ага, вот как раз сейчас думаю об этом. В этом есть смысл. Selective sync в Dropbox просто ужасен — у меня он начал удалять содержимое папок, которые я убрал из синхронизации.
          Когда спадет волна эйфории по поводу ковыряния в недрах Qt, думаю, что я сделаю поддержку синхронизации нескольких папок, а значит и выборочную синхронизацию.
            0
            Согласитесь, QT — прекрасна!!! Даже если ее очень глубоко ковырять.
          0
          Другой пример (наверно, не по теме уже) — вам нужно синхронизировать папки которые, не являются поддиректориями одной и той же папки. В Dropbox так нельзя и это сильно расстраивает.
            0
            Согласен. Сейчас у меня все завязано на Unison и его профиль. Надо посмотреть может ли он синхронизировать сразу из нескольких папок. Хотя никто не мешает сделать более глубокую интеграцию с Unison'ом или использовать что–нибудь другое. Rsync, например.
              0
              А почему именно Unison?
              На вики пишут, что разработчики его более не развивают и переключились на Harmony.
                +1
                Я сейчас как раз обдумываю оставаться ли с Unison'ом или использовать что–нибудь другое. Rsync кажется возможной заменой, но он не знает ничего про конфликты. Последний релиз rsync'а был примерно тогда же, когда и последний релиз Unison'а.

                К минусам Unison'а я бы отнес его требовательность к версиям программы и то, что он написан на OCaml.
                Плюсом для меня была простота использования и кросс–платформенность.

                В идеале хотелось бы, чтобы была кросс–платформенная библиотека, которая умела бы синхронизировать папки через SSH. Но похоже, что такую библиотеку нужно писать самому.
            0
            Маленький вопрос, если я вашу разработку начну использовать прямо сейчас для рабочих проектов — мне это ничем не будет грозить?

            Сервер: Gentoo местной сборки. Клиента 2: Windows, Kubuntu.
              0
              Нет, не будет. В плане надежности синхронизации все дело за unison. Он разрабатывается уже давно и стабилен. Но я не тестировал сервер именно под Gentoo. Будет здорово, если вы потом поделитесь опытом установки.
              0
              Отлично. Огромное спасибо. Я ждал, что кто-то, да сделает открытый аналог дропбокса.
              Форкнул ваш проект, появится время — буду пилить под себя )
                0
                Надеюсь, что время появится. А так на вскидку: что бы хотели допилить?
                  0
                  Ну, как минимум, большей настраиваемости… Папок, сетевых настроек, прокси, файлы исключения… придумать можно много всего )
                0
                А вот кстати, интересный вопрос.
                Что делать, когда все место на сервере кончилось?
                Выкидывать старое или не добавлять новое?
                Или как-то выставить приоритет для файлов?

                Например, как я понял, по горькому опыту, Dropbox выкидывает.
                Но по какому принципу, я не осознал. Например, архивы он не тронул,
                а стилевые .sty файлы куда-то пропали. Причем, тоже выборочно.
                  0
                  Это зависит от реализации «движка синхронизации». Только что проверил, что делает Unison. Он сообщает, что синхронизация не прошла. При этом забивает доступное место настолько, насколько может. Например, не помещается у меня уже mp3 файл размером 4 мегабайта, но 2 еще войдут. Тогда после синхронизации, в папке–получателе будет файли с именем .unison.<file-name>..unison.tmp размером 2 мегабайта.
                  0
                  И еще мучает, вопрос. Как в таких случаях правильно поступать с симлинками и хардлинками. Допустим если оба воплощения хардлинка хотим синхронизировать, но в разных директориях. В идеале, надо на сервере тоже хранить как хардлинк, но по-моему это ненужная головная боль.

                  С симлинками аналогично. Хотя, самое интересное начинается, наверно начинается как раз тут. Особенно занятно, как завязывать ntfs-link и никсовый.
                    0
                    Хардлинки хочется обрабатывать в смысле уменьшения пространства для хранения? Тогда имеет смысл хранить на сервере в файловых системах типа lessfs или Opendedup, хранящие одну копию дублирующихся данных.

                  Only users with full accounts can post comments. Log in, please.