2009年ふりかえり

2009年も残すところ3時間弱。今年は他人事だと思っていた勉強会の主催をやってしまったり、GAE/Jの登場以来、クラウドコンピューティングへ傾倒していくなど、とても刺激的な一年でした。

来年はより大きなことにも挑戦しつつ、基礎固めは忘れないようやっていきたいです。

Keep

  • 勉強会への積極的な参加
  • 勉強会の主催、スピーカーを継続してやっていく
  • Google App Engine Javaをしゃぶりつくす
  • 受け身の姿勢ではなく、積極的・自発的に行動する

Problem

  • 積極的に行動した結果、色々回らない部分も出てきた
  • 人前で話すことが苦手&人と話すことが苦手なのは治らなかった
  • まだまだ知識が足りていない

Try

  • インプット・アウトプットのバランスを取りつつ、学習量を増やす
  • Amazon EC2, Windows Azureといった技術にも触れる
  • 人ともう少し積極的に話せるようになる
  • レコメンデーションエンジンを作成する

勉強会等でお世話になった皆様、色々とありがとうございました。また来年もよろしくお願い致します。

名古屋SGGAE/J勉強会を開催しました

2009年12月26日に名古屋では恐らく初となる、Google App Engine Javaの勉強会を開催しました。

今回は日本Grails/Groovyユーザーグループ名古屋支部の@tyamaさんと、名古屋Scala勉強会の私とで、共同開催という形を取らせていただきました。きっかけはノリでした。ごめんなさい、ごめんなさい。

Google App Engine Java 入門 @tantack

最初のセッションは私が担当しました。Google App Engineに初めて触れる、という方の参加が多そうな雰囲気でしたので、後のセッションを理解しやすいよう入門的な内容がテーマ。

入門的な内容を盛り込みつつも、今年のGoogle App Engine Javaをふりかえる、といった試みをやってみたかった。しかし色々と中途半端になってしまいました。
今回は一切コードを載せないようにしたのですが、具体的にコードを挙げて説明、スクリーンショットを使いつつ導入方法を説明、ライブコーディングをやって見せる、と色々やり方があると思うので、次回以降に入門者向けのお話をする場合には、もう少し別のアプローチを考えてみます。

Lift on GAE/J @RKTMさん

資料:http://www.slideshare.net/rrrkitamura/20091226-sggaejlift-on-gaej
2番手は名古屋Scala勉強会を共同主催している@RKTMさんに無茶ぶりをして、この場に出ていただきました。

前半はScalaとLiftの紹介、名古屋Scala勉強会の宣伝でした。後半は本題であるLift on GAE/Jのお話で、Mavenを使えば簡単にLiftをGAE/J環境に乗せるための設定ができるよ、とのことでした。

LiftをGAE/Jで使うとリソースを馬鹿食いするのでは?との質問を投げたのですが、よく分からないということで、次回発表用のネタが今できましたね、と内心ほくそえんでいました。嘘です。調子乗ってすみませんでした。
ただ実際リソース消費量=お金に繋がるサービスなので、各種フレームワークを乗せることによって、リソース消費が増大するのではという懸念があります。このあたりの検証・比較ネタもあると面白そうですね。

GroovyなGAE/J:Gaelykでかんたんbot工作 @kazuchikaさん

資料:http://www.slideshare.net/kazuchika/groovygaej-gaelykbot
3番目のセッションは、Groovyイン・アクションやGaelykチュートリアルの邦訳者@kazuchikaさんによる、Gaelyk+GAE/Jでtwitter botを作るお話でした。

Gaelykフレームワークというほど大層なものではないようで、GAE/J上でGroovyを使いやすくするための簡易ツールキットとのこと。

そのGaelykで作成されたtwitter botは、モジュール形式をとっており、わずか数行コマンドを打ち込んだだけでtwitter botが生成されていました。
GroovyではXML処理やスクレイピングが簡単なので、その特徴を生かしてtwitterのみならずWEBの各所から情報を拾ってくることのできるモジュール、収集してきた情報を内容・重要度等でDatastoreに格納し、格納された情報を重要度等を元にフィルタリングするモジュール、フィルタリングしたデータをtwitterはじめ各所に出力できるモジュールで構成されているとのことでした。

cronを動かしていると時々失敗している、という話には自身も心当たりがあったため、失敗時にフラグを立ててリトライ処理をしているか質問してみたのですが、していないとのことでした。失敗したらリトライした方がいいのかは、気になっているところなのですが、それについては次回以降に持ち越しという形になりました。

あと出力先の一つとして挙げられていた、謎の喋るうさぎロボが懇親会で人気?だったようです。

GrailsGaelykでハンズオン @tyamaさん

資料:http://www.slideshare.net/tyama/sggaej-grailsgaelyk
Grails徹底入門著者の一人@tyamaさんのGrails1.1.1を使ってGAE/Jで動くアプリケーションを作ってみましょうというお話でした。本当はリリースされたばかりのGrails1.2.0を使いたかったけど、GrailsのGAE/J用プラグイン開発者がクリスマス休暇に入ってしまい、プラグインリリースが年明けに持ち越されて今回は出来なくなったとのこと。
Grails1.2.0では動作速度が1.1.x系の2倍、更にはapp engine 1.3.0でリフレクションの動作最適化が行われたことによって、速度が大幅に改善されるかも知れないとのことで、Grails1.2.0+GAE/Jの登場が楽しみです。

GrailsはSpringとHibernateを使っているので、どうやってGAE/Jで動かしているんだろ?と思っていましたが、専用のAppEngine PluginやGORM-JPAプラグインを使って問題を解決しているようです。

ライブコーディングを眺めていた感じでは、若干設定箇所のハマりどころや、書き換える箇所が多めで、Grailsの良さが殺されてるかも?と思ったりもしました。やはり現状では発展途上とのことで、なかなか思い通りにいかない箇所もあるようです。

簡単レジュメ生成サービス on GAE (markdown + GAE/J + Groovy) @tomoakioshimaさん

資料:http://osima.jp/diary/sggaej/index.html
唯一LTにエントリーしていただけた@tomoakioshimaさんによる、マークダウンを使ってHTMLを自動生成する「レジュメAnywhere」というサービスをGroovy+GAE/Jな環境で作ってみたよ、というお話でした。
マークダウンっていうのは、このダイアリーで使っている「はてな記法」みたいなもので、紙に印刷することが前提となるため、必要なところには改ページコードを埋め込むための書式を追加したとのこと。
紙媒体からWEBへ、が最近の流れではありますが、WEBから紙媒体への変換を前提としたアプリケーションも面白そうです。

反省点

  • 参加者チェックリストを用意していなかった。
  • 申込時にATNDの懇親会参加可否アンケートに気づかず、デフォルトの「参加」にチェックを付けたままにしてしまっている方が結構いた。
  • 進行がイマイチだった。盛り上げたり質問を促すのは難しい・・・。
  • 初の勉強会主催だったため、死ぬほど緊張した。
  • そもそもプログラミング始めて2年経ってない人間がこんなことやってていいの?もっと適任者がいるんじゃないの?という疑問。

次回に向けて

果たして名古屋でGoogle App Engineに取り組んでいる方がどの程度いるのか、スピーカーはどうやって集めよう、等いろいろと不安要素はありましたが、年末にも係わらず、最終的には29名の方に参加していただき、なんとか無事開催することができました。
参加者の皆様、スピーカーの方々、会場を提供してくださった株式会社ニューキャスト様、その他サポートしていただいた皆様ありがとうございました。

次回以降の開催予定ですが、年が明けて2010年の3月を予定しています。元々Javaのみではなく、GroovyやScalaも扱っていくよ、っていうコンセプトの勉強会ですので、JVMで動く言語のみでなくPythonも加えればいいんじゃないかという話になりました。ですので、特に言語は限定せず、Google App Engineについて話したい、聞きたいという方がいれば、誰でもOK!といった感じの勉強会になります。

既に次回勉強会のスピーカーやLTを希望される方を募集していますので、何かやらせろ!という方がいれば、私まで連絡をお願いします。

twitterと悪のOAuthというテーマでLTしてきました

2009年12月12日に行われた名古屋合同勉強会でLTしてきました。

テーマは「twitterと悪のOAuth」。twitterが来年6月にBasic認証を廃止し、OAuthに一本化することを受け・・・たわけでもありませんが、twitter上でOAuthを悪用した場合、どんなことが可能になり、どういった防衛策がとれるのかについてのお話をしてきました。

今回のLTでは、普段の名古屋式LTとは違って5分という厳しい時間制限がありましたので、だいぶ話す内容を絞り込んだつもりだったのですが、結局5分で話しきれず打ち切りになってしまいました。

また詳しいお話を後日書きたいと思っていますが、とりあえずLT資料だけアップしておきます。

Amazon Product Advertising APIを使ってみる

これから作成する予定のWebサービスAmazon Web Service APIが必要だったため、久しぶりにAPIを叩いて利用してみたところ、これまでの書き方では何やらエラーが出てきました。
なぜだ!?と調べてみたところ、今年の8月にAPIのサービス名が「Product Advertising API」となり、これまでよりも複雑な認証を行って、APIを呼び出すことが必要になっていたようです。


新しい方式について理解するため、Calculating Signature Version 2 Signaturesのサンプルコードを元に、Scalaを使ったGAE/J上で動く商品検索アプリケーションのサンプルを作成してみました。
フォームに検索ワードを入力すると、Amazonにある商品のなかからワードに近いものを取得、商品名だけを画面上に表示する簡易アプリケーションです。

検索結果イメージ。Scalaで検索したのに酷いありさまですね。


SignedRequestsHelper.scala

渡されたパラメータとアクセスキーを元に、APIを呼び出す認証用URLを作成するクラスです。サンプルのJavaコードそのまんまなので、少しずつScala向きにリファクタリングしていきたいです。
アクセスキーとシークレットアクセスキーは、個人のアカウントに依存するため、とりあえず0を並べてあります。アカウント持ってないよーって方は、こちらでアカウントを取得すれば、APIを使えるようになります。
あとApache Commons Codecを使っているので、ライブラリを持ってきて、クラスパスを通してやる必要があります。

package jp.tantack.yomitori

import java.io.UnsupportedEncodingException

import java.net.URLDecoder
import java.net.URLEncoder

import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException

import java.text.DateFormat
import java.text.SimpleDateFormat

import java.util.Calendar
import java.util.HashMap
import java.util.Iterator
import java.util.Map
import java.util.SortedMap
import java.util.TimeZone
import java.util.TreeMap

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

import org.apache.commons.codec.binary.Base64

class SignedRequestsHelper {
  private val UTF8_CHARSET: String = "UTF-8"
  private val HMAC_SHA256_ALGORITHM: String = "HmacSHA256"
  private val REQUEST_URI: String = "/onca/xml"
  private val REQUEST_METHOD: String = "GET"

  private val endpoint: String = "ecs.amazonaws.jp" // must be lowercase
  private val awsAccessKeyId: String = "00000000000000000000"
  private val awsSecretKey: String = "00000000000000000000"

  private var secretKeySpec: SecretKeySpec = null
  private var mac: Mac = null

  val secretyKeyBytes = awsSecretKey.getBytes(UTF8_CHARSET)
  secretKeySpec = new SecretKeySpec(secretyKeyBytes, HMAC_SHA256_ALGORITHM)
  mac = Mac.getInstance(HMAC_SHA256_ALGORITHM)
  mac.init(secretKeySpec)

  def sign(params: Map[String, String]): String = {
    params.put("AWSAccessKeyId", awsAccessKeyId)
    params.put("Timestamp", timestamp())

    val sortedParamMap = new TreeMap[String, String](params)
    val canonicalQS = canonicalize(sortedParamMap)
    val toSign = REQUEST_METHOD + "\n" + endpoint + "\n" + REQUEST_URI + "\n" + canonicalQS

    val hmac2 = hmac(toSign)
    val sig = percentEncodeRfc3986(hmac2)
    return "http://" + endpoint + REQUEST_URI + "?" + canonicalQS + "&Signature=" + sig
  }

  private def hmac(stringToSign: String): String = {
    var signature: String = null
    try {
      var data = stringToSign.getBytes(UTF8_CHARSET)
      var rawHmac = mac.doFinal(data)
      var encoder = new Base64(0)
      signature = new String(encoder.encode(rawHmac))
    } catch {
      case e: UnsupportedEncodingException => throw new RuntimeException(UTF8_CHARSET + " is unsupported!", e)
    }
    return signature
  }

  private def timestamp(): String = {
    var timestamp: String = null
    var cal: Calendar = Calendar.getInstance()
    var dfm: DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
    dfm.setTimeZone(TimeZone.getTimeZone("GMT"))
    timestamp = dfm.format(cal.getTime())
    return timestamp
  }

  private def canonicalize(sortedParamMap: SortedMap[String, String]): String = {
    if (sortedParamMap.isEmpty()) return ""

    var buffer = new StringBuffer()
    var iter: Iterator[Map.Entry[String, String]] = sortedParamMap.entrySet().iterator()

    while (iter.hasNext()) {
      var kvpair: Map.Entry[String, String] = iter.next()
      buffer.append(percentEncodeRfc3986(kvpair.getKey()))
      buffer.append("=")
      buffer.append(percentEncodeRfc3986(kvpair.getValue()))
      if (iter.hasNext()) {
        buffer.append("&")
      }
    }
    var cannoical = buffer.toString()
    return cannoical
  }

  private def percentEncodeRfc3986(s: String): String = {
    var out: String = null
    try {
      out = URLEncoder.encode(s, UTF8_CHARSET)
      .replace("+", "%20")
      .replace("*", "%2A")
      .replace("%7E", "~")
    } catch {
      case e: UnsupportedEncodingException => out = s
    }
    return out
  }

}

YomitoriServlet.scala

フォームの入力情報を受け取ったり、APIから返されたXMLをパースして表示用画面に遷移したりするクラスです。唯一Scalaらしいと言えるのは、XMLのパース処理でしょうか。

package jp.tantack.yomitori

import jp.tantack.yomitori.SignedRequestsHelper

import scala.io.Source
import scala.xml.XML
import java.util.HashMap
import java.util.ArrayList
import java.io.IOException
import javax.servlet.http.{HttpServlet
                           , HttpServletRequest => HttpReq
                           , HttpServletResponse => HttpResp}

class YomitoriServlet extends HttpServlet {

  /** アクセスキー */
  val accessKey = "00000000000000000000"
  /** バージョン */
  val version = "2009-11-15"
  /** オペレーション */
  val operation = "ItemSearch"

  override def doPost(req: HttpReq, resp: HttpResp) = doGet(req, resp)

  override def doGet(req: HttpReq, resp: HttpResp) {
    val keywords = req.getParameter("keywords")
    val searchIndex = req.getParameter("searchIndex")

    //APIの条件をセット
    val keyMap = new HashMap[String, String]()
    keyMap.put("AWSAccessKeyId", accessKey)
    keyMap.put("Version", version)
    keyMap.put("Operation", operation)
    keyMap.put("SearchIndex", searchIndex)
    keyMap.put("Keywords", keywords)
    //API呼び出しのURLを生成する
    val signedRequestsHelper = new SignedRequestsHelper()
    val urlStr = signedRequestsHelper.sign(keyMap)
    //XMLを取得してパースする
    val source = Source.fromURL(urlStr, "UTF-8")
    val xml = XML.loadString(source.mkString)
    val bookTitle = xml \\ "Title"
    //パースした結果をリストにセット
    var list = new ArrayList[String]()
    for(title <- bookTitle) list.add(title.text)
    req.setAttribute("list", list)
    //結果表示画面に遷移する
    val context = getServletContext()
    val reqDispatcher = context.getRequestDispatcher("/index.jsp")
    reqDispatcher.forward(req, resp)
  }
}

index.jsp

バリデーションとかをやっていない、ダメコードの典型例です。30分ぐらいで作ったやっつけ仕様のため、もう少し作り込まないと公の場には置けません。

<%@page pageEncoding="UTF-8" isELIgnored="false"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Product Advertising APIで商品検索</title>
</head>
<body>
<form method="POST" action="yomitori">
商品検索:<input type="text" size="20" name="keywords" />
カテゴリー:
<select name="searchIndex">
  <option value="All">すべて</option>
  <option value="Books">書籍</option>
</select>
<input type="submit" value="検索する" />
</form>

<hr />

<c:if test="${list != null}">
  検索結果: ${fn:length(list)} 件<br />
  <c:forEach var="title" items="${list}">
    商品名: <c:out value="${title}" /><br>
  </c:forEach>
</c:if>
</body>
</html>

API呼び出しのための認証が入ったことで、取っ掛かりの部分がこれまでよりもかなり面倒になっていますね。

Redmineが動かない?

UbuntuにRedmineをインストールしてみるでインストールしたRedmineが動かなくなっていました。

$ cd /home/tantack/development/redmine/redmine-0.8.6/
$ script/server -e production
=> Booting WEBrick...
=> Rails 2.1.2 application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options

Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

とのこと。MySQL関連のトラブルでも頻出するもののようです。

以下、追記中。

UbuntuにRedmineをインストールしてみる

最近趣味でやっている開発や読んでいる書籍について、進捗状況の把握が難しくなってきたため、そういったものの管理をチケットでやってみることにしました。

こちら方面のツールとして有名なものは、TracRedmineでしょうか。両者について情報を集め、それを元に検討した結果、導入は大変そうだけど、運用面では軍配の挙がりそうなRedmineを使ってみることにしました。

どのような場所からでも利用可能な、Amazon EC2といったクラウド環境下で動かすことが理想ではありますが、今回はUbuntu9.10を使ったローカル環境へのインストールを行います。

Rubyのインストール

RedmineRuby on Railsによって開発されているようですので、まずはRuby周りのパッケージからインストールします。

$ sudo apt-get install ruby ruby-dev irb rdoc ri

インストールが完了したら、バージョンを確認。

$ ruby --version
ruby 1.8.7 (2009-06-12 patchlevel 174) [i486-linux]

RubyGemsのインストール

RubyGemsってなに?と思って調べたところ、Rubyの標準パッケージ管理ツールのようです。UbuntuでいうSynaptic パッケージ・マネージャみたいなもの?

ここからrubygems-x.x.x.tgzを拾ってきます。

$ tar xzvf rubygems-1.3.5.tgz

解凍したら、ディレクトリを移動してsetup.rbを実行します。

$ cd rubygems-1.3.5/
$ sudo ruby setup.rb

インストールに必要なパッケージが不十分だと怒られます。

RubyGems 1.3.5 installed
./lib/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- rdoc/rdoc (LoadError)
	from ./lib/rubygems/custom_require.rb:31:in `require'
	from ./lib/rubygems/commands/setup_command.rb:352:in `run_rdoc'
	from ./lib/rubygems/commands/setup_command.rb:247:in `install_rdoc'
	from ./lib/rubygems/commands/setup_command.rb:120:in `execute'
	from ./lib/rubygems/command.rb:257:in `invoke'
	from ./lib/rubygems/command_manager.rb:132:in `process_args'
	from ./lib/rubygems/command_manager.rb:102:in `run'
	from ./lib/rubygems/gem_runner.rb:58:in `run'
	from setup.rb:35

ものすごい勢いで怒られました。ごめんなさい。

バージョンを確認。

$ gem1.8 --version
1.3.5

Ruby on Railsのインストール

ここでRubyGemsを使って、Ruby on Railsに必要なもの一式をインストールできるわけですね。なるほどなるほど。

$ sudo gem1.8 install rails

バ(ry

$ rails --version
Rails 2.3.4

MySQLのインストール

Redmineで利用できるRDBMSは、MySQL, PostgreSQL, SQLiteの3つのようです。Redmineの開発に使われているRDBMSMySQLということで、無難にMySQLを選択しておきました。

$ sudo apt-get install mysql-client mysql-server libmysqlclient15-dev

途中でmysql-serverのrootパスワード設定を求められるので、パスワードを設定してやります。

インストールが完了したら、デフォルトの文字エンコーディングを設定してやります。

$ sudo vim /etc/mysql/my.cnf

vimを起動したら、"i"を押して挿入モードへ、以下の箇所にUTF-8の記述を行います。

[mysqld_safe]
default-character-set = utf8
[mysqld]
default-character-set = utf8
[mysql]
default-character-set = utf8

編集が終わったら"Esc"でモードを抜け、":w"で保存、":q"で終了。

早速MySQLサーバを起動します。

$ /etc/init.d/mysql start
 * Starting MySQL database server mysqld

お好みでUbuntuが起動した際に、MySQLサーバが自動起動するよう設定してやります。Debian系のUbuntuでは、chkconfigではなく、sysv-rc-confというデーモン管理ツールを使うのが一般的なようです。

$ sudo apt-get install sysv-rc-conf
$ sudo sysv-rc-conf mysqld on
$ sudo sysv-rc-conf --list mysqld
mysqld       2:on	3:on	4:on	5:on

続いてRubyMySQLにアクセスするためのドライバをインストールしてやります。

$ sudo gem1.8 install mysql -- --witdh-mysql-lib=/usr/lib/mysql
Building native extensions.  This could take a while...
Successfully installed mysql-2.8.1
1 gem installed

これでインストールは完了です。

Redmine用データベースとユーザーの作成

Redmineで利用するデータベースとユーザーを作成してやります。

$ mysql -uroot -p
Enter password: MySQLのrootパスワード入力
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 44
Server version: 5.1.37-1ubuntu5 (Ubuntu)

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database データベース名 default character set utf8;
Query OK, 1 row affected (0.00 sec)

mysql> grant all privileges on データベース名.*
    -> to ユーザー名 identified by 'パスワード';
Query OK, 0 rows affected (0.00 sec)

mysql> exit;
Bye

Redmineのインストール

既に「もうぐったりだよ!」って感じなのですが、ここからが本番です。
ここからredmine-x.x.x.tar.gzを拾ってきます。

$ tar zxvf redmine-0.8.6.tar.gz

解凍したら、インストール先のディレクトリに放り込んでやります。以下はインストール先の例。

$ mv redmine-0.8.6 /home/tantack/development/redmine/
$ cd /home/tantack/development/redmine/redmine-0.8.6/

次は先ほど設定したデータベース名等にあわせて、database.ymlを書き換えてやります。

$ cd config/
$ cp database.yml.example database.yml
$ sudo vim database.yml

以下はdatabase.ymlの設定例。

production:
  adapter: mysql
  database: データベース名
  host: localhost
  username: ユーザー名
  password: パスワード
  encoding: utf8

development:
  adapter: mysql
  database: データベース名
  host: localhost
  username: ユーザー名
  password: パスワード
  encoding: utf8

ここではエディタにvimを使っていますが、自分の使いやすいエディタで編集して下さい。

設定を終えたら、Redmineのインストールディレクトリに戻ります。

$ cd ../

続いてデータベースの初期化を行います。

$ rake db:migrate RAILS_ENV=production
rake aborted!
no such file to load -- openssl

なんか怒られました。本のうそつき!

調べて見たところ、Ruby用のopenssl?なるものが存在するとのことなので、それをインストールしてみます。

$ sudo apt-get install libopenssl-ruby

で、リベンジ。

$ rake db:migrate RAILS_ENV=production
$ rake redmine:load_default_data RAILS_ENV=production

Select language: bg, ca, cs, da, de, en, es, fi, fr, he, hu, it, ja, ko, lt, nl, no, pl, pt, pt-br, ro, ru, sk, sr, sv, th, tr, uk, vn, zh, zh-tw [en] ja
====================================
Default configuration data loaded.

やった!通った!!言語を選べと言われたので、"ja"を選択しました。

さあ、いよいよRedmineを起動し、動作確認をしてみましょう

$ script/server -e production
=> Booting WEBrick...
=> Rails 2.1.2 application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2009-11-08 23:21:24] INFO  WEBrick 1.3.1
[2009-11-08 23:21:24] INFO  ruby 1.8.7 (2009-06-12) [i486-linux]
[2009-11-08 23:21:24] INFO  WEBrick::HTTPServer#start: pid=11926 port=3000

無事起動しました。ブラウザを立ち上げて、以下のアドレスを打ち込みます。

http://localhost:3000/


キター!長かった・・・!

本格運用に向けて

動作確認に使用したWebサーバは、WebrickというRuby付属のWebサーバのようです。今回のように動作確認に使用する程度なら問題はありませんが、同時に一つのHTTPリクエストしか処理できないとのことなので、チームでの運用には向きません。
そこでPassengerというRuby on Railsアプリケーションを実行するためのApacheモジュールを利用し、RedmineApache上で動作させることが、チームでの運用には望ましいようです。


PassengerによるApache上でのRedmine動作は次回以降に回したいと思います。
さすがに疲れた記事が長すぎますので。

参考

入門Redmine Linux/Windows対応

入門Redmine Linux/Windows対応

UbuntuからAndroidアプリを実機にインストールしてみた

Androidのセンサーを利用したアプリケーションのテストをすべく、手持ちのHT-03AUbuntu上から自作アプリをインストールしてみました。Windowsでは結構簡単にインストールできたのですが、Linuxの場合は一手間いるようです。

インストール対象のHT-03AはAndroid1.6入り、開発環境はUbuntu9.10、ADTプラグイン導入済みのEclipseといった感じです。


まずは実機であるHT-03Aに自作アプリをインストールできるよう設定を変更します。

  1. メニューもしくはアプリケーション一覧から"設定"を選択。
  2. 設定画面から"アプリケーション"を選択し、アプリケーション設定画面を開く。
  3. "提供元不明のアプリ"をチェックする(これをチェックしないとインストールできませんでした)。
  4. 同画面内にある"開発"を選択し、開発画面を開く。
  5. "USBデバッグ"をチェックする。
以上でHT-03A側の設定は完了です。


続いてUbuntuの設定。ここを設定してやらないと、USB接続しても開発環境がHT-03Aを認識してくれません。

  1. "/etc/udev/rules.d/"ディレクトリ配下に、以下の内容で"51-android.rules"ファイルを作成する。

    SUBSYSTEM=="usb", SYSFS{idVendor}=="0bb4", MODE="0666"


  2. 以下のコマンドを実行する。

    $ sudo chmod a+rx /etc/udev/rules.d/51-android.rules


  3. HT-03AをUSB接続する。

  4. Android SDKにパスを通した状態で、以下のコマンドを入力し、実機が認識されていることを確認する。

    $ adb devices
    HT********	device


うまくいかなかったら、適当にググって解決してください。


Android SDKにパスが通ってない場合は、以下のコマンドを打ち込んでやります。

$ PATH="$PATH:[Android SDKを置いたディレクトリ]/tools"


あとはEclipse上からAndroidアプリを作成、実行することで、HT-03Aにアプリがインストールされました。USB接続を解除してもアプリが消えることはなかったので、削除したい場合は、HT-03Aの方から削除してやる必要があるのかもしれません。

実機とEclipseを使ってデバッグモードで動かしたい場合には、"AndroidManifest.xml"から"Application"タブを選択し、項目"Debuggable"を"true"としてやればOKです。XMLファイルを直接編集してやってもOK。ちゃんとブレークポイントで止まってくれました。


以下実際にインストールしてみたアプリケーションです。今日はScalaじゃなくてJavaなソース。本当はScalaでやりたかったけど、どうしてもエラーの解決できない箇所があって断念。
内容的にはHT-03Aに搭載されているセンサーを利用し、加速度と方位の変化を数値として出力してやるだけのものです。

GSensor.java

package jp.tantack.gs;

import java.util.List;

import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;

public class GSensor extends Activity implements SensorEventListener {

	/** センサーマネージャー */
	private SensorManager manager;
	/** 加速度センサー */
	private Sensor accSensor;
	/** 方位センサー */
	private Sensor oriSensor;
	/** コンテンツビュー */
	private ContentView view;

	/** 初期化 */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		//ビュー設定
		view = new ContentView(this);
		setContentView(view);
		//センサーマネージャー取得
		manager = (SensorManager)getSystemService(SENSOR_SERVICE);
		//センサー取得
		List<Sensor> sensor = manager.getSensorList(Sensor.TYPE_ACCELEROMETER);
		accSensor = sensor.size() > 0 ? sensor.get(0) : null;
		sensor = manager.getSensorList(Sensor.TYPE_ORIENTATION);
		oriSensor = sensor.size() > 0 ? sensor.get(0) : null;
	}

	/** アプリケーション開始 */
	@Override
	public void onResume() {
		super.onResume();
		//センサーのリスナーを登録する
		if(accSensor != null) manager.registerListener(this, accSensor, SensorManager.SENSOR_DELAY_NORMAL);
		if(oriSensor != null) manager.registerListener(this, oriSensor, SensorManager.SENSOR_DELAY_NORMAL);
	}

	/** アプリケーション終了 */
	@Override
	public void onStop() {
		//センサーのリスナーを解除する
		manager.unregisterListener(this);
		super.onStop();
	}

	/** センサーの値が変化した時の処理 */
	@Override
	public void onSensorChanged(SensorEvent event) {
		//センサーの種類毎に値を設定する
		if(event.sensor == accSensor) view.setAcc(event.values);
		if(event.sensor == oriSensor) view.setOri(event.values);
		//描画開始
		view.invalidate();
	}

	/** センサーの精度が変化した時の処理 */
	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {}

}

ContentView.java

package jp.tantack.gs;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;

public class ContentView extends View {

	/** 加速度 */
	private float[] acc = new float[3];
	/** 方位 */
	private float[] ori = new float[3];

	/** コンストラクタ */
	public ContentView(Context context) {
		super(context);
		//ビューの背景色を設定
		setBackgroundColor(Color.WHITE);
	}

	/** 加速度を設定 */
	public void setAcc(float acc[]) { this.acc = acc; }

	/** 方位を設定 */
	public void setOri(float ori[]) { this.ori = ori; }

	/** 描画処理 */
	@Override
	public void onDraw(Canvas canvas) {
		Paint paint = new Paint();
		paint.setAntiAlias(true);
		paint.setTextSize(15);
		//文字配置座標計算用
		int x = 0;
		int y = 20;
		//キャンバスに表示内容を描画
		canvas.drawText("加速度-X軸" + acc[0], x, y * 1, paint);
		canvas.drawText("加速度-Y軸" + acc[1], x, y * 2, paint);
		canvas.drawText("加速度-Z軸" + acc[2], x, y * 3, paint);
		canvas.drawText("方位" + ori[0], x, y * 4, paint);
		canvas.drawText("ピッチ" + ori[1], x, y * 5, paint);
		canvas.drawText("ロール" + ori[2], x, y * 6, paint);
	}

}


わりと簡単に実機へのインストールができますね。

参考

Developing on a Device
加速度センサーの利用

GOOGLE ANDROIDアプリケーション開発入門

GOOGLE ANDROIDアプリケーション開発入門