Jabber-нотификация в redmine

Просматривая статистику посещений, обратил внимание на то, что многих интересует словосочетание «redmine jabber». Пришла пора удовлетворить спрос. В начале «как обычно» небольшая предыстория о том, «как всё начиналось»:

Несколько лет назад мне удалось внедрить redmine на «производстве», коллеги потихоньку втягиваясь в процесс, затребовали «книгу отзывов и предложений». Одним из первых пожеланий была замена стандартных уведомлений по email на, успевшие уже войти в моду, jabber-уведомления, которые мы используем для нотификации о новых коммитах в svn. Я вооружился тем самым поисковым запросом, о котором упомянул и немедленно нашёл нужной функциональности готовый плагин. Помимо уведомлений, автор обещал нам некие таймеры, которые можно стартовать/останавливать посредством команд боту в чате, тем самым «точно» подсчитывать затраченное на решение задачи время. В теории это казалось очень нужной и удобной игрушкой, на деле же оказалось, что плагин был выпилен для старой версии redmine и с trunk-версией работать по полной программе отказался. Худо-бедно он иногда стартовал какие-то таймеры, иногда присылал какие-то уведомления, от полученных команд иногда впадал в ступор и потом долго игнорировал собеседника, в общем вёл себя непозволительно загадочно и непростительно таинственно, но скиллов для исправления обнаруженных проблем мне не хватало, а автор на открытые тикеты особо не реагировал. Мыши плакали, кололись и продолжали есть кактус до одного смешного стечения обстоятельств: я находился в другом городе, бессовестно тратил отпускное время на прогулки, осмотры достопримечательностей и дивных пейзажей, внезапно получил входящий звонок от коллег. Слёзно просили выручать – что-то мол случилось, трах-бах-тарарах …и всё, нет у нас больше redmine. Немного порассуждав вслух, источник проблемы вроде бы вычислили, каким-то временным образом проблему по телефону решили, но в тот же вечер, добравшись до интернета, плагин я без сожалений выкинул. Случилось следующее — при старте redmine, плагин коннектился к jabber-серверу, а уж затем поднимался сам редмайн, но в тот роковой момент соединение с интернетом пропало, от чего случился обширный exception и redmine умер не приходя в сознание.

***

Тогда стало понятно, что схема со злобным плагином не работает и уведомления надо рассылать внешним по отношению к redmine решением (здесь знатоки rails могли бы долго возражать, но уже поздно). Параллельно с этой проблемой существовали другие. (more…)

27.06.11  |  , ,  | 3 comments

Встаньте в очередь!

Встроенная камера ноутбука уже давненько определялась "через раз", был выработан целый шаманский обряд для её включения, но видимо я чем-то прогневил Великого Байта и последние месяцы камера совсем, что называется "отвалилась". Поэтому "случайно" посетив большой магазин с кучей техники, "случайно" же приобрёл usb-вебкамеру. Вопреки опасениям камера определилась и заработала (в skype) сразу же, без каких-либо танцев с драйверами, настройками, поиском anus-ware решений и т.п. дребеденью непременно сопровождающей подключение «любого» периферийного устройства в linux. Это не может не радовать!

После перезагрузки вдруг перестал работать звук в flash. Недлительное гугление рассказало о какой-то libflashsupport, которая немедленно поможет справиться с недугом, но внутренний голос пробасил что-то вроде «ты же не менял никаких настроек уже несколько недель». Что характерно – звук в amarok, vlc и skype работал исправно, но было замечено что в kmix первой закладкой было устройство под названием "USB Device 0x46d:0×825" вместо обычной "HDA Intel", которая стала второй. Запустил alsamixer – и точно, устройством по-умолчанию стала новоявленная карта, у которой даже и playback-а-то нет. Но видимых настроек у flash-player-а нет, поэтому надо было исправлять ситуацию "системно".

Поскольку уже имелся негативный опыт ковыряния с asound.conf(.asoundrc), то сразу стал искать способ либо переименовать устройства (вроде того, как переименовывают eth* правилами в udev) или определить порядок загрузки драйверов – лишь бы не трогать asound.

И способ нашёлся – в /etc/modprobe.d/sound.conf добавлены следующие правила:

options snd_hda_intel index=0                                                      
options snd_usb_audio index=1   

После перезагрузки карты выстроились в порядке живой очереди:

$ cat /proc/asound/cards
 0 [Intel          ]: HDA-Intel - HDA Intel
                      HDA Intel at 0xa5100000 irq 41
 1 [U0x46d0x825    ]: USB-Audio - USB Device 0x46d:0x825
                      USB Device 0x46d:0x825 at usb-0000:00:1d.7-5.4, high speed

Асинхронные задачи в PHP

Не открою Америки, если скажу что порой требуется выполнить некую времязатратную операцию, результат которой либо не нужен пользователю вовсе (запись в лог, удаление временных файлов и другое обслуживание сервера), либо его можно обмануть и сказать что операция выполнена успешно, а саму операцию выполнить “попозже”. Самым наверное близким всем примером такой операции можно назвать отправку почты – smtp-cессия может длится довольно долго, особенно если письмо огромное, сервер тормозной (ну да вы сами всё знаете, в реальном мире великое множество острых углов), но зачем пользователю ждать результата? Что ему делать если результат неуспешный? “Попробуйте повторить операцию позже” ? Не смешно! Рядовой пользователь на ваш рядовой ресурс не вернётся, дотошный – свяжется с вами другими способами, так что можно с чистой совестью соврать ему – мол всё путём, всё отправлено, всё доставлено, записано и всё так успешно и идеально, а в это время потихоньку начать на самом деле выполнять задачу.

Конечно если вокруг вас крутятся тысячи серверов, слово “ынтырпрайз” для вас звучит буднично, то для вас уже изобретено много-много buzz-word-ных решений с очередями сообщений, очередями задач и другими полезными решениями, но львиная доля разработчиков всё-таки создают сайты-визитки, поддерживают сайты на хостинг-планах “всё по 20 рублей”, да и вообще на мой взгляд глупо для отправки почты окружать простейший php-скрипт кучей софта вроде gearmand + расширения для работы с ним. Я же хочу показать решение “для бедных”, простое, но удобное в разработке, поддержке и отладке решение.

Итак задача – отправить письмо, не заставляя ждать пользователя.
Имеем:

list($recipient,$subject,$body) = get_vars_from_request();
include 'superpupermailer.php';
$mailer = new SuperPuperMailer($recipient,$subject,$body);
if ($mailer->send()) {
echo "Аллилуя! Мы сделали это, храни нас Великий Байт.";
} else {
echo "О нет, это случилось!!! Быть того не может...но всё же случилось - приходи, милый друг, в другой раз, а сейчас ошибка!";
}

Тут всё понятно – либо отправилось, либо не отправилось – всем приходилось видеть это с разных сторон, те кто видел это со стороны браузера нередко наблюдали не только “ошибочка вышла”, но и другие подробности вроде конкретных строк в скриптах, warning-ов и Fatal error-ов. Но мы уже выяснили ранее – во-первых пользователю совершенно по барабану что у вас произошло с сервером, а во-вторых “пробовать ещё раз” он скорее всего не будет. Поэтому код можно изменить следующим образом:

list($recipient,$subject,$body) = get_vars_from_request();
$async_job = 'send();';
if (file_put_contents('/dir/for/jobs/email.php',$async_job)) {
echo "Ура! Мы сделали этот мир лучше!";
} else {
echo "Увы, мир жесток и безжалостен...";
}

Что это и зачем? Где отправка почты? Каким образом письмо отправится? Больше вопросов чем ответов. Опять-таки остался else, шило поменяли на мыло? В целом да – заменили тёплое мягким, но всё-таки нет – как часто у вас заканчивается неудачей запись на диск? Чаще чем таймаут при коннекте к почтовому серверу?

Теперь о главном – зачем файл? Где отправка почты?

Этим займётся другой скрипт:

if (true === include('/dir/for/jobs/email.php')) {
  unlink('/dir/for/jobs/email.php');
}

Осталось вызвать этот скрипт. Например так:

...
echo 'Ура! Мы сделали этот мир лучше!';
...

Или же добавим в cron задание на вызов job.php каждый час/пять минут/каждую минуту.

Всё. Мы не заставили пользователя ждать, мы отправили сообщение, мы молодцы. А если не отправили? Отправим потом – файл-то остался!

Конечно возникает много закономерных возражений – что будет если send.php (или job.php) будет вызван одновременно двумя пользователями?
Эти вопросы надо обстоятельно решать, задачи создавать с уникальными именами, блокировать одновременный запуск скрипта job.php, в общем работы аж на целых десять минут.

Самое интересное в конце:

...
$job_name = uniqid('mail_');
if (file_put_contents('/dir/for/jobs/'.$job_name.'.php',$async_job)) {
   echo "Ура! Мы сделали этот мир лучше!";	
              $job_url = '/job.php?job='.$job_name;
              if (false !== ($fh = @fsockopen($_SERVER['SERVER_ADDR'], $_SERVER['SERVER_PORT'], 
    $errno, $errstr, 0.01))) {
                  fputs($fh,
                      "GET $job_url HTTP/1.0\r\n"
                          . "Host: {$_SERVER['HTTP_HOST']}\r\n\r\n"
                  );
                  fgets($fh,32);
                  fclose($fh);	
              }

} else {
   echo "Увы, мир жесток и безжалостен...";
}

Что здесь происходит? Мы создали php-файл, при выполнении которого отправится письмо, затем подключились к веб-серверу и запросили скрипт job.php, передав ему параметром имя только что созданного файла. И тут же отключились – ведь нам не важен результат, мы уже солгали пользователю о том, что операция завершилась успешно. На всё-про всё потратили доли секунды. Дальше уже дело техники – job.php захватит lock-файл, проверить наличие файла, имя которого ему передали, выполнит его, удалит в случае успеха, а затем отпустит lock-файл. Конечно надо не забывать, что скрипт может по каким-то причинам не выполнится (почтовый сервер недоступен, или ответит ошибкой, да и мало ли какие напасти происходят в реальности), поэтому следует вызывать job.php ещё и ещё, но пользователя это уже не должно волновать – у вас его письмо сохранилось и вы его доставите, он вам верит!

Разумеется решение годится не только для отправки почты, но и как я сказал в начале – для любых действий, результат которых не нужен немедленно.

16.04.11  |   | 7 comments

Решения и их последствия.

Разрабатывать продукт в недра которого может влезть “чужой” непросто. Мы придумываем всяческие “неймспейсы”, давая файлам, директориям, переменным и классам псевдо-уникальные префиксы, пытаясь предотвратить коллизии с чужеродной средой.

Однажды, много лет назад, ко мне свалилась задача – размещение некой сложной формы на сторонних ресурсах. Очевидное и годами отработанное решение – iframe было забраковано, т.к. опыт и пятая точкашестое чувство подсказывали – как только владелец сайта разместит форму у себя, его светлую голову немедленно посетит мысль – “а как мне приделать перламутровые пуговицы?!”. Это, признаться, пугало – целыми днями верстать и раскрашивать незнакомым дядям и тётям одну и ту же страницу пятью миллионами способов? Нет, пристрелите меня семеро! Второй способ – разработать и задокументировать API, чтобы владелец ресурса сам разрабатывал себе форму, был отвергнут как трудозатратный, но совершенно нежизнеспособный так как рядовой клиент как правило (тут могло бы быть нечто оскорбительное про уровень интеллекта и радиус кривизны передних конечностей, но цензура не пропустит) не готов своими силами что-то создавать, тогда как продукт конкурента был проще в освоении (работал из коробки). Был выбран третий путь – размещение формы в виде html-разметки, которую мы отдаём заказчику, что называется “в руки”, а данные подгружаем через <script src="http://my-resource.example.com/?parameters"></script>, это позволяло владельцу ресурса до определённой степени контролировать внешний вид полученного документа и избавляло нас от трудозатратной кастомизации сотен инсталляций продукта. Но этот способ потенциально создавал много проблем, т.к. находясь в инородной среде наш документ мог “подхватить” незапланированные свойства, “заразится” чужими переменными и вообще рассыпаться в пыль в руках неуклюжего веб-мастера. Необходимо было тщательно изолировать всё и вся. Одним из способов отделить своё “добро” внутри документа было создание уникального “пространства имён”, например создание атрибутов в виде <div rpz:property="value"></div> – это самое rpz: давало надежду на то, что внешний скрипт(движок сайта или сам вебмастер) “случайно” не создаст аналогичный атрибут с другим value (или вовсе без него), развалив всю эту шаткую конструкцию. Решение оказалось рабочим во всех доступных графических браузерах и популяризировалось мной не только в этом продукте, но и повсеместно в других (печатая эти строки пытаюсь понять – зачем вообще нужны были именно атрибуты и почему их нельзя было заменить на переменные в скрипте, но разумного ответа почему-то не нахожу).

Это неоднозначное решение “выстрелило в ногу” только на днях – появился IE9… Невероятно быстр, ангельски красив, дьявольски умён – блеск, а не браузер На вид такой же унылый как все предыдущие В общем обзор новшеств можно наверное найти на сайте разработчиков, ну а я увидел его первый раз два дня назад, ничего хорошего про него сказать не могу, да и речь совсем не о том. Случись такое совпадение – сайт заказчика оказался настолько весь из себя валиден, что IE9 работал со страницей в своём новом document.documentMode. И чтобы вы думали? Точно! Годами отработанная “технология” дала сбой – “самодельные” атрибуты просто-напросто не видны на такой странице, а моя твёрдая уверенность в том, что они есть запретила здравому смыслу проверять их наличие… так и случаются epic fail-ы, так было и со мной.

Один грязный хак удалось временно залатать другим грязным хаком, но урок получен – недокументированная возможность это лишь “возможность”, строить на ней что-то прочное нельзя.

К слову сказать, не так давно вместо атрибута rpz:property="value", я начал использовать “документированную возможность” – атрибут data-rpz-property="value" и соответствующий вызов в js изменился с jQuery(selector).attr("rpz:property") на jQuery(selector).data("rpz-property") , видимо “шестое чувство” о чём-то подозревало…

Amarok. Следующая остановка – eof?

В последнее время практически не слушаю музыку из локальной коллекции по причине описанной ранее. Но иногда запускаю и Amarok. И вот этот самый amarok начал чудить – останавливает проигрывание после каждого файла. Перерыл все настройки на предмет управление playback-ом, по многочисленным советам с разных убунтофорумов попробовал удалять все конфиги, перерыл багтрекер – все найденные баги были пофиксены несколько версий назад, всё безрезультатно, надежды нет и конец близок. Хоть бери да используй другой плеер…

Решение оказалось неожиданным – потребовалось сменить backend у phonon (кто бы мог подумать?!) с mplayer на xine, чтобы плеер перестал проказничать.

“Такой день.” (ц)

PhpStorm

С недавних пор начал плотно использовать PhpStorm на работе – с появлением в системнике “лишней” памяти она (IDE) стала ну просто космически быстрой, дьявольски умной и невероятно удобной. Одно тяготило меня – не нашёл возможности увидеть вывод отлаживаемого скрипта. Особенно яростно это давит в момент отладки веб-сервисов. И вот сегодня утром IDE предложила написать о себе отзыв. И я не отказал ей в тёплом слове и заодно спросил – где же, чёрт возьми, output?!

Был приятно удивлён скорой реакцией на запрос – приветливый support попытался мне помочь, а затем мы выяснили, что данный функционал ещё в пути и пока не готов. В связи с чем хочу выразить благодарность читателю за то, что он зайдёт на трекер к разработчикам и проголосует за эти фичи: #WI-4323 и #WI-4466 и отдельные “спасибы” раздать Сергею Баранову и Николаю Матвееву за скорую и адекватную помощь и снисхождение к русскоязычной аудитории пользователей.

P.S.

Если вы понятия не имеете о чём идёт речь, но разрабатываете на PHP, вы просто обязаны попробовать PhpStorm в деле – скачать eap-релиз можно на сайте разработчиков.

28.02.11  |  , ,  | 7 comments

Communication problem with “kded”

Экспериментируя с Plasma сильно-сильно уронил KDE. Роковое стечение обстоятельств – как раз перед этим обновил систему (в т.ч. и kde) и не успел рестартнуть сессию. Итого: утомительная многочасовая битва с мельницами, почти полностью утерянные настройки и эта чудо{вищная} заметка.

Симптомы были следующие: kde не стартует под пользователем, но прекрасно запускаются под root-ом.

startkde: Starting up...
Connecting to deprecated signal QDBusConnectionInterface::serviceOwnerChanged(QString,QString,QString)
kded(4946): Communication problem with  "kded" , it probably crashed. 
Error message was:  "org.freedesktop.DBus.Error.ServiceUnknown" : " "The name org.kde.kded was not provided by any .service files" " 

(4944)/ KStartupInfo::createNewStartupId: creating:  "${HOSTNAME};1283501102;250139;4944_TIME0" : "unnamed app"
startkde: Shutting down...
klauncher: Exiting on signal 1
startkde: Running shutdown scripts...
startkde: Done.

Незамедлительно был сделан вывод: что-то “пришло” в обновлённых пакетах, как назло недоступен slackware.com (нет, ну вы подумайте – всё вот так вот разом, а?). Был перелопачен /etc на предмет разного рода изменений в правах доступа. Затем в /dev/null один за одним полетели файлы из $HOME, /tmp и т.п. Удалил весь $HOME, создал девственно чистый – ничего. Начинала свербить мысль – а может остаться под root-ом…. ?!

Решение как всегда оказалось простым и ожиданным. Позабыл удалить /var/tmp/kdecache-$USER. Уже не первый раз сталкиваюсь и не последний раз забываю начать именно с этого.

P.S.
Вот на кой ляд половина хлама падает в /tmp, а другая в /var/tmp ?

05.12.10  |  ,  | 2 comments

Performance Optimization WordPress Plugins by W3 EDGE