24 октября 2010

Линковка в Mac OS X

Симптомы

ld: in lib_foo.a, archive has no table of contents
collect2: ld returned 1 exit status

Лечение
export COMMAND_MODE=unix2003

ещё можно почитать man compat и убедиться, что врут, негодяи:
if it is unset or set to something other than legacy or unix2003 it behaves as if it were set to unix2003

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

22 октября 2010

уходи, iTunes, уходи!

Чтобы угомонить вконец обнаглевшего уродливого толстяка iTunes, возомнившего себя единственным на свете плеером и намертво присосавшегося к аппаратной кнопке Play/Pause моего макбука после обновления оного до сноулео, достаточно сделать так:

sudo chmod -x /Applications/iTunes.app/Contents/MacOS/iTunes

а для редких случаев, когда iTunes всё-таки нужен, сделать в Automator приложение с простым шелл-скриптом:
sudo chmod +x /Applications/iTunes.app/Contents/MacOS/iTunes
/Applications/iTunes.app/Contents/MacOS/iTunes &
sudo chmod -x /Applications/iTunes.app/Contents/MacOS/iTunes
Это я после очередного апдейта вспомнил, записываю чтоб не забыть.
UPD:
После того, как в очередной раз пришлось курощать iTunes, скрипт перестал работать, то есть работал, но iTunes продолжала сетовать на отсутствие лайона. Рабочий вариант ниже:
sudo chmod +x /Applications/iTunes.app/Contents/MacOS/iTunes
open -a iTunes &
sleep 5
sudo chmod -x /Applications/iTunes.app/Contents/MacOS/iTunes

20 октября 2010

Yet another Vim blog post (YAVBP)

За последние пару недель как-то много вокруг меня стало инфоповодов на тему Vim. Вот например прекрасная статья о том, почему его надо использовать (очень толково, просто, есть про настройку с описанием).
Так что я усиленно постился^Wпилил конфиг, запрягал Vim работать в паре с Elipse для работы над Android проектом (всё на удивление просто - последний Elipse, последний MacVim или GVim и Eclim), собрался с духом, засунул наконец все конфиги в git и отправил на GitHub. Конфигурация получилась лёгкой, из неё было выброшены все вековые наслоения конфигов, ненужных плагинов и прочей чуши. Ещё там есть маленький враппер для открывания файла в новой вкладке существующего Vim'а а не в новом экземпляре Vim'а. Так что полюбопытствуйте.
Впрочем, скорее всего, разработку под андроид вскоре можно будет отдать на откуп IntelliJ Idea X EAP, в которой есть и поддержка оной и плагин-эмулятор Vim'а. Попробовал, повертел - за час не возникло серьёзных претензий (да и вообще продукты JetBrains - это что-то, как и их поддержка), разве что не сразу понятно куда там коней^WSDK запрягать. Во всяком случае решение лучше Eclipse.

27 сентября 2010

Как заставить работать Force Close Dialog на себя

... или Stacktrace or GTFO.

Задача.
Итак, первое приложение для Android написано и его бета достигла девайсов пользователей. Задача - при получении исчерпывающего сообщения от пользователя "Ничего не работает" (ну ладно, ладно, более конкретного - "Вылетает") знать, где и что не работает. Вообще это должно быть первым, что будет написано в приложении. Далее будет показано, как гарантированно поймать исключение и послать отчёт о нём в Redmine.

Примечание: большая часть информации относится скорее к Java, чем к Андроиду, однако повод и место применения намекают.

Стратегия.
Приложение совершило критический промах и укусило себя в глаз. Частный и самый распространённый случай этого - исключение (есть и другие варианты, я сталкивался с перезагрузкой телефона), которое принято отлавливать и что-то делать по факту. Если оно не отловлено - пользователь увидит т.н. Force Close Dialog. Начиная с версии 2.2 ещё и предложение собрать данные об ошибке и поспамить ими девелопера, НО надо понимать, что
1. специфика некоторых ошибок привязана к определённым моделям девайсов, на которых вполне определённые версии Андроида, и это не 2.2
2. специфика некоторых рынков в популярности определённых моделей девайсов, см. выше
так что на этот способ не рассчитываем (как на основной).

Необработанное исключение нужно прежде всего отловить. Для этого в Java есть обработчик исключений потока, Thread.UncaughtExceptionHandler, реализация Андроида как раз и показывает диалог и пишет стектрейс в LogCat. С отловленным исключением нужно что-то сделать (узнать о самом факте его возникновения) и... в случае с UI Thread Андроида отпустить дальше - передать умолчальной реализации обработчика, потому что в противном случае, если исключение утаить, получим вместо одного диалога об ошибке другой (Application Not Responding). Кроме того, пользователю тоже особо ничего сказать не получится - UI Thread-то уже загнулся.



public class CrashReporter implements UncaughtExceptionHandler {
private UncaughtExceptionHandler defaultHandler;
public CrashReporter(UncaughtExceptionHandler defaultHandler)
{
this.defaultHandler = defaultHandler;
}

public void uncaughtException(Thread thread, Throwable e) {
Log.e("CrashReporter", "Caught " + e.toString() + " in "
+ thread.toString() + "\n" + Log.getStackTraceString(e));
// allow system to kill us
if(defaultHandler != null)
defaultHandler.uncaughtException(thread, e);
}
}



На основе этого бесполезного пока (потом будет интереснее) класса и будет строится обработка ошибок.

Тактика. Андроид.

Поскольку UI Thread в Андроиде один на всех, то получить глобальную обработку ошибок очень просто, в главной Activity достаточно при создании указать этот класс как обработчик:

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setUncaughtExceptionHandler(
new CrashReporter(Thread.currentThread().getUncaughtExceptionHandler()));
}


Тактика. Продвинутые техники.
Однако не всё делается в UI потоке Андроида, тяжёлые вычисления требуется вынести за его пределы. В Андроиде есть AsyncTask, который мне не подошёл (да и вообще на мой взгляд подходит только для простейших вещей), я использую ThreadPoolExecutor. Чтобы создаваемые для него потоки имели нужный класс обработчика ошибок, придётся создать свою фабрику (да, вот он, Java way) и передавать её в конструктор ThreadPoolExecutor'а:


public class CrashReportingThreadFactory implements ThreadFactory{
public Thread newThread(Runnable r) {
Thread newThread = Executors.defaultThreadFactory().newThread(r);
newThread.setUncaughtExceptionHandler(
new CrashReporter(newThread.getUncaughtExceptionHandler()));
return newThread;
}
}


public class CrashExecutor extends ThreadPoolExecutor {
private CrashExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
new CrashReportingThreadFactory());
}


Но это полдела. Теперь хлопок другой ладонью - надо заставить Executor обрабатывать исключения, возникшие в задачах, в т.ч. отправленных на выполнение вызовом submit и/или обёрнутые в класс типа FutureTask (за объяснениями - в доки к ThreadPoolExecutor.afterExecute и литературу):



protected void afterExecute (Runnable r, Throwable t)
{
super.afterExecute(r, t);
// задача отправлена на выполнение через submit или изначально является Future
if (t == null && r instanceof Future) {
try {
// принудительный вызов исключения
((Future<?>) r).get();
} catch (CancellationException ce) {
// отмена задачи - нормальное событие
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
// прерывание потока извне - передача исключения выше
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null) {
Thread thread = Thread.currentThread();
thread.getUncaughtExceptionHandler().uncaughtException(thread, t);
}
}

Так задача гарантированно будет вызывать исключение (из-за вызова get()) а исключения от Runnable не будут проглатываться.

Тактика. Redmine.

Всё, исключение есть, пора настроить Redmine:
1. Создать роль типа Crash reporter, имеющую право только создавать issue
2. учётную запись с этой ролью
3. набор дополнительных полей для Issue - например номер билда, тип девайса, на котором произошло падение
4. добавить учётку репортера в проект
5. разрешить REST web service
6. дополнительно можно завести под это свой тип трекера, чтобы отделять автоматически созданные issue

теперь можно постить:


// post reports only from wild (end-user) devices
if(!ApplicationInfo.getInstance().isAppDebuggable()){
try {
StringBuilder postBody = new StringBuilder()
.append("<issue><project_id>20</project_id><subject>")
.append(e.toString())
.append("</subject><tracker_id>4</tracker_id><priority_id>10</priority_id><custom_field_values><1>")
.append(Uri.decode(Settings.deviceInfo))
.append("</1><2>")
.append(ApplicationInfo.getInstance().deviceuid)
.append("</2><3>")
.append(String.valueOf(ApplicationInfo.getInstance().getAppBuild()))
.append("</3></custom_field_values><description><![CDATA[")
.append(Log.getStackTraceString(e).replaceAll("]]>","]]>]]><![CDATA["))
.append("]]></description></issue>");
post("http://redmine.company.com/issues.xml", postBody.toString());
} catch (Exception e1) {/*silently die*/}
}

public static void post(String url, String postData) throws IOException {
HttpClient client = new DefaultHttpClient();
HttpPost request = new HttpPost(url);
request.addHeader(BasicScheme.authenticate(
new UsernamePasswordCredentials("crash", "password"), "latin1", false));
request.addHeader("Content-Type", "application/xml");
StringEntity se = new StringEntity(postData);
request.setEntity(se);
HttpResponse response = client.execute(request);
}


ApplicationInfo - собственный класс, который собирает основную инфу об устройстве. Упоминаемые в xml ID можно узнать если посмотреть на ссылки соответствующих вещей - трекера, дополнительных полей, проекта.

Можно собирать релизный билд. Главное - не забыть убрать логирование и возможность отладки (удивительно, как много приложений, в которыx это не сделано).

09 июля 2010

Shell hint - end task notification

Запустил долгую задачу, а когда кончится - непонятно, мониторинг активности консоли (как в Konsole или iTerm) тоже не помогает - процесс иногда обделывается выводом в STDERR, что делать?

^z
fg; say "Job done!"

Это для маков, но идея проста - отправить задачу в сон, вернуть обратно, по окончании отчитаться (линуксоиды могут прослушать ядро).

28 июня 2010

Старший onchange наблюдает за тобой

У dom элемента есть события. Среди них есть onchange. Если я делаю

var onchange = function() {/* do here something*/};

то не странно ли присобачивать эту функцию _ко всем контролам ввода_ на странице? Они вообще слышали про пространства имён? Или это умышленно так?

11 мая 2010

jqGrid custom date picker

Да, давно не писал, но это жизнь, которой мало, и работа, которой много. Вот по работе иногда приходится хлопать одной ладонью.

Так и на прошлой неделе - нужно было приделать к редактору модели в jqGrid свой элемент для редактирования дат, наподобие того, что генерят рельсы в ответ на select_date. Скудость документации и отсутствие толковых примеров и как следствие полдня экспериментов на пути к желаемому и стали причинами появления данного поста.

Для начала обратимся к истокам и объявим наше поле как custom, указав свои функции для создания элемента и извлечения значения из него.
{name:'v_birth', index:'v_birth',formoptions:{elmprefix:"* &nbsp"},edittype:'custom',editable:true,editoptions:{custom_element:dinput, custom_value:dvalue}}


Дальше немного JS магии.



Создаём, заполняем значениями с выбором одного из них, и возвращаем массивом три select'а и кнопку сброса.

Подразумевается, что в таблицу дата приходит в американском формате (mm-dd-yyyy), именно поэтому у меня поле называется v_birth - это виртуальный атрибут, в базе это естественно тип Date. О валидации таких атрибутов и их применении совместно с jqGrid стоит сказать, пожалуй, отдельно, своим постом.

Пользователь увидит три селекта и кнопку сброса их в начальное значение (--). Браузер - невалидный код (три селекта с одинаковым id), но промолчит. Функция извлечения значения благодаря этому увидит все три select'а, отмапит их в массив выбранных значений и склеит из него строку. Бэкенд - либо опять же дату в американском формате, либо пустую строку, если пользователь сбросил дату, либо неверную дату, и может сохранить, об NULL'ить или остановить валидацию на своё усмотрение.

И для завершения - ещё один штрих. Истоки.

...
.navGrid('#my_pager',
{edit:true,add:true,del:true,search:false,refresh:true},
{recreateForm:true, afterSubmit:function(r,data){return afterSubmit(r,data,'edit');}},
....
Обратите внимание на recreateForm:true. С этой опцией элемент будет создаваться каждый раз при открытии формы редактирования и устанавливать начальные значения из нужной модели, а не из первой, для которой он был вызван.
А теперь отмазки. JS код грязноват и неоптимален, но я иррационально не люблю JS. Кроме того, на самом деле я использую 2dcJqgrid Rails plugin, но уже сильно модифицированный (оригинал бы не позволил сделать описываемое), так что привожу JS код для создания таблицы.

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

13 января 2010

Коммить это!

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

21 ноября 2009

Док

Нашёл для себя идеальный вариант дока - не показывать ничего кроме запущенных программ (и запускать их через Namely). И пару папок с документами (ебуками) ибо для быстрого открытия документов индексаторы типа Spotlight ещё не годятся совсем (о, где же ты, krunner). Хотя, казалось бы, какого чёрта, на кой в системе тогда есть locate?
UPD: нашёл такую штуку как Butler. Как минимум fuzzy-find и запуск документов (да и вообще всего) он умеет, что уже покрывает мои потребности, а там столько всего...

16 ноября 2009

Preview, Opera, whatever and Dictionary.app

Недавно возмущался тем, что в самых нужных местах не работает поиск выделенного текста по хоткею с помощью стандартного Dictionary.app. Не работало это в частности в Preview (так и хочется опять пнуть Эппл, оба приложени их) и в Opera (тут стоит пнуть саму Оперу - интеграция с макосью у них так себе, более того, в контекстном меню пункт "Поискать в словаре" загружает словари Яндекса, нужны они сто лет с таким latency).
А сегодня внезапно нашёл решение, оно как всегда в стиле Эппл - то есть несколько сбоку (читай через задницу). В меню каждого приложения есть пункт Services -> Look Up in Dictionary, этим и воспользуемся, дальше понятно без слов:


13 ноября 2009

Гоню на макось.

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

  1. Нет буфера выделения.
  2. Нельзя потаскать окно мышкой при зажатом Alt'е (ну вот тут коллега пишет костыль для этого)
  3. Зато на Alt + letter забиндены все возможные Deadletters - чувствуешь себя в mc инвалидом, пока не вспомнишь, что можно юзать Esc
  4. ФМ ортодоксальных толковых НЕТ. Кроме mc, ну вы поняли
  5. Нет централизованного пакетного менеджмента, а макпорты и финк - говнище, каждый по-своему. Есть ещё Homebrew, идея мне нравится, но тоже хрень пока - очень молод. В общем ничего лучше портежей ещё я не видел.
  6. Как следствие культура "всё с собой" для бандлов (думайте про пакеты со всеми зависимостями внутри. тысячи их [зависимостей] !)
  7. Как следствие культура "всё с собой" для ОС - приготовьтесь работать с почерствевшими версиями языков и библиотек. Хорошо, что я тут root, посносил всё старьё нафиг.
  8. Мало крутилок. Мало! А те, что есть, по умолчанию выставлены в идиотские положения
  9. Некоторые скучают по тайловым оконным менеджерам, да и вообще оконный менеджмент тут слабоват.
  10. Жестокие и беспощадные вариации на тему программ. iTunes на макбуке про (про!) стартует так медленно, что можно уснуть и без колыбельной на ночь
  11. А держать всё в памяти не позволяет то, что всё дико свопится. На домашнем вообще своп отключен, используется в лучшем случае гигабайт памяти. Тут же менее гигабайта занятым не видел даже после старта.
  12. Обновления. Приехало обновление - отвалилась нативная версия игры, которая до того работала как часы, не упав ни разу. И никакого вам отката - нефиг. Да даже в винде какие-то там обновления присутствовали в Установке и удалении программ, как бы намекая нам, что их можно удалить (на минутку, в винде икспи, вышедшей в каком году? давно в общем). Ну а недавно вышедший унылый леопард уже давно стал притчей во езицех, утерев нос даже майкрософту с их вистой.
  13. Каждое рабочее утро начинаем с упражнения "Настрой-ка снова второй монитор".
  14. Каждый приход домой - с упражнения "найди ручками и подключи нестандартно настроенный вайфай" (в связи с некоторыми событиями это упражнение заменилось другим, см. ниже)
  15. Накрылся недавно домашний сервер, стал интернет раздавать с макбука. Ежечасное упражнение "воткни обратно непонятно куда девшуюся галочку Раздавать инет". И после этого они ещё выпускают какой-то там макосикс сервер. Отпавший PPPoE вроде научился переподнимать, да и то не всегда.
  16. Всякие разные спорадические глюки, чего только стоят внезапно отрубившиеся клава и тачпад макбука при работяющих как ни в чём ни бывало внешних мышке и клаве. Всего не вспомнишь.

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


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

JSON, YAML, whatever...schema

Не так давно прикрутил к проекту валидатор схемы JSON документа. По ссылке всякая документация и примеры.
Зачем оно надо?

  1. Для спецификации. Причём спека может быть написана хоть на YAML, хоть на JSON, хоть на чистом ruby - валидатор схемы создаётся из ruby-объекта. Так что перед тем как пистаь очередной протокол обмена чего-то с чем-то через JSON можно написать его спецификацию на том же JSON'е и избежать разночтений (свежа в памяти фраза коллеги - не, так [по разные стороны протокола] мы работать не будем, просто не будем друг друга слышать и будем писать несовместимых продюсеров и консюмеров).
  2. Для валидации, как ни странно. До валидатора у меня была хитроумная система проверки с привлечением всех хитрых штук динамического программирования в ruby. Сейчас нет. Защита от дурака, особенно полезная в случае если потом таким образом будет кому-то предоставлено API.

В общем, на мой взгляд, с какой стороны не подойди - исполняемая (не просто где-то записанная, а исполняемая) спецификация - полезная в хозяйстве вещь.

24 октября 2009

C/C++/Obj-C closures

Не так давно решил таки ознакомиться (никак не мигрировать, боже упаси) на представленный в MacOS X 10.6 GCD, почитал их введение в это дело (ровно одна строчка кода, всё остальное - маркетоидный буллщит), но одна фраза заставила на себя обратить внимание - основной компонент этого GCD - блоки - что-то подозрительно напоминали. И добрался до расшаренной коллегой статьи про реализацию блоков, и понял - ну точно, это ж самые что ни на есть closures, которым скоро 50 лет, их наконец-то добавят и в эти языки... Однако, всё сильнее и сильнее этот лагерь офункционаливается.
Со статьёй крайне рекомендую ознакомиться, а потом по ссылкам в конце. Особенно по первой, там чуть ли не дословно цитируют ruby stdlib и часть парадигм RoR и вообще рубишных фреймворков.
Однако в руби их использование гораздо более синтаксически-слаще...

12 октября 2009

Cache money warm-up

С недавнего времени использую в своём проекте Cache money. Вещь хорошая, но, конечно же, есть и недостатки, впрочем, это предмет для форка и багфиксов. Стоит также отметить, что решение может уже и существует среди форков, коих много.
Если у вас всё в порядке с памятью (RAM, I mean), то может возникнуть желание принудительно заполнить кеш по всем индексам всех моделей. Сниппет извлечён из моего Rakefile'а и адаптирован для использования в рельсах. Предупреждение: не тестировалось именно в рельсах и против составных индексов (хотя и предусматривает их использование).

27 августа 2009

Using DRb

Сегодня ночью понял, что вслед за книгой the Ruby way дал маху и имел лишний вызов DRb.start_service на клиенте. Если почитать документацию, то в комментариях сэмпл кода выясняется, что это требуется далеко не всегда и уже в доке к классу DRbServer выясняются детали. А ведь пока в сорец не залез - не понял. А это устранило единственный крупный bottleneck в приложении.
Вот и читай после этого полезные книги.

10 августа 2009

Camping 1.9, Passenger & ruby 1.9

Стремление найти себе на разные места приключений никогда не покидает таких как я, вот и захотелось завести текущий проект под ruby 1.9.1 и перевести под passenger.
Пара слов - по общему моему впечатлению, мир руби не готов ещё использовать 1.9.1. То одно, то другое не работает, приходится хачить уже написанное, вставлять проверки версии языка, что-то вообще не лечится (так, у меня не завёлся монгрел, даже после советов с isitruby19.com)
Начнём с самого ruby. Даже на InfoQ засветился скрипт для установки и переключения разных версий Ruby: ruby_swither.
Однако для моей цели пришлось внести в него поравки в секции для 1.9.1 (OMG, опять на одну маленькую строчку я убил полдня):

 export GEM_HOME=~/.ruby_versions/ruby-1.9.1-p129/lib/ruby/gems/1.9.1
Дальше всё привычно - ставим гемы, ставим camping:
gem install camping --source http://gems.judofyr.net
Сам camping придётся поправить, вот патчик:
--- /Users/phoenix/projects/camping/lib/camping.rb      2009-07-05 00:35:46.000000000 +0600
+++ /Users/phoenix/.ruby_versions/ruby-1.9.1-p129/lib/ruby/gems/1.9.1/gems/camping-1.9.316/lib/camping.rb       2009-08-09 18:07:48.000000000 +0600
@@ -3,12 +3,12 @@
 S=IO.read(__FILE__)rescue nil;P="<h1>Cam\ping Problem!</h1><h2>%s</h2>"
 U=Rack::Utils;Apps=[];class H<Hash
 def method_missing m,*a;m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super end
-undef id,type;end;module Helpers;def R c,*g
+undef id,type if respond_to? :id;end;module Helpers;def R c,*g
 p,h=/\(.+?\)/,g.grep(Hash);g-=h;raise"bad route"unless u=c.urls.find{|x|
 break x if x.scan(p).size==g.size&&/^#{x}\/?$/=~(x=g.inject(x){|x,a|
 x.sub p,U.escape((a[a.class.primary_key]rescue a))})}
 h.any?? u+"?"+U.build_query(h[0]):u end;def / p
-p[0]==?/?@root+p:p end;def URL c='/',*a;c=R(c, *a) if c.respond_to?:urls
+p[0]==?/?@root+p : p end;def URL c='/',*a;c=R(c, *a) if c.respond_to?:urls
 c=self/c;c=@request.url[/.{8,}?(?=\/)/]+c if c[0]==?/;URI c end
 end;module Base;attr_accessor:input,:cookies,:headers,:body,:status,:root
 M=proc{|_,o,n|o.merge(n,&M)}
В прошлый раз я дал маху, заявив, что новые версии camping под Passenger не идут - исправляюсь. config.ru:

ENV['GEM_HOME'] = '/Users/phoenix/.ruby_versions/ruby-1.9.1-p129/lib/ruby/gems/1.9.1'
require 'rubygems'
require 'camping'
$: << ::File.expand_path(::File.dirname(__FILE__))
require 'bin/app'

App.create if App.respond_to? :create

run App

Удачного полёта!

07 августа 2009

Делегирование методов контроллеров в Camping

Потребовалось мне недавно написать обёртку ко всем существующим ныне в приложении контроллерам, дабы их можно было вызывать через единую точку входа. Именно обёртку, поскольку всё к чертям переписывать не хотелось принципиально да и старый механизм работы мне бы самому пригодился. После пятнично-вечернего воскуривания сорцов Camping'а я это сделал (<ненависть>сорцы кемпинга - это лютое изнасилование мозга. Пусть этому вроде как и есть оправдание - '4K full of gags pocket framework', но так писать а тем более читать код нельзя</ненависть>). Вот вам примерчик - пусть есть контроллер, который просто возвращает завёрнутые в JSON свои параметры, и есть потребность вызвать его из другого контроллера:

  class Controller
    def post(param1, param2)
      return "{\"#{param1}\":\"#{param2}\"}"
    end
  end

controller_post_responce = App.post(:Controller, 'param1', 'param2').body
При этом вызываемый контроллер унаследует все куки, хедеры, окружение и прочий мусор вызывающего, так что вызываемый контролер и не заметит подвоха и менять в нём ничего не придётся.

02 августа 2009

Шаблон

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

способы запуска Camping приложений

Недавно ради интереса посчитал - после моих экспериментов над Camping приложением его можно запустить 4 способами:
  • традиционным camping app.rb
  • напрямую app.rb или ruby app.rb
  • через Passenger
  • с использованием библиотеки Daemons
Первый не требует никаких дополнительных действий и использует все традиционные умолчания, этим и удобен.
Второй требует, чтобы приложение само себя запустило, примерно таким кодом:
if __FILE__ == $0 || !ENV['RACK_ENV'].nil?
  App::Models::Base.establish_connection :adapter => 'sqlite3', \
    :database => "#{File.expand_path(File.dirname(__FILE__))}/../app.db"
  App.create
end
if __FILE__ == $0
  require 'mongrel/camping'
  server = Mongrel::Camping::start(OPTIONS[:app_host], OPTIONS[:app_port], '/', App)
  trap("INT"){server.stop;exit}
  trap("KILL"){server.stop;exit}
  $logger.info "Apnp server running at http://#{OPTIONS[:app_host]}:#{OPTIONS[:app_port]}"
  server.acceptor.join
end
Первый условный блок общий для данного метода и запуска из-под Passenger. Полезно, если требуется запустить как-то нестандартно - с другим адаптером БД etc. Есть один неприятный момент - этот метод работает только с 1.5 версией Camping, так что если используется Git-версия(1.9) - то стоит или ставить обычным gem install camping или использовать другой способ запуска (судя по экспериментам, для 1.9 работает только первый).
Третий способ тоже требует создания AR подключения и запуска приложения. Запуск из-под Passenger обнаруживается по наличию переменной окружения RACK_ENV. Об использовании этой переменной немного ниже. Ну а config.ru выглядит так:
# vim:syntax=ruby
require 'rubygems'
require 'rack'
$: << File.expand_path(File.dirname(__FILE__))
require 'bin/app'
run Rack::Adapter::Camping.new(App)
Самый, на мой взгляд, удобный способ со многими преимуществами: легко перезапускать приложение (да, уже не автоматически, как в первом), ну и все возможности апача на руках. С автоматическим перезапуском поможет vim:
" restart Passenger app
au BufWritePost * silent !test -f 'tmp/restart.txt' && touch 'tmp/restart.txt'
Четвёртый способ заключается в написании небольшого враппера вокруг приложения, который запускает его вторым способом, но в фоне:
#!/usr/bin/env ruby
require 'rubygems'
require 'daemons'
Daemons.run( File.expand_path(File.dirname(__FILE__))+'/app.rb')
Однако на практике, имхо, это проблем создаёт больше, чем преимуществ, и ради демонизации приложения можно использовать того же пассажира.
Ну и напоследок об окружениях. Идея RAILS_ENV хороша, почему бы и тут не сделать так же.
DEVELOPMENT = ENV['RACK_ENV'].nil? || ENV['RACK_ENV'] == 'development' # some things are for development purposes only
$logger = ((ENV['RACK_ENV'] || defined? Daemons )? \
           # file-based logger for background execution and stdout for the rest
           Logger.new("#{File.expand_path(File.dirname(__FILE__))}/../log/app.log", 10, 1024000) : \
           Logger.new(STDOUT))
Так, у меня в development окружении поставлены в некоторых местах задержки для тестирования клиента этого приложения, а для всех фоновых способов запуска логи пишутся в файл вместо стандартного вывода. При желании, раз уж нам бесплатно досталась в руки RACK_ENV, то можно делать подключения к разным базам и писать логи в разные файлы в зависимости от неё.

30 июля 2009

XCode screenshots

Заметка на полях - XCode сохраняет скриншоты с устройств в ~/Library/Application Support/Developer/Shared/Xcode/Screenshots