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は変更済み。
改行崩れとかなくなってすっきり。
余談
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
どうもこういう仕様らしい。はまった人たくさんいるんだろうなぁ…