Mojolicious::Lite で Kobito ビューワー的なものを作ってみた

最近勉強メモを残すのに Kobito というアプリを使っている。ただこのアプリ、残念なことにMac用のクライアントしかなくて、データもローカルに保存されているので、せっかくメモしても会社のWindows PCから見ることが出来ない。

なんとかして会社からも見たいなと思い調べてみると、データは ~/Library/Kobito/Kobito.db にSQLiteなDBで保存されていることが分かったので、サーバ経由で閲覧出来るようWebアプリを書いてみることにした。


データのアップについてはKobito.dbをDropboxに移して元あった場所にはsymlinkを張るのが一番楽かなと思ったけど、サーバにDropbox内のデータを全て同期させるのはちょっと嫌だし、閲覧専用になら常にMac側がマスターとなるので、ベタにcronでサーバへrsyncすることに。

自宅サーバなため、そのままcrontabに書くと外でwifiを繋いだときにエラーを吐きまくるのでシェルスクリプトでごにょごにょと。

rsync_kobito_db.sh

#!/bin/sh

export PATH=/bin:/sbin:/usr/bin

SERVER=192.168.0.50
MD5=`md5 -q ~/Library/Kobito/Kobito.db`
LASTMD5=~/tmp/kobito.db.md5

echo ${MD5} | diff ${LASTMD5} - && exit

if [ `ifconfig en0 | grep -w 'inet' | awk '{print $2}' | cut -d "." -f1` == 192 ] ; then
    date ; rsync -av ~/Library/Kobito/Kobito.db ${SERVER}:/path/to/app/
fi

echo ${MD5} > ${LASTMD5}

これを10分間隔で回す。

*/10 * * * * ~/bin/rsync_kobito_db.sh > ~/var/log/rsync.log 2>&1


あとはDBからselectして表示させるアプリを作るだけ。・・・といってもプログラマーではない僕にはなかなか難しいわけで、色々と調べながらなんとかそれっぽいものを作ってみた。
自分の無知っぷりをさらけ出すのは恥ずかしいけど、人は叩かれて強くなるのでコードを晒してみる。ちなみに初Gist。ドキドキ。


はじめは Kossy で作ろうと思っていたんだけど Text::Xslate で部分的にタグをエスケープさせない方法が分からなかったので、断念して Mojolicious::Lite にした。Mojolicious のいいところはドキュメントが豊富なところ。あとテンプレート部分もそのままPerlで書けるので分かりやすい。

とりあえずこれでWebから閲覧出来るようにはなったけど、以下のとおり色々微妙なところがある。

  • タグを付けていないメモは表示されない。
  • Text::Markdownで再度HTML化しているのが無駄だしKobito.appと表示が異なる。
    • ZBODYに保存されているHTMLを引っ張ってきて、Kobito.app内のcssを持ってくれば同じように表示出来そうな気がするけどZBODY内のHTMLからbody部分だけを上手く抜き取る方法が思いつかなかった。
  • 上に関連して ```Perl:hoge.pl ``` の記法を変換出来ない。
  • UIのセンスが(ry

さて、どうしたものか。。

追記

id:yaottiさんに XML::XPath とか使えばbody部分抜き取れるよ!って教えてもらったので Text::Markdown で再変換するのを止めてDBに保存されているHTMLを直接取ってくるようにした。

使い方が悪かったのか、XML::XPathだと<link>や<br>など対となるタグがない所でこけるので、代わりに HTML::TreeBuilder::XPath を使ってみたところ上手くいった。(最後に<body>タグをs//で消してるのがカッコ悪いけど・・・) 上のGistは変更済み。

f:id:nkwhr:20120504213103p:plain

改行崩れとかなくなってすっきり。

余談

HTML::TreeBuilder::XPathのSYNOPSISで

$tree->delete; # to avoid memory leaks, if you parse many HTML documents

と言ってるけどスコープ外れたら開放されるもんだよね?よね?と最初は $tree->delete; を書いていなかったんだけど、だんだん自信が無くなってきたので有無でのメモリ使用量を計測してみた。

このスクリプトを回した状態で

$ while true ; do (for i in {1..10} ; do GET http://localhost:3000/?memo=$i >/dev/null; done) ; sleep 5 ; done

こんな感じでグルグルGETしてみる。

  • $tree->delete; 無し
# while true ; do ./smap.sh `pgrep plackup` ; sleep 5 ; done
PID=19084        RSS=23816       SHARED=2044 (8.00%)
PID=19084        RSS=26868       SHARED=2148 (7.00%)
PID=19084        RSS=27108       SHARED=2132 (7.00%)
PID=19084        RSS=27396       SHARED=2132 (7.00%)
PID=19084        RSS=27696       SHARED=2132 (7.00%)
PID=19084        RSS=27984       SHARED=2132 (7.00%)
PID=19084        RSS=28572       SHARED=2148 (7.00%)
PID=19084        RSS=28872       SHARED=2132 (7.00%)
PID=19084        RSS=29192       SHARED=2132 (7.00%)
PID=19084        RSS=29496       SHARED=2132 (7.00%)
PID=19084        RSS=30012       SHARED=2148 (7.00%)
PID=19084        RSS=30128       SHARED=2132 (7.00%)
^C
#
  • $tree->delete; 有り
# while true ; do ./smap.sh `pgrep plackup` ; sleep 5 ; done
PID=23973        RSS=23832       SHARED=2044 (8.00%)
PID=23973        RSS=23832       SHARED=2044 (8.00%)
PID=23973        RSS=26716       SHARED=2132 (7.00%)
PID=23973        RSS=26808       SHARED=2148 (8.00%)
PID=23973        RSS=26716       SHARED=2132 (7.00%)
PID=23973        RSS=26716       SHARED=2132 (7.00%)
PID=23973        RSS=26716       SHARED=2132 (7.00%)
PID=23973        RSS=26716       SHARED=2132 (7.00%)
PID=23973        RSS=26716       SHARED=2132 (7.00%)
PID=23973        RSS=26716       SHARED=2132 (7.00%)
PID=23973        RSS=26716       SHARED=2132 (7.00%)
^C
#

あれ、deleteしてない方は増え続けてる・・・

こういうものなのか分からなかったので、ググってみたらtypesterさんの記事が出てきた。
HTML::TreeBuilder::XPath + WWW::Mechanize なスクリプトを永続化させようとしてはまった - unknownplace.org

真似して Devel::Leak::Object でも調べてみた。

  • 1回だけ実行
$ perl -MDevel::Leak::Object=GLOBAL_bless xpath_test.pl
Tracked objects by class:
        Config                                   1
        DBD::SQLite::_WriteOnceHash              1
        DBI::var                                 5
        Encode::Internal                         1
        Encode::utf8                             2
        HTML::Element                            145
        HTML::Element::_travsignal               5
        HTML::TreeBuilder::XPath                 1
        XML::XPathEngine                         1
        XML::XPathEngine::Expr                   1
        XML::XPathEngine::LocationPath           1
        XML::XPathEngine::NodeSet                1
        XML::XPathEngine::Root                   1
        XML::XPathEngine::Step                   2
        utf8                                     21
  • 100回ループ
$ perl -MDevel::Leak::Object=GLOBAL_bless xpath_test.pl
Tracked objects by class:
        Config                                   1
        DBD::SQLite::_WriteOnceHash              1
        DBI::var                                 5
        Encode::Internal                         1
        Encode::utf8                             2
        HTML::Element                            14500
        HTML::Element::_travsignal               5
        HTML::TreeBuilder::XPath                 100
        XML::XPathEngine                         1
        XML::XPathEngine::Expr                   1
        XML::XPathEngine::LocationPath           1
        XML::XPathEngine::NodeSet                1
        XML::XPathEngine::Root                   1
        XML::XPathEngine::Step                   2
        utf8                                     21

お、おう。。

  • $tree->delete;をつけて100回ループ
$ perl -MDevel::Leak::Object=GLOBAL_bless xpath_test.pl
Tracked objects by class:
        Config                                   1
        DBD::SQLite::_WriteOnceHash              1
        DBI::var                                 5
        Encode::Internal                         1
        Encode::utf8                             2
        HTML::Element::_travsignal               5
        XML::XPathEngine                         1
        XML::XPathEngine::Expr                   1
        XML::XPathEngine::LocationPath           1
        XML::XPathEngine::NodeSet                1
        XML::XPathEngine::Root                   1
        XML::XPathEngine::Step                   2
        utf8                                     21

どうもこういう仕様らしい。はまった人たくさんいるんだろうなぁ…