2011年9月20日火曜日

foursquare global hackathon @ Tokyo

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
NYC, SF, Paris, Tokyoで同時開催のfoursquare global hackathon、東京会場のオーガナイザ兼参加者として9/17-18の2日間約42時間ぶっつづけで楽しんできました。

東京会場では参加者15名、応募作品9と他会場と比べて少ない数字でしたが、みんなでワイワイおやつ食べたり深夜居残り組はみんなで銭湯行ったり、いい雰囲気でした。
NYC,SF,Parisの中継映像をスクリーンに映して、音声無しながらもParisがエッフェル塔切り抜いたのカメラの前に貼って遊んでたのに東京タワーで対抗?してみたりw
Togetterまとめ

いきなりですが東京会場内での作品の投票結果発表!
日本時間9/23の正午まで、http://fshackathon.appspot.com/で一般投票が行われているので気に入った作品にぜひ投票をお願いします(何作品でも投票できます)!

1位(会場得票数4): photosquare
作者:中継カメラ設置等いろいろ手伝ってくれた@koogawaさん
内容:近くのvenueに投稿されてる写真をスライドショー表示してくれるiPhone/iPad用アプリ。アプリ審査があるのでまだダウンロードできませんが、http://twitvideo.jp/06TpCから映像見ることができます。
投票する

2位(会場得票数3):The Journalist
作者:今回素晴らしい会場を提供してくださったGaiaXの@aomushi510さんと、直前に@aomushi510さんに誘われて急遽参加のTomoyuki Hisadaさんのチーム
内容:4sqのチェックイン履歴を使用して、自分の行った場所の記事を投稿できる、ソーシャルニュース的なサービス。新聞的なデザインや、細かいところの動きがよくできていて完成度高くてびっくり!
http://journalist.azu.sh/で見れる…はずが残念ながら今エラー出てます…
投票する

同着2位(会場得票数3):4sqlist(仮)
作者:pomu0325(すいません自分です。)
内容:サブベニュー(駅のホームとか、モール内の店舗とか)をグループ化して探しやすくするiPhone/iPad用WEBアプリ。時間が無くて最低限の機能しか作り込めませんでした… 同じアイデアで作り始めた@ikawamotoさんとは違い、手動でサブベニューの情報を編集するアプローチです。http://4sqlist.appspot.com/
投票する

以下、順不動での掲載です。

チーム名:curly
作者:@takochuuさん、@sunny_510さんのチーム
内容:venue毎に、そこにチェックインした人のつぶやきが見えたりランキングが見れたりするサービス。デザインもよくまとまってましたが、公開はまだ…かな?
投票する

Packed Venues
作者:大阪から来てくれた@ikawamotoさん
内容:住所が同じベニューをまとめて表示するiPhoneアプリ。動かしているところの動画はこちら。住所表記のブレが解決できれば自動でまとめられるアプローチの方がいいですね!
投票する

Squarecount
作者:meetup主催等いつも手伝ってくれてる@jayjpn
内容:4sqへのチェックイン回数を表示するだけw。エンジニアじゃないのに締め切りぎりぎりまで頑張ってました。http://squarecount.appspot.com/index.html
投票する

4sq Scouter
作者:自分と同じく携帯用4sq「じゃぽすく」を作っている@jpfoursquareさん
内容:スカウターと言ったらおなじみ、例のアレ風に「強さ」を数値化して表示してくれます。数値には、「複雑な足し算」が使用されているとのことですw http://scouter.jpfoursquare.com/から試せます。
投票する

fourSquare PopUp Utility
作者:The Journalistもチームメンバーで作っているTomoyuki Hisadaさん
内容:4sqのユーザやvenueの情報をマウスオーバーで表示してくれるjsライブラリ。ページ内のfoursquareへのリンクを自動で変換してくれるみたいです。twitter anywhereの4sq版、と言えば分かる人には分かる。使い方等
投票する

Complete Japanese fried chicken delis in MusashiKoyama
作者:@Suger1008 さん
内容:むさこ(武蔵小山)の唐揚げ屋さんを制覇! むさこ愛にあふれるスマートフォン用アプリをTitanium Mobileで作られていました。公開はまだですが解説等はこちら
投票する

応募があったのは以上9作品ですが、他にも時間が足りなかったり、既に作っているものを4sq対応したりと各自ガリガリがんばっていました。

最後に、深夜含め2日連続と無理な条件にも関わらずすてきな会場を提供してくださった、ガイアックスさん、本当にありがとうございました!!!
ハッカソンなどのイベントでよく会場提供をしているそうなので、またお邪魔させていただきます!

2011年3月29日火曜日

[Lift] 画像ファイルにjsessionidを付けない

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
takedasoftさんがUrl Rewrite Filterを使った方法を書いてましたが、Boot.scalaでLiftRule.urlDecorateにルール追加するだけで簡単にできたのでメモ。

val ImgPtn = """(.*\.(?:png|gif|jpg));jsessionid=.+""".r
LiftRules.urlDecorate.append {
  case ImgPtn(url) => url
}

※Lift 2.2からデフォルトではjsessionid付かなくなったのはここに書いてある通りです。

2011年3月22日火曜日

[Scala] sbt jetty-runでhttpsを使用したい

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

方法1: カスタムjetty.xmlを使う

sbt標準だとjetty.xmlを1から書けばOK。
override def jettyConfiguration = new CustomJettyConfiguration {
  override def jettyConfigurationXML = List("/path/to/jetty.xml")
}

この方法だとjetty.xmlにConnectorとかHandlerとか全部自分で書かないといけないので面倒…
参考:jetty.xmlのリファレンス

方法2: sbtにちょっと手を入れてjetty設定用のhookを使う

sbtのソースをcheckoutしてhookが設定できるようにする


参考:Build - simple-build-tool -

sbtのソースをcheckout
$ git clone git://github.com/harrah/xsbt.git
$ cd xsbt

変更したのは下の2行だけ。jetty-runが呼ばれてデフォルトのhttpポートのConnectorの設定直後にcallbackで設定した関数が呼ばれるようにする。
diff --git a/sbt/src/main/scala/sbt/WebApp.scala b/sbt/src/main/scala/sbt/WebApp.scala
index f5a3d09..997294e 100644
--- a/sbt/src/main/scala/sbt/WebApp.scala
+++ b/sbt/src/main/scala/sbt/WebApp.scala
@@ -105,6 +105,8 @@ trait DefaultJettyConfiguration extends JettyConfiguration
        def parentLoader: ClassLoader
        def jettyEnv: Option[File]
        def webDefaultXml: Option[File]
+
+       def callback: Option[AnyRef => Any] = None
 }
 abstract class CustomJettyConfiguration extends JettyConfiguration
 {
diff --git a/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ b/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ
index 263afa6..4ca4b0e 100644
--- a/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ
+++ b/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ
@@ -54,6 +54,7 @@ private object LazyJettyRun${jetty.version} extends JettyRun
                                case c: DefaultJettyConfiguration =>
                                        import c._
                                        configureDefaultConnector(server, port)
+                                       c.callback.foreach(_(server))
                                        val webapp = new WebAppContext(war.absolutePath, contextPath)
                                        webDefaultXml.foreach{webDefaultXml:File => webapp.setDefaultsDescriptor(webDefaultXml.toString)}

sbtのビルド

$ sbt update generate-loader-compat proguard "project Simple Build Tool" publish-local
※generate-loader-compatしないとjetty6用のクラスが一部生成されなかった。(jettyは6->7でパッケージ名が変わったので、sbtのソースではテンプレート化して6,7,7.2用のscalaソースが吐かれるようになってる

ssl用証明書の準備

JDKのkeytoolを使用。とりあえずオレオレ証明書で。
$ keytool -keystore src/test/resources/keystore -alias jetty -genkey -keyalg RSA
$ keytool -selfcert -validity 1024 -keystore src/test/resources/keystore -alias jetty
※keystoreのパスワードを聞かれるので入力します

sbt プロジェクト側の準備

build.propertiesの sbt.version=0.7.5.RC0 にしてsbt実行、先に標準の0.7.5.RC0を入れておく。

ビルドしたsbt/target/scala_2.7.7/sbt_2.7.7-0.7.5.RC0.jar をプロジェクトのproject/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.5.RC0/sbt_2.7.7-0.7.5.RC0.jar に上書き。

project/build/MyProject.scala の設定。callbackでjettyのServerインスタンスにSslSocketConnectorを追加。ClassLoaderが違うので普通に書くとClassCastExceptionが発生するのでリフレクションで。
  override def jettyConfiguration: JettyConfiguration =
    new DefaultJettyConfiguration {
      def classpath = jettyRunClasspath
      def jettyClasspath = MyProject.this.jettyClasspath
      def war = jettyWebappPath
      def contextPath = jettyContextPath
      def classpathName = "test"
      def parentLoader = buildScalaInstance.loader
      def scanDirectories = Path.getFiles(MyProject.this.scanDirectories).toSeq
      def scanInterval = MyProject.this.scanInterval
      def port = jettyPort
      def log = MyProject.this.log
      def jettyEnv = jettyEnvXml
      def webDefaultXml = jettyWebDefaultXml
      override def callback = Some((server: AnyRef) =>  {
        val cl = server.getClass.getClassLoader
        val ssl = cl.loadClass("org.mortbay.jetty.security.SslSocketConnector").newInstance.asInstanceOf[{
          def setPort(p: Int): Unit
          def setMaxIdleTime(t: Int): Unit
          def setKeystore(s: String): Unit
          def setPassword(s: String): Unit
          def setKeyPassword(s: String): Unit
          def setTruststore(s: String): Unit
          def setTrustPassword(s: String): Unit
        }]
        val addConnector: java.lang.reflect.Method = server.getClass.getMethod("addConnector", cl.loadClass("org.mortbay.jetty.Connector"))

        ssl.setPort(8443)
        ssl.setMaxIdleTime(30000)
        ssl.setKeystore(info.projectPath + "/src/test/resources/keystore")
        ssl.setPassword("password")
        ssl.setKeyPassword("password")
        ssl.setTruststore(info.projectPath + "/src/test/resources/keystore")
        ssl.setTrustPassword("password")
        addConnector.invoke(server, ssl)
      })
  }
※上記はjetty6の場合で、jetty7の場合はパッケージ・クラス名がちょっと違います。

これで、jetty-runで8080でhttp、8443でhttpsが起動します。

2011年2月9日水曜日

[Scala][sbt]DefaultWebProjectでwarではなくjarも作りたい

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
DefaultWebProjectだとsbt packageタスクでwarが作られますが、jarも作りたい時があった場合、こんな感じで。
lazy val jar = packageTask(packagePaths, jarPath, packageOptions).dependsOn(compile) describedAs "Creates a jar file."
sbt jarでjarファイルが作られます。

2011年2月7日月曜日

[GAE] bulkloaderをGAE/Jで使う

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
python版appengine-sdkを使ってcsvファイルからデータをDatastoreにロードすることができます。

準備

web.xmlにremote_api用のServletの設定を書く

<servlet>
  <servlet-name>remoteapi</servlet-name>
  <servlet-class>com.google.apphosting.utils.remoteapi.RemoteApiServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>remoteapi</servlet-name>
  <url-pattern>/remote_api</url-pattern>
</servlet-mapping>
security-constraintも追加しておいた方が良さげ。
<security-constraint>
  <web-resource-collection>
    <url-pattern>/remote_api</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>admin</role-name>
  </auth-constraint>
</security-constraint>

ロード設定用のyamlファイルを書く

python_preamble:
- import: base64
- import: re
- import: google.appengine.ext.bulkload.transform
- import: google.appengine.ext.bulkload.bulkloader_wizard
- import: google.appengine.api.datastore
- import: google.appengine.api.users

transformers:

- kind: Scores
  connector: csv

  connector_options:
    encoding: ms932

  property_map:
    - property: __key__
      external_name: id
      import_transform: int

    - property: name
      external_name: name

    - property: score
      external_name: score
      import_transform: transform.none_if_empty(float)
  • キーにidではなく文字列(name)を使用する場合は、import_transform: intは不要。
  • import_transformに他に指定できる例
    日付: transform.import_date_time('%Y/%m/%d %H:%M')
    固定値: import_transform: lambda x:1
    数値変換: import_transform: lambda x:int(x)*100
    ※lambdaは引数の:の後にスペースを空けるとダメな様です

    ロード用CSV
    id,name,score
    1,Alice,5.0
    2,Bob,2.5
    3,Caroline,
    

    ロード用コマンド

    appcfg.py upload_data --filename=test.csv --config_file=test.yml --url=http://{appid}.appspot.com/remote_api --application={appid} --kind=Scores -v
    
    ※{appid}はアプリケーションIDに置き換える

    security-constraintの設定をしている場合、毎回管理者アドレス/パスワードの入力が必要です。
    ローカルのdev_appserverだとエラーが出て動きませんでした…

    こんな感じに反映されます。
    ※1万件だと約500秒(8分ちょっと)くらいかかりました(CPU Timeを無料Quotaの10%ほど消費します)

    その他注意点

  • slim3を使っている場合、filterの設定があると、"ProtocolBufferDecodeError: corrupted"の様なエラーが出てしまうので一時的にコメントアウトして回避しました。(※他にいい方法があれば教えて下さい…

    2/11追記: bulkload用にfilterをコメントアウトしたversionをデプロイしておいて、--url=http://latest.bulkload.{appid}.appspot.com/remote_api みたいに指定するといいかも

    3/22追記:slim3ではなくscenic3の方の設定でした。matcherで/remote_apiを除外したらOK。

    参考: Ikai Lan says / Using the bulkloader with Java App Engine
  • 2011年1月15日土曜日

    [Blogger] スマートフォン用テンプレートを有効にする

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
    いつの間にかβ版としてスマートフォン用テンプレートの機能が追加されてたので有効にしてみた。

    draft.blogger.com の方からログインするとβ版機能が使えます。

    有効にするには、設定 > メールとモバイル > モバイル テンプレートを表示する で はい を選ぶだけ。スマートフォンの場合は自動でリダイレクトしてくれるようです。

    ガラケーからもurlの最後に?m=1を付けるとモバイル用テンプレートで表示してくれましたが、ページ遷移にjavascriptが使われているようで残念ながら記事一覧以降進めませんでした…。

    2011年1月12日水曜日

    [Lift] Liftで処理したくないリクエストの設定

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
    web.xmlで全てのリクエストがLiftで処理されるようになっているので、Lift以外のサーブレット等で処理したい場合に困ります…
    <filter-mapping>
      <filter-name>LiftFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    Boot.scalaのdef bootにこんな感じで書けばOK。
    LiftRules.passNotFoundToChain = true
    LiftRules.liftRequest.append {
      case Req("_ah" :: _, _, _) => false
      case Req("remote_api" :: _, _, _) => false
    }
    

    ※filter-mappingで除外ルールが書ければいいんだけど…