Абсурдная идея

Если создать в интернете базу данных всех существующих файлов, то любой архив будет представлять собой список имен каталогов, файлов, дата+время изменения/создания и хеши этих файлов. А при распаковке архива достаточно будет просто скачать из интернета содержимое файла по его хешу, записать на диск и присвоить атрибуты (дата,время). Т.е. даже архив с полным сезоном какого-нибудь сериала из 20 серий будет занимать не больше нескольких килобайт (в независимости от качества видео).

Всем понятно, что создать базу хешей всех файлов просто физически невозможно. И даже не потому что для этого требуется большой объем для хранения файлов, а потому что новые файлы появляются каждую секунду. И такая online база данных просто не будет успевать сохранять огромный массив данных.

Уменьшение абсурда

Упростим условия и будем ограничится не всеми файлами, а только какой-то группой файлов. В этом случае скорость появления новых файлов будет не такой огромной и объем данных также будет в разумных пределах.

И самое очевидное применение такого подхода - это система контроля версий  файлов. Т.е. мы будем ограничиваться только файлами одного проекта.

Небольшое лирическое отступление

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

Формализация задачи

Сохранение всех элементов (файлов, каталогов, ссылок) папки проекта с возможностью вернуться к любому сохраненному состоянию.

  • Проект - папка(директория) с элементами. Элементом может быть

    • Файл

    • Директория

    • Символьная ссылка (если она указывает на элемент внутри проекта)

  • Состояние проекта - список всех имен элементов проекта +  их состояния.

  • Состояние элемента определяется как

    • Файл - имя(путь внутри проекта)+хеш файла + размер + дата/время последнего изменения

    • Директория - имя(путь внутри проекта)

    • Символьная ссылка - имя(путь внутри проекта) + относительный путь (относительно базового пути проекта) на ссылающийся элемент

  • ХЕШ файла = хеш (SHA-3+crc32) данных файла + размер файла

  • Трансформация - список команд (добавление, удаление), которые переводят проект из предыдущего состояние в следующее.

  • Команда трансформации - состоит из

    • Тип команды

      • D Добавление элемента

      • R Удаление элемента

    • Имя элемента

    • Данные команды (только для команды Добавления)

      • Добавление каталога - нет данных

      • Добавление ссылки

        • LINK - ссылка на элемент

      • Добавление файла

        • HASH - хеш данных файла

        • SIZE - размер

        • TIME - дата/время изменения

Описание процесса сохранения на сервер

Первоначально папка проекта находится в состоянии "пусто". Трансформация 1 переводит её в Состояние 1. В трансформации 1 две команды, которые добавляют два файла. Затем трансформация 2 переводит проект в Состояние 2. Во второй трансформации четыре команды: удаление file1.txt, удалить file2.txt, добавить file2.txt, добавление file3.txt

Схема процесса сохранения:

  1. Сканируем папку проекта и получаем список состояний всех локальных элементов.

  2. Запрашиваем с сервера все трансформации проекта.

  3. Выполняем все трансформации и получаем состояние проекта на сервере.

  4. Определяем трансформацию со списком команд, которые переводят проект из состояние на сервере в локальное состояние.

  5. Отправляем на сервер все новые файлы.

  6. Отправляем на сервер новую трансформацию.

Процесс восстановления аналогичен, только трансформация со списком команд строится для перевода из состояния сервера в локальное состояние.

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

Сервер хранения

Сервер хранения будет выступать только как хранилище файлов. Использовать можно FTP, HTTP или облачное API. В простейшем случае в качестве сервера хранилища может выступать каталог на локальном компьютере или сетевом диске.

Файловая структура хранилища:

  • files - все файлы хранилища. Имя файла это хеш файла.

  • projects - проекты

    • имя проекта

      • NN трансформации - номер трансформации. От 1 до 4.294.967.295

Алгоритм запроса с сервера состояний элементов (т.е. всех трансформаций проекта):

  1. Читаем в корне директории проекта файл `shasoft-vcs.json` и из него получаем имя проекта.

  2. Номер трансформации NN = 1.

  3. Проверяем на сервере наличие трансформации NN в файле `projects/<хеш имени проекта>/NN`. Если нет то КОНЕЦ.

  4. Читаем с сервера трансформацию NN.

  5. NN = NN + 1.

  6. Идти к пп.3.

При сохранении новых трансформаций определяем последний номер трансформации и сохраняем новые трансформации под следующем номером на сервер.

Для уменьшения размера будем упаковывать список трансформаций (и файлы) в архив GZIP. Как вариант, можно прикрутить и другой формат упаковки. Однако реализация GZIP у меня есть + этот формат может распаковывать PHP. Что удобно если я всё-таки сделаю HTTP сервер аля github.

Реализация работы с хранилищем

Адрес сервера хранения задаётся в виде: <тип хранилища>://<параметры хранилища>

Функции работы с хранилищем располагаются в dll с именем storage-<тип хранилища>. Динамическая библиотека должна реализовать следующие функции:

  • int32 storage_connect(const byte *url) - установить соединение

    • Возвращает идентификатор соединения или 0 в случае ошибки

  • void storage_disconnect(int32 idConnect) - разорвать соединение

  • int32 storage_upload(int32 idConnect, const byte *filepathFrom, const byte *filenameTo) - записать файл в хранилище

    • Возвращает статус выполнения операции

      • 0 - Ok

      • 1 - Ошибка записи, так как файл с таким именем уже существует

      • 99 - Ошибка записи

  • int32 storage_download(int32 idConnect, const byte *filenameFrom, const byte *filepathTo) - получить файл из хранилища

    • Возвращает статус выполнения операции

      • 0 - Ok

      • 1 - Ошибка чтения, так как файла с таким именем не существует

      • 99 - Ошибка чтения

  • int32 storage_file_exists(int32 idConnect, const byte *filename) - проверить существование файла

    • Возвращает статус выполнения операции

    • 0 - файл НЕ существует

    • 1 - файл существует

    • 99 - Ошибка

Имена файлов передаются в виде utf-8 строки с символом \0 в конце.
На текущий момент поддерживается два типа хранилища:

  • fs - файловая система. Пример адреса `fs://<путь до каталога хранилища>`

  • ftp - работа с FTP. Пример адреса `ftp//:[[<логин>[:<пароль>]@]<хост>[:<порт>]][/<папка на сервере>]`

Безопасность

Слабая сторона данной схемы - безопасность. В общем случае нет возможности на сервере проверить имя пользователя (потому что сервер выступает как файловое хранилище и не может выполнять функции авторизации). А значит любой, кто знает параметры доступа к файловому серверу может удалить или изменить там данные. Как вариант решения - отключить эту возможность, оставив только возможность добавления файлов. В этом случае вредитель не сможет удалить или изменить данные. Но сможет добавить некоректные значения. Что тоже не очень хорошо.

Вариант решения тут только один - сделать специальный файловый сервер (к примеру HTTP/HTTPS) для работы с системой контроля версий который будет проверять авторизацию пользователя и не давать выполнять какие-то действия, если у пользователя нет на это прав.

Справка по работе с программой

Shasoft VCS version 1.0.0-beta.15
Usage: shasoft-vcs.exe -m -s [-l] [-n] [--nosave] folder
Positional arguments:
  folder Project folder
Optional arguments:
  -m, --mode    Working mode
    backup  Save data to storage
    restore Restore data from storage
  -s, --storage Link to storage/Storage ID
  -l, --log     Display log on screen
  -n, --nn      Recovery Version ID
  --nosave      Without saving the result
Storage ID settings file:
  <...>/AppData/Roaming/Shasoft VCS/storages.json
  • folder - папка проекта. В указанной папке ищется файл `shasoft-vcs.json` с именем проекта.

{
  "name" : "<имя проекта в формате /[a-zA-Z0-9_@$\\-]{1,}\\/[a-zA-Z0-9_@$\\-]{1,}/>"
}

В случае отсутствия поиск продолжается в родительской папке. И так до самого верхнего уровня. Если файл с именем проекта не найден, то ошибка. Если найден, то происходит сохранение в найденный проект. При этом сохраняется только указанная папка, а не весь проект. Т.е. если файл проекта находится в `c:/myproject/shasoft-vcs.json` и указана папка `c:/myproject/aaa/bbb`, то сохранится/восстановится только папка проекта `aaa/bbb`.

  • -m, --mode - режим работы. Два значения

    • backup - сохранение

    • restore - восстановление

  • -s, --storage - адрес/псевдоним хранилища.

  • -n, --nn - номер версии для восстановления

  • --save - без внесения изменений. Режим добавлен для тестирования системы игнорирования элементов проекта через файл .gitignore

Внизу указывается расположение файла с соответствиями псевдонима хранилища его адресу в формате

{
  "<псевдоним 1>" : "<адрес 1>",
  "<псевдоним 1>" : "<адрес 1>",
  ...
}

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

Планы

1. Добавить шифрование. Это позволит создавать хранилища даже в облаке без опасения что данные "утекут"

2. Собрать программу под linux. Для этого проект был переведен на cmake. Установил VSCode в Simply Linux. Нужно разбираться дальше.

Ссылка на программу

Комментарии (13)


  1. Deosis
    09.12.2022 06:40
    +14

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

    Похоже на торренты.


  1. maeris
    09.12.2022 06:58
    +4

    Похоже на IPFS.


    1. maeris
      09.12.2022 07:03
      -1

      Я бы, впрочем, предложил написать не на С++ (правильно же угадал?), а на каком-нибудь более безопасном языке, чтобы оно легче читалось и поддерживалось. Кстати, а исходники вы из каких-то побуждений не выкладываете на github?


      1. shasoftX Автор
        09.12.2022 07:14

        Чтобы выкладывать исходники, нужно их "причесать". Иначе это будет просто куча непонятно чего. Пока руки до этого не дошли.

         на каком-нибудь более безопасном языке,

        Вообще идеи такие были на Go или Rust переписать. Заодно и потренироваться. Но тут опять нужно время + понимание что же более безопасно.

        чтобы оно легче читалось и поддерживалось

        C++ более распространен, так что большинству разработчиков его читать и поддерживать проще,


        1. domix32
          09.12.2022 11:27

          Читать код меньшая из проблем. Да и лаконичный код намного понятнее пелёнок с итераторами и прочими. Или вы сразу на 20х стандартах пишите?


          1. shasoftX Автор
            09.12.2022 11:35

            Да, судя по настройке в cmake

            set(CMAKE_CXX_STANDARD 20)

            Но это для скорости написания. Скорее всего можно и уменьшить версию, переписав какой-то код. Это я себя уже по рукам стал бить и принял волевое решение что 20x это нормально и на текущий момент поддерживать более старые стандарты не к чему.


  1. ri1wing
    09.12.2022 09:13

    Я правильно понял, что если я отредактирую файл сто раз, у меня будет сто трансформаций вида "Удалить file1.txt Добавить file1.txt" и на сервере будет храниться сто самостоятельных версий file1.txt? Но при этом, если я захочу восстановить версию 99й итерации, я не могу её просто скачать, а мне нужно будет сначала последовательно добавить-удалить предыдущие 98?


    1. shasoftX Автор
      09.12.2022 09:18

      Если в результате редактирований файл будет иметь 100 разных содержимых, то да, на сервере будет 100 файлов. ,Что вполне понятно: если у вас 100 разных файлов и вы хотите иметь возможность получить любой из них, то все они должны хранится на сервере.

      если я захочу восстановить версию 99й итерации

      Нет. Трансформации выполняются в памяти и скачивается только итоговый вариант файла.

      Т.е. у вас 100 разных файлов и 100 трансформаций (200 команд =100 удалений + 100 добавлений). Трансформации будут выполнены, будет определено что нужен файл № 100 и скачен будет только он. Все остальные 99 файлов скачиваться не будут. Потому что это файл с одним и тем же именем. И в проекте он будет только один. А значит нет смысла качать 99 его исторических копий, которые нам не нужны.


      1. ri1wing
        09.12.2022 09:41

        Нет. Трансформации выполняются в памяти и скачивается только итоговый вариант файла. <...>
        А значит нет смысла качать 99 его исторических копий, которые нам не нужны.

        Тогда следующий абзац ввёл меня в заблуждение.

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


        1. shasoftX Автор
          09.12.2022 09:50

          Понял. Да, сами трансформации придется качать все. Иначе не ясно какой файл в последней итерации.

          У меня было два варианта:

          1. Хранить все состояния в каждой трансформации

          2. Хранить в трансформации только изменения

          Я выбрал вариант № 2 потому что в 1-ом слишком много избыточных данных. А так как размер самих трансформаций небольшой (+они кэшируются в папке проекта), то скорость не падает.


  1. firegurafiku
    10.12.2022 03:53

    ХЕШ файла = хеш (SHA-3+crc32) данных файла + размер файла

    Удивительное сочетание алгоритмов! Если у вас уже есть криптостойкая контрольная сумма, зачем добавлять к ней CRC-32? Кстати, а почему SHA-3?


    1. shasoftX Автор
      10.12.2022 08:26

      зачем добавлять к ней CRC-32

      На "всякий случай"

      Кстати, а почему SHA-3

      Почитал, пишут что он более надёжен чем sha-2


  1. nixtonixto
    10.12.2022 09:13

    Очень похоже на инкрементальный бэкап — таких программ мне известно десятки (на Windows), с сохранением куда угодно — от WebDAV/FTP до облаков, с сжатием и шифрованием, платных и бесплатных.