play2.1で個人的なものを作った

play framework 2.1で個人的な課題を解消するためのサービスを作ってみた。
github https://github.com/kencharos/yakimashi/
herokuでのサンプル http://yakimashi.herokuapp.com/

動機

家族が増えて以来写真を撮ることが多くなり、写真は自分のMacbookiPhotoで管理しているんだけど、
家族が写真を見たりするときに面倒。
またプリントしてアルバムに残したり、親戚に送ったりするので、写真の一覧を紙に印刷して、家族でこの写真は誰々に何枚みたいな作業を数か月に1回くらいやっている。
写真の一覧だと細部がわかりにくかったりするし、集計がすげえ面倒。
というわけでこれらを解消するためのものを勉強がてら作ろうというのが動機。

要望

  • 家族各自のPC,iPadから見れる。
  • 写真の一覧、詳細を確認できる。
  • 写真ごとに送る人の設定とかができる。
  • 基本的に家族でしか使わないからユーザー管理とかしない。

構成要素

勉強のため自分の仕事であんまり触ってないものを中心にやってみた。

  • play framework2.1 + scala
  • mongoDB (playとの接続はplay-salatプラグインが超便利)
  • metadata extractor (JPGのEXIF取得javaライブラリ)
  • jquery + colorbox
  • LESS
  • css3
  • github
  • heroku

メモ、忘備録

作っていてはまったことやこれはすげえと思ったことなど。

  • 雑感として,playだとコード量がとても少ない(多分javascriptの方が多い。)。とはいえ学習コストは他のフレームワーク同様高いと感じた。
  • 色々な方式をscalaの文法や関数でなるべく対処しようとしているんだと思うんだけど、おまじないとしか思えない記述が多すぎて色々戸惑う。(下のformの宣言とか、formの検証とか最初はよくわからなかった)
  • playでformとcaseクラス、JSONとcaseクラスの変換を宣言的に記述できて面白い。

また、上記で特定の項目だけcaseクラスに設定したくないとか単純にマッピングできない場合にも対応できていて○

  • play、サーバー起動中の修正が再起動なしで反映されて楽。
  • ただし、コンパイルが微妙に遅く、viewとかrouteもコンパイルされるので、画面をちょっと直して5,6秒待つみたいなケースがある。
  • view templateは@に続く箇所にscalaコードを記述できるので色々できるんだけど慣れるまで微妙。
  • viewで使用するデータは、viewで宣言した引数のみで、JSPみたいな暗黙なリクエストとかセッションみたいなものは(宣言しない限り)出てこない。

viewの描画が関数の呼び出しになっているので、viewで色々できる危険性が減っている、、のかな。

  • Actionを合成することで、Action前後のログ出力とか、ログイン判定とかができる。
  • mongoDBが便利すぎて笑える。1対多のデータをそのままコレクションに突っ込めるとか。
  • play-salatも超便利。case classのコンパニオンに特定のクラスを継承するだけで、CRUD諸々の機能が追加される。
  • heroku でmongoHQを使う場合、herokuの環境変数(heroku configで確認)に、MONGOHQ_URLというmonngoDBのurlが設定されるのでこの値を設定する。設定するには、Procfileを作成し以下の形式でconf/application.confのプロパティを上書きする。
  • -Dmongodb.default.uri="$MONGOHQ_URL"

playコマンドがヒープ不足で失敗する時

ちょっと興味があって[www.playframework.com/:title=Playframework]を触っています。
自宅のmacと会社のWindowsPCにそれぞれPlay2.1の環境を作って共有しているんだけど、
WindowsPCでPlayプロジェクトでplayコマンドをうつと、以下のようにコンソールにで起動に失敗する。

Error occurred during initialization of VM
Could not reserve enough space for object heap
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

PCのスペックはWin7 32bit メモリ2G
ヒープ領域が確保できないようなので、playのスクリプトを見てみると、
/framework/build.bat
が本体のようでこのファイルの中に、

java -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M %DEBUG_PARAM% %JAVA_OPTS% -Dfile.encoding=UTF-8 -Dplay.version="%PLAY_VERSION%" -Dsbt.ivy.home="%~dp0..\repository" -Dplay.home="%~dp0." -Dsbt.boot.properties="%fp%sbt/sbt.boot.properties" -jar "%~dp0sbt\sbt-launch.jar" %*

とあるのでここを編集する。
Xms,Xmx,Xss,MaxPermSize全てのオプションを削除しても起動したのだけど、Xmxのでフォルトは64Mで心もとないので、Xmxを768Mに修正して無事起動した。
色々試した所、上記のPCのスペックだと、ヒープ領域とパーマネント領域の合計が1150Mを超えると失敗するようだった。
というかデフォルトでXmxが1Gは随分と富豪だなと感じる。

google driveのOCRを試すWebアプリを作ってみた

Ruby 認定資格Gold合格したので、何か作ろうかなと考えていた所、
Google DriveのOCRって使い物になるのかなと思い、
ファイルをアップロードしたらGoogle Driveに登録して、OCRのテキストを取得するような物を作ってみた。

Trying Google Drive OCR
利用するにあたって、GoogleのアカウントとDriveの利用、OAuth認証が必要です。
また、ここでアップロードされたファイルは、Driveに保存されます。

イメージはこちら

見て分かる通り、Driveに元画像とOCRの解析結果テキストが一つになった文書が作成されます。
テキストだけの取得は、色々調べたけどどうしてもページ内に埋め込めず断念。
というか、 OCRの精度は低い。あと日本語もまだ対応してないっぽい。


以下は作成時のメモ

Google Drive SDKのドキュメントはこちらにある。
Google Drive SDK — Google Developers
SDKAPIがあって、SDKChrome Web Storeに色々登録しろとか書いてあるけど、今回みたいなDriveへのファイルの登録とか取得だけだったらAPIだけでいい。SDKはDriveのUIに作成したアプリのメニューを追加したり連携したりする場合に使う。


APIを使う場合は、Google API ConsoleGoogle Accounts から、Drive APIを有効にし、Client IDとsecretを発行する。

今回作ったソースは https://github.com/kencharos/google-drive-oc-ruby に。
初めてGitをまともに使ったので、色々と変な事している気がする。
個人で実行する場合は上記のClient IDとsecretを、crient_secret.jsonに記載する。


アプリケーションは、heroku上で、sinatra,datamapper,haml などで作りました。
sinatraとてもいいです。


なお、herokuではファイルアップロードが一切できないと勘違い*1しており、最初はブラウザ側でFile APIを使用してbase64エンコードしてサーバーに送り、サーバー側でエンコードするような処理をしてた。
ファイル選択時にプレビュー出るのはその名残。


今回、初めてgit,github,ruby,herokuを使ってみました。
職場の、java, 占有ロックが基本の某バージョン管理ツール、起動するまで5分かかるサーバーという環境に比べると
色々と快適すぎて涙が出ます。


今後は、Rspec, Scala、html5の各API、ましなUI などもやってみる。


作成にあたり、以下のページを参考にさせていただきました。ありがとうございます。
Heroku上でSinatraアプリを動かすまでのまとめ - まちゅダイアリー(2011-10-02)
HerokuアプリをGitHubにもプッシュする - アインシュタインの電話番号
HerokuとGitHubの両方にプッシュする時の秘密にしたい値の扱い - アインシュタインの電話番号

*1:ファイルを書き込める領域が無いだけで、tmpfileは使える

Ruby始めました

仕事だとJavaしかやってないし、javaの仕事しかない業界だったりするので、
前から興味があったRubyの勉強始めました。
ついでなんで、Ruby技術者認定の取得も目指してます。
使ってるのはこの教科書

Ruby公式資格教科書 Ruby技術者認定試験 Silver/Gold対応 (EXPERT EXPASS)

Ruby公式資格教科書 Ruby技術者認定試験 Silver/Gold対応 (EXPERT EXPASS)

特異クラスおもしれーとかって思ってたらその辺はGold試験の範疇らしく、
Silver試験は組み込みクラスの挙動についてがメインのようで、暗記物が多くめんどい。
どのメソッドが破壊的メソッドなのかかどうかがわかりにくく*1、この辺を整理するのが吉かと思い、
とりあえず表にまとめてる。String,Array,Hashができれば多分対策としては十分と思われる。

Welcome to Google Docs

*1:Scalaだと型でimutable,mutableを切り替えられるので、そこは静的型付けの利点なのかな

総当たりのパターンを作る

検証用のコードなんかではたまに、複数個のパラメータの設定の組み合わせを全部試すようなことをやることがあります。
例えばとあるメソッドに5個パラメータがあるとしてそれらのパラメータに値を設定する or 設定しない だけでも
2の5乗で32の組み合わせがあります。

全部の組み合わせをどうにか作成したいと思い、二進数の各桁の値で設定有無を判定するようなコードを書きました。
以下のような感じです。

public class Test {

	public static void main(String[] args) {		
		final int SIZE = 5;
		for (int i = 0; i < Math.pow(2, SIZE); i++) {
			for (int j = 0; j< SIZE; j++) {
				int flg = (i >> j) % 2;
				System.out.print((flg == 0 ? "OFF" : "ON") + "\t");	
			}
			System.out.println();
		}
	}
}

値のon/offだけでなく、色々なバリエーションがある場合はこれでは難しいので、
ちょっと再帰を使ってみて、scalaで書いてみる。
要するに直積を求めるものですね。

  def product[A](list:List[A]*): List[List[A]] = list match {
    case Seq() => List(Nil)
    case _ => list.head.flatMap(x => product(list.tail: _*).map(y => x :: y))
  }
  product(List(1,2,3), List("A","B","C"), List(7,8,9)) foreach println

実はこんなことしなくても、scalaでは

for(a<-List(1,2,3);b<-List("A","B","C");c<-List(7,8,9)) yield {
  a::b::c::Nil
}

で同じ事ができるのですが、引数を可変にしたいということでこうなりました。

なお、今回のコードは末尾再帰ではないので引数が増えると簡単にOutOfMemoryErrorが起きます。
頑張って末尾再帰にしたり、Stream使ってみたりしましたが、
何だか旨くいかなかったので、何か方法を考える。。。
FPは奥が深い。

iTextで目次付きのPDFを作る

Java,C#でPDFを作成できるiTextというライブラリがあります。
国産の帳票製品だとレイアウトを作り込むようなものがほとんとで、論文のようなアウトラインがある文章を作るのが難しいので、そういうときに便利です。
以前はMPL等のライセンスでしたが、今はAGPLもしくは、お金払って専用ライセンス購入という形式になったようです。

アウトラインの内容で目次も作れたらなーと色々考えていたが、ページ数が動的に変わる場合もあるので、
一度本文を作ってから、別のPDFで目次を作るような流れになってしまった。
他にいいやり方あるのかな。。

できたPDFはこちらに。

ソースは一応Gist

ファイル内の全文検索の検討メモ

テキストファイル、wordやExcelなどのOfficeファイル、PDFなど様々なファイルを登録しておいて、
登録したファイルの中の文章から検索を行いたいというような話です。
今はApache Solrとかが流行ってますが、
アプリケーションの導入となると何かとハードルが高いので、
手持ちの材料で何かできないかというメモです。
雑です。

というわけで、まずはOracle Textについて調べてみた。
Oracle Textは全文検索の仕組みをOracleに載せたもの。
例えば、文章の中に"田中△一郎"、"田中・一郎"、"田中一郎" みたいに同じ単語でも間にスペースや記号があったりしても、"田中一郎"という検索ワードで一律ヒットさせたいというようなもの。
最近のバージョンであれば製品に最初から入っている(インストールの仕方によっては入ってない場合もあるが追加インストールできる)

とりあえず試してみるのに、参考にしたのはこの辺りの資料

Oracle Textの全文検索OracleらしくSQLのwhere条件にCONTAINS('列名','検索ワード')という条件を書く事で行う。そのためには、

  • 特定のテーブルの列(不定形の文章を入れるような列)にOracleText用のIndexを作る。
  • そのIndexには、構文解析の設定が必要(例, N-GRAM方式のJAPANESE_VGRAM_LEXERや、形態素解析のJAPANESE__LEXERなど)
  • ただしテーブルの挿入や更新と同時にそのIndexは更新されないため、定期的にIndexの同期をプロシージャで行う必要がある。(普通のIndexと比べると処理が重いためか)

などを行う必要がある。

とはいえ、これはテーブルの列について全文検索する設定なので、ファイルの場合はどうするの?
というと、上記のIndexの設定で、特定のディレクトリやURLなどを設定することで
その場所にあるファイルからテキストを自動で抽出してIndexを作成するようである。
それはそれで面倒なので、テーブルにBLOBの列を作成して、そこに全文検索のIndexを作って、
ファイルのバイナリを登録したらどうなるのかを試してみたら、
あっさりとバイナリからファイルのテキストを抽出してIndexを作ってくれました。
全部は試していませんが、ファイルの形式(ワードとかエクセルとか)を識別しますので、
それなりに使えるかと思います。
ただし、Officeのテキストボックスの中の文章は読み込めないようでした。
(Indexの定期的な同期など、考えないといけない事は色々ありますが)

補足として、検索条件にヒットした文章のどこがヒットしたのかを明示したいこともあるかと思います。
そういう場合は、別途プロシージャを使う必要があります。
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/text.102/B19214-01/cdocpkg.htm
にある、CTX_DOC.プロシージャに色々と種類があり、例えば、
CTX_DOC.MARKUP というプロシージャで、検索条件にヒットした文章の中の、
ヒットした箇所を、<<< と >>> で囲んで表示できます。
CTX_DOC.MARKUP には、全文検索のIndex名や、レコードのユニークキー、検索条件などを引数として渡す必要があるので、
検索してヒットしたレコード1件ずつに対して、MARKUPを行っていくという手順になります。


非常に雑だと自分でも思いますが、とりあえず使ってみた結果はこんな感じでした。
Oracleが手元にあるなら、とりあえずやってみてもいいかもしれませんが、
Indexの再作成や、ちょっと手の込んだことをするとなるとプロシージャを多用したりするので、そこは割と面倒だと感じました。
ファイルの抽出でOfficeのテキストボックを抽出できないのはちょっと残念なので、
別のライブラリも試す予定。