Автообновление в 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 так не умеет, поэтому обновляться до каждой следующей версии приходится средствами предыдущей, что иногда сильно осложняет задачу.
Так что вот, учитесь на чужих ошибках, пока дают!
Мэн, пиши такие заметки чаще. Любопытно было прочитать, подумать над такой задачей. Сенкс.
Велкам, а чего надумал-то?
Ничего лучше не придумал.
Возможно будет легче использовать метод имплементированный для плагинов в форуме SMF, там плагин указывает файл, строчку, и на что эту строчку заменить. Получается отличная вещь для плагинов, но не для обновлений. Так вот.
Слушай, а зачем делать кучу if’ов? Можно, к примеру, вспомнить, что из switch не выпускают без break и сделать красивее (имхо):
switch ($from) {
case ’xxx’:
case ’xxx+1’:
case ’xxx+N’:
}
Или я что-то упускаю?
Упускаешь. У меня же не ##if ($from == 1234)##, а ##if ($from < 1234)##. В твоём случае from может просто не попасть ни в один из кейсов.
А ещё у меня какая-то неприязнь к сишному switch. Я его никогда нигде не использую. Так что даже если бы ты был прав, я бы всё равно делал if’ами.
Ну, как тебе сказать. =) У тебя же версии все известны, так что проблема несколько надумана =) А свитч зря не используешь. Он порой сильно ускоряет работу и, что немаловажно, разработку.
Я могу выпустить десять обновлений подряд, и ни в одном из них не потребуется таких вот действий дополнительных. Тогда мне придётся специально добавлять кейсы только для того, чтобы отловить эти версии, которые у кого-то могут стоять. Стоит мне один раз забыть что-нибудь, как у кого-нибудь сломается движок. Зачем мне делать такую систему, которая изначально ждёт бага? Не понимаю.
Как сделано у меня сейчас, дополнительные строчки потребуются только тогда, когда действительно что-то изменилось.
Можешь привести пример, когда switch ускоряет разработку? Мне просто интересно, пока ещё никому этого не удавалось :-)
Ну, например, банальный выбор режима работы, когда тебе требуется выполнить одно и то же действие, но с разными настройками при противоположных вариантах условия, при этом существует третий вариант работы, который выполняется в редких случаях, но не даёт возможность вынести повторяющуюся конструкцию из if’а. (что-то вроде того =). Ну, или попробую построить ккой-нибудь автомат на if’ах, а потом на switch’ах =) В целом, и той и той конструкции есть хорошие применения, надо только их найти. Реальный пример, если интересно, могу запостить, когда его сделаю (если не забуду в процессе работы =).
Также switch позволяет неплохо визуально представить код, требующий проверки некоего выражения больше, чем на одно значение. Если форматировать полностью по правилам, то конструкция if_elseif_else будет уходить «направо» довольно сильно и неудобно. И избавиться от этого можно, только, нарушая правила форматирования.
Какие ещё правила? Вы что, не знаете, что правил не существует? Вот как я пишу:
%%if ($if_test) {
if_code ();
} elseif ($elseif_test) {
elseif_code ();
} else {
else_code ();
}%%
Это ж какими нужно пользоваться правилами, чтобы это уползало вправо? Если у вас код одного уровня записан на разной «глубине» вправо, то, мне кажется, вам стоит пересмотреть ваши правила.
Попробую таки доказать правоту 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
}%%
%%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
}%%
меня глючит или здесь ошибка?
Клопс предлагает использовать cvs для файлов и логики.
http://www.livejournal.com/users/clops/464867.html?view=5736163#t5736163
Да я в курсе, я обсуждал с ним уже это всё. Я не понимаю, почему он считает, что это проще, чем то, что сделал я.
э. я извиняюсь, возможно вопрос глупый, XMLRPC прикурутить случайно нельзя, чтобы постить БлогДжетом например