Pull to refresh

Автоматическое обновление ПО или круги оптимизации

Когда наш разрабатываемый продукт вышел на тропу коммерческого релиза, мы задумались над алгоритмом его автоматического обновления. Как оказалось, дело это весьма творческое и индивидуальное для каждого ПО. Придумав и отыскав некоторые решения, мы столкнусь с некоторыми вопросами и проблемами. О том, как мы поступили, читайте под катом…
Автоматическое обновление


Что имеем:


Структура ПО

Рисунок 1

• коммерческое десктопное Windows приложение app.exe, написанное на Delphi, которому требуется модуль автоматического обновления;
• модуль автоматического обновления* updater.exe, который и планируем написать;
• файлы ПО (библиотеки, драйвера и т.д);
• пользовательские данные (личные настройки, некий контент личного кабинета и прочее).

*Модуль обновления – обычно это, по сути, отдельное приложение помимо основного ПО, которое и производит скачивание, обновление + где-то существует серверная его часть (упаковщик), которая собирает и отдает пакеты обновлений. Не сам же себя человек оперирует.

На первый взгляд все довольно элементарно и, казалось бы, очевидно:
image

Рисунок 2
image

Рисунок 3

• приложение запускается;
• модуль обновления спрашивает, есть ли новая версия ПО;
• если да, то скачивает его;
• модуль обновления подменяет файлы и запускает само ПО.

После того, как эта схема выстроилась у нас в головах, мы задумались над некоторыми важными вопросами.

Все или ничего?


В зависимости от архитектуры приложения можно выделить два подхода:
image

Рисунок 4

Обновлять все. Так обновляются мобильные приложения. Есть папка build, и отдельно есть папка с пользовательскими данными (user data). Делается бэкап, а далее, без особых размышлений, удаляется весь билд и накатывается новый. Это оправдано, когда размер билда не такой большой и когда пользовательские данные и данные программы четко разграничены.

Обновлять модули. Иногда архитектура приложения (по большей степени файловая) позволяет выделить некие модули. Тогда размышлений нужно побольше, но и каждый апдейт будет легче и целенаправленней что ли.

Конечно, мы, как «истинные про», не искали легких путей, и пошли путем оптимизаторов – решили обновлять модули. К тому же наша архитектура файлов вобрала и персональные данные и программные данные, которые уже сложно отделить на данном этапе разработки (говорю же — «истинные про»). Поэтому просто все удалить и залить обновленные файлы не получится.

Но как же наш модуль обновления будет понимать, что ему подменять, а что оставить в покое? Решили, что будем кормить ему некий список указаний (скрипт/to-do-list), по которому он и будет действовать. Недолго думая (по-моему, это ключевая фраза), разработали свой протокол скрипта обновления:

Файл update.script состоит из набора строк типа:
name: Имя объекта |type: Тип объекта |action: Действие над объектом ~

Например:
name:temp|type:dir|action:delete~

Type может принимать значения:
• dir – директория;
• file – файл.
Action может принимать значения:
• delete – удалить;
• create – создать;
• update – обновить.

По этому скрипту модуль обновления понимает, что ему удалить, что обновить, а что добавить.

Но как быть с непоследовательным обновлением?


В примененном нами механизме получается, что если пользователь обновляется с версии например 1.0.0 до версии 1.2.0, пропустив все промежуточные обновления, то он рискует остаться с нерабочим ПО. Допустим, вдруг мы в версии 1.0.1 добавили файлик, который в версии 1.2.0 просто необходим.

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

Рассмотрим один файл по имени temp, с которым в каждой версии мы что-то делаем, тогда скрипты обновления будут выглядеть например так:
image

Рисунок 5

Первое действие (v 1.0.1 action:update) свидетельствует, что у нас есть файл temp, который нужно обновить. Последнее действие (v 1.2.0 action:delete) сообщает нам, что файл temp нужно удалить. То есть получается, что промежуточные действия нам не важны. Важно первое действие – оно свидетельствует, что такой файл в принципе существует в директории нашего ПО, и важно самое последнее действие – оно собственно и определяет судьбу этого файла.

Учитывая это правило, комбинируем различные операции и получаем 9 кейсов/сочетаний первой и последней операции:
image

Рисунок 6

Поясню на примере из рисунка 5. Модуль обновлений:
• знает, что на компьютере клиента есть ПО версии 1.0.0;
• знает, что есть более актуальная версия ПО – 1.2.0;
• знает, что в директории ПО есть файл temp, судя по тому, что в скрипте обновлений версии 1.0.1 для файла temp предназначена операция update;
• знает, что в самой свежей версии 1.2.0 для это файла прописана операция delete.
Модулю обновления не важно, что происходило в промежуточных версиях. Пользуясь алгоритмом из рисунка 4, модуль обновления понимает, что нужно просто удалить файл temp.

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

Решили думать дальше…

Обновление по ладам.


… и пришли к следующему:
image

Рисунок 7

Мажорные билды будут содержать все файлы и папки. Минорные (последние 2 цифры версии) — только необходимые файлы. Таким образом, если у пользователя старый мажорный билд, то компоноваться будут только актуальный мажорный билд и минорные в соответствующей ветке. Если у пользователя актуальный мажорный билд, то компонуются только необходимые минорные.

Перед склейкой билдов создается временная папка. В нее копируются без перезаписи все необходимые билды, начиная с актуального. Файлы update.script компонуются в один, начиная с самого раннего. При дублировании команд логика следующая:
• update – остается только последняя команда;
• create – остается только первая команда;
• delete – остается только последняя команда.
Итоговая папка архивируется и отдается на скачивание. Название файла содержит в себе номера версий: с какого билда по какой идет обновление. Таким образом, при следующем аналогичном запросе будет отдаваться кешированный архив. Если целевая версия архива не соответствует актуальной, его можно удалить, освободив пространство. Билды с неактуальной мажорной версией можно также удалять/архивировать, т.к. обращаться к ним апдейтер уже никогда не будет.

Вывод.


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

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

В-третьих, поделитесь пожалуйста с нами, как ВЫ реализовывали автоматическое обновление на своих рокетсайенслевел приложениях.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.