Автообновление в e2: технология

Я уже писал о том, как работает автообновление в e2. Однако там я говорил о пользовательском experience, а не о технической реализации. Сегодня расскажу о том, как это всё работает изнутри. (Сразу скажу, что работает это весьма успешно, несмотря на то, что реализовано очень коряво.)

Стержнем всей системы является файл verlog.txt, который я тут усердно веду, вписывая в него каждое изменение. Эта страница на сайте e2 генерируется именно на базе файла verlog.txt. Помимо текстового описания обновлений в нём содержится информация о том, какие файлы изменились в данной версии (если этого не указано, значит изменился только core.php). Когда e2 обращается к серверу за информацией о доступных обновлениях, он передаёт ему свою текущую версию, а сервер отвечает куском верлога от его версии до последней доступной.

Например, вы пытаетесь обновиться с v1099; последняя доступная версия сейчас — v1109. Вот, посмотрите, что выдаёт сервер. Если хотите, можете это unserialize’нуть и print_r’нуть. Дак вот, теперь e2 знает, что качать. Сами файлы последнего дистрибутива всегда лежат по адресу e2.ilyabirman.ru/download/updates/путь-к-файлу. Если вы вдруг хотите посмотреть, как в последнем дистрибутиве выглядит design_single_note.php, то возьмите и посмотрите. Теперь e2 попытается скачать все файлы, которые изменились между вашей версией и последней доступной. Если хотя бы один файл скачать не удалось, либо скрипту осталось выполняться меньше 5 секунд, то обновление отменяется. Если же всё хорошо, то старые файлы заменяются новыми.

После этого начинается самое интересное. При генерации следующей страницы e2 видит, что его версия больше той, которая прописана в реестре (при установке и после окончания обновления движок прописывает в реестр собственную версию). Это значит, что новое ядро выполняется в старой среде. Тогда запускается служба perform_update, задача которой состоит в том, чтобы выполнить все остальные действия по обновлению e2: изменить структуру БД и реестра или, например, удалить файлы, которые больше не нужны. Но откуда она знает, что именно нужно делать?

Дело в том, что эта служба сама уже выполняется из нового core.php. А каждый core.php, так уж получилось, знает, чем он отличается от всех предыдущих. Я знаю, что это коряво, но работает — и это главное. То есть, ещё раз, в самой службе perform_update просто-напросто написан примерно вот такой код:

...
if ($from < 894) { /* Делаем одно /* }
if ($from < 932) { /* Делаем другое */ }
if ($from < 1026) { /* Делаем третье */ }
...

Поскольку такого рода изменения нужно вносить не очень часто, то эти строки не так уж сильно увеличивают размер core.php, но всё-таки увеличивают. Начиная с версии v1027 e2 больше не умеет автообновляться с версий, выпущенных до Release 1:

v1027. e2 больше не будет автообновляться с pre-release-версий (то есть, если у вас стоит e2 v850 и более ранних, то обновляться теперь придётся вручную). Это позволило выкинуть из кода около 9 килобайт мусора, который оставался только для возможности такого обновления. Просто предполагается, что это никому давно не надо.

Напомню, что система обновления в e2 работает начиная с v294. Для диапазона v294...v850 9 килобайт — это не так страшно.

После того, как все эти if’ы пройдены и все изменения сделаны, в реестр прописывается новая версия e2, поэтому следующая генерация страницы уже не приведёт к вызову службы perform_update.

Всё это очень хорошо до тех пор, пока не возникнут какие-нибудь проблемы.

Во-первых, проблемы могут возникнуть во время замены старых файлов новыми. Пользователь мог не прочитать инструкцию и забыть прописать права 777 ко всем файлам и папкам e2. Если не удалось заменить ни одного файла, то это ещё хорошо — тогда просто не получится обновиться. А что произойдёт, если удалось обновить только часть файлов? Этого никто не знает. Конечно, в этом случае всегда можно сказать: «надо было читать документацию». Это явно не самое лучшее решение. Да и вообще, это могла быть не вина пользователя, например, во время обновления отключили электричество.

Во-вторых, проблемы могут возникнуть во время работы perform_update. Например, несколько if’ов были успешно пройдены, а потом в каком-нибудь произошла ошибка. В этом случае в реестре останется прописана старая версия e2, поэтому при следующем вызове все if’ы, включая успешно пройденные, будут проходиться заново. Это почти наверняка не страшно, так как там не делается никаких действий, которые нельзя было бы сделать 2 раза подряд (например, если дважды записать в реестр одно и то же значение, хуже никому не будет, а дважды добавить в таблицу одну и ту же колонку просто не даст сама СУБД). Но это очень коряво.

В будущих версиях я думаю немного улучишить всю эту схему. Во-первых, я хочу, чтобы код для perform_update не входил в core.php, а скачивался с сервера обновлений вместе с самими новыми файлами. Потому, что в ядре ему делать нечего. Во-вторых, я хочу, чтобы e2 вёл журнал процесса обновления и, в случае, если он был прерван, мог продолжить его с того же места. В-третьх, я хочу, чтобы в ситуации, когда движок находится где-то в середине процесса обновления, все режимы кроме login и все службы кроме login и perform_update падали в STOP, а perform_update выполнялась только если пользователь залогинен.

К сожалению, когда я придумывал схему автообновления для e2, я не предусмотрел возможность обновления самого кода системы автообновления без обновления всего остального. В Windows, например, такая штука есть. Помните, когда вы заходите на Windows Update, вам сначала предлагают скачать последнюю версию самой обновлялки? e2 так не умеет, поэтому обновляться до каждой следующей версии приходится средствами предыдущей, что иногда сильно осложняет задачу.

Так что вот, учитесь на чужих ошибках, пока дают!

Дальше
9 комментариев
cDima 2005

Мэн, пиши такие заметки чаще. Любопытно было прочитать, подумать над такой задачей. Сенкс.

Илья Бирман

Велкам, а чего надумал-то?

cDima 2005

Ничего лучше не придумал.

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

gregor 2005

Слушай, а зачем делать кучу if’ов? Можно, к примеру, вспомнить, что из switch не выпускают без break и сделать красивее (имхо):
switch ($from) {
case ’xxx’:
case ’xxx+1’:
case ’xxx+N’:
}

Или я что-то упускаю?

Илья Бирман

Упускаешь. У меня же не ##if ($from == 1234)##, а ##if ($from < 1234)##. В твоём случае from может просто не попасть ни в один из кейсов.

А ещё у меня какая-то неприязнь к сишному switch. Я его никогда нигде не использую. Так что даже если бы ты был прав, я бы всё равно делал if’ами.

gregor 2005

Ну, как тебе сказать. =) У тебя же версии все известны, так что проблема несколько надумана =) А свитч зря не используешь. Он порой сильно ускоряет работу и, что немаловажно, разработку.

Илья Бирман

Я могу выпустить десять обновлений подряд, и ни в одном из них не потребуется таких вот действий дополнительных. Тогда мне придётся специально добавлять кейсы только для того, чтобы отловить эти версии, которые у кого-то могут стоять. Стоит мне один раз забыть что-нибудь, как у кого-нибудь сломается движок. Зачем мне делать такую систему, которая изначально ждёт бага? Не понимаю.

Как сделано у меня сейчас, дополнительные строчки потребуются только тогда, когда действительно что-то изменилось.

Можешь привести пример, когда switch ускоряет разработку? Мне просто интересно, пока ещё никому этого не удавалось :-)

gregor 2005

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

Также switch позволяет неплохо визуально представить код, требующий проверки некоего выражения больше, чем на одно значение. Если форматировать полностью по правилам, то конструкция if_elseif_else будет уходить «направо» довольно сильно и неудобно. И избавиться от этого можно, только, нарушая правила форматирования.

Илья Бирман

Какие ещё правила? Вы что, не знаете, что правил не существует? Вот как я пишу:

%%if ($if_test) {
if_code ();
} elseif ($elseif_test) {
elseif_code ();
} else {
else_code ();
}%%

Это ж какими нужно пользоваться правилами, чтобы это уползало вправо? Если у вас код одного уровня записан на разной «глубине» вправо, то, мне кажется, вам стоит пересмотреть ваши правила.

Ramon 2005

Попробую таки доказать правоту gregor’а в некоторых случаях. Простой кусочек кода:
%%switch ($test) {
case 1:
// do first
break;
case 2: case 3:
// do second and third
case 4:
// do second, third and forth.
break;
default:
// do something otherwise
}%%

frst 2005

%%switch ($test) {
case 1:
// do first
break;
case 2: case 3:
// do second and third
case 4:
// do second, third and forth.
break;
default:
// do something otherwise
}%%
меня глючит или здесь ошибка?

cDima 2005

Клопс предлагает использовать cvs для файлов и логики.
http://www.livejournal.com/users/clops/464867.html?view=5736163#t5736163

Илья Бирман

Да я в курсе, я обсуждал с ним уже это всё. Я не понимаю, почему он считает, что это проще, чем то, что сделал я.

dmitriyG 2005

э. я извиняюсь, возможно вопрос глупый, XMLRPC прикурутить случайно нельзя, чтобы постить БлогДжетом например

Мои книги