<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
	<channel>
		<title>bits and bytes</title>
		<link>http://labs.gmo.jp/blog/ku/</link>
		<description>GMOインターネットラボの熊谷健太郎が bit と byte について語ります。</description>
		<language>ja</language>
		<copyright>Copyright 2008</copyright>
		<lastBuildDate>Fri, 29 Aug 2008 13:11:03 +0900</lastBuildDate>
		<generator>http://www.sixapart.com/movabletype/</generator>
		<docs>http://blogs.law.harvard.edu/tech/rss</docs>
					<item>
				<title>Firebugクックブック #2</title>
				<description><![CDATA[<a href="http://labs.gmo.jp/blog/ku/2008/08/firebug_1.html">前回</a>に引き続き、自分がよくあるブラウザ上での単純作業をFirebugコンソールで片付けている方法の紹介です。前回はブラウザに表示されているデータをこっち側に持ってくる作業をどうやっやるかでした。今回はブラウザの向こう側のデータをFirebugで操作するにはどうするかです。

<h3>ボタンをクリックさせる</h3>
ウェブのサービスを使っていて、今までに投稿したものを全部消したくなったり(でもアカウントは消したくないとか)することがあります。管理画面で投稿を消すことはできるけど、いちいちクリックしていかないといけなくて面倒.... という時にはFirebugでフェイクのクリックイベントを作って送れば、自動でクリックさせることができます。

Twitterのfavoritesを全部外したい、というのを例としてあげます。TwitterのfavoritesはAPIがあるのでAPIを使う手もありますが、サービスによってはAPIが使えないこともよくありますよね。まずFirebugでHTMLを調べると
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%202-30.png" height="405" width="630" hspace="4" vspace="4" class="border" />
</div>
こういう構造になっています。favoriteを外すためのリンクを持っている<code>a</code>タグは <code>div.status_actions a</code> というCSSセレクタで表すことができるので
<pre><code>$$('div.status_actions a').map ( function (a, i) {
    setTimeout( function () {
      var e = document.createEvent('MouseEvent');
      e.initMouseEvent("click", 1,1, null,null,0,0,0,0,0,0,0,0,0,null);
      a.dispatchEvent(e)
    }, i * 1000);
} );
</code></pre>
<a href="http://developer.mozilla.org/en/DOM/document.createEvent">document.createEvent</a>と<a href="http://developer.mozilla.org/En/DOM:event.initMouseEvent">event.initMouseEvent</a>を使って<code>a</code>タグにクリックイベントを送信すれば、自分でクリックしないでfavoriteを外すことができます。この方法だとFirefoxに表示されているだけのfavoritesしか外せません。1ページに表示されている量よりも多い量を対象にしたい時は<a href="http://userscripts.org/scripts/show/8551">AutoPagerize</a>を使って、対象にしたい量だけ読み込んでから実行すると、1ページに収まらない量を対象にすることができます。

<code>setTimeout</code>の中で実行しているのは、一度に大量のリクエストをサーバに送って迷惑をかけたりしないようにするためです。大量にリクエストを送ることになるときには必ず入れましょう。


<h4>クリックしたあと確認のダイアログが出るとき</h4>
twitterのfavoriteのように、クリックしたら即操作が反映されるものは比較的少数です。削除のように元に戻せない操作のときにはjavascriptで確認が表示されることが多いです。Flickrで写真を削除しようとしたときには確認画面が出てきて、けっきょくまたクリックしないといけなくなったりします。
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%203-30.png" height="251" width="610" hspace="4" vspace="4" class="border" /></div>
そういうときにはこの確認画面を出している<code>confirm</code>関数を上書きしてしまいましょう。Firebugコンソールで
<pre><code>window.confirm = function () {
  return true;
}
</code></pre>と書いて一度実行しておけば、<code>confirm</code>は出てこなくなって、常にOKを押したことにすることができます。(参考: <a href="http://p0t.jp/archives/2008/08/google-4.html">Googleマップのアラートを消す最悪の方法 - p0t</a>)

<h3>HTTPリクエストを送る</h3>
クリッイベントを作るだけじゃうまくいかない、クリックしたあと別のページに遷移してしまってスクリプトの実行が中断されてしまう、そういうときはもう<code>XMLHttpRequest</code>を使ってリクエストを作って送ります。

<a href="http://d.hatena.ne.jp/amachang/20080822/1219383366">wassr で全 follow するブックマークレット - IT戦記</a>がちょうどそれをやっているので、まねして<a href="http://friendfeed.com/settings/recommended">FriendFeed - Recommended Friends</a>でお勧めされているひとをsubscribeするというFriendFeedバージョンを作るのを例にします。


まず<a href="https://addons.mozilla.org/ja/firefox/addon/3829">Live HTTP Headers :: Firefox Add-ons</a>を使って、送信されているリクエストを確認します。
<pre><code>POST /account/newfriends HTTP/1.1

Host: friendfeed.com

Content-Type: application/x-www-form-urlencoded; charset=UTF-8

X-Requested-With: XMLHttpRequest

Referer: http://friendfeed.com/settings/recommended


id=06c27bf8-2cfd-11dd-be28-003048343a40&at=12419835770643829971_1218795910
</code></pre>
subscribeボタンを押すとこういうリクエストがPOSTで送信されています。まずはPOSTのパラメータになっている<code>id</code>と<code>at</code>をページの中から探してきましょう。

ユーザのIDを示しているGUIDみたいな<code>id</code>は<code>td</code>タグに<code>userid</code>という属性で示されていました。
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%205-20.png" height="220" width="264" hspace="4" vspace="4" alt="Picture 5-20" class="border" />
</div>
<code>at</code>は調べてみたらcookieの中に入っている値でHTMLには入ってないのですが<code>getCookie('AT')</code>で得ることができました。材料が揃ったところで、あとはコードを書けばいいだけです。

<pre><code>$x('//td[@userid]').map ( function (e, i) {
  setTimeout( function () {
    var body = [
     "id=" + e.getAttribute("userid"),
     "at=" + getCookie("AT")
    ].join("&");
    var r = new XMLHttpRequest();
    r.open("POST", "/account/newfriends", false);
    r.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    r.send(body);
  }, i * 1000);
} )
</code></pre>

<code>XMLHttpRequest</code>は同じドメインにしかリクエストを送れないので、同じドメインである<a href="http://friendfeed.com/settings/recommended">FriendFeed - Recommended Friends</a>を開いて、Firebugでこのスクリプトを実行すると、おすすめされたひとをみんなsubscribeすることができます。


<h3>あとがき</h3>
Firebugを使うと、クッキーで認証がかかっていて、ほかのスクリプト言語でコードを書くにはめんどくさい、というときにも、作業そのもののコードを書くだけですませられるので簡単です。最近はJSONやJSONPで結果を返してくれるAPIも増えたので、ますますFirebugは細かい作業をこなすのに使いやすくなっています。

]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/08/firebug_2.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/08/firebug_2.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">Firefox</category>
											<category domain="http://www.sixapart.com/ns/types#tag">javascript</category>
					
				
				<pubDate>Fri, 29 Aug 2008 13:11:03 +0900</pubDate>
			</item>
					<item>
				<title>Firebugクックブック #1</title>
				<description><![CDATA[最近の中学生の<a href="http://d.hatena.ne.jp/amachang/20080818/1219046394">はじめてのプログラミング言語がJavaScript</a>だったりするこの時代、最も使いやすいJavaScriptの実行環境である<a href="http://getfirebug.com/">Firebug</a>は現代のコマンドラインです。UNIXコマンドラインで<code>grep</code>や<code>uniq</code>を使って、日常の細々した処理を行うのと同じようにFirebugとjavascriptを使いこなせると、日常作業のちょっとしたことをさくっとこなすことができます。ちょっとした作業だから手作業でやってもいいけど自動でやればミスったりしないし、気分的には楽なので自動でやりたい、という作業がけっこうないでしょうか。例えば、ページの中の特定の部分の文字列をリストにしてテキストファイルに保存したい、とか。

そこで今回は私が普段よくやっている単純作業をFirebug+javascriptでさくっとかたづける方法を2回にわけてご紹介します。

<h3>ページの中からテキストや属性の値を拾う</h3>
ページの中から特定の部分の文字列を抜き出して、特定の条件に当てはまるものだけをリストアップしてファイルに保存したい、ということがないでしょうか。perl+<a href="http://search.cpan.org/~miyagawa/Web-Scraper/">Web::Scraper</a>でやることもできますが、ページにcookieなどで認証がかかっている場合ログイン処理を書くのがちょっと面倒です。書いたスクリプトを何回も使うならスクリプトを書く甲斐もありますが、単に今ちょこっとページからデータを取り出したいだけで二度と使わないようなときや、ページがjavascriptで動的に生成されていてHTMLファイルを持ってきただけでは手も足も出ない、というような時にはFirebugの出番です！ブラウザはHTMLをレンダリングするためにあるようなものですしHTMLもブラウザで表示するために書かれているようなものです。ブラウザの上であれば、タグの構造や要素がページ上のどこにあるのかを目で見て確認しながら作業ができるのでブラウザできることならブラウザでやるとストレスなくすすめられます。


例として、このブログの左側にあるタグクラウドのタグ名の横についている数字を取り出して、全部足す、というのをやってみましょう。
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%206-14.png" height="568" width="580" hspace="4" vspace="4" alt="Picture 6-14" class="border" /></div>
HTMLをみるとこうなっています。<em>li</em>の下の<em>a</em>の<em>sup</em>の中に数字が入っているので<em>sup</em>を全部取り出して足し算すれば数がわかります。
<pre><code>var total = 0;
var a = $x("//li/a/sup");
for ( var i = 0; i &lt; a.length; i++ )
    total += parseInt( a[i].textContent );
total // 210
</code></pre>
XPathで対象の<code>sup</code>要素を全部取り出して、その中の数字を足しています。<code>textContent</code>プロパティには要素の子孫にあるテキスト全部が入っています。そのまま<em>+</em>演算子で足していくとタグの数が文字列として連結されていってしまうので<code>parseInt()</code>を使って数値として足し算されるようにしています。

<a href="http://labs.gmo.jp/blog/ku/2008/06/webkitjavascript16array.html">WebKitでサポートされているJavaScript1.6のべんりなArrayメソッド</a>で紹介した<a href="http://developer.mozilla.org/ja/docs/New_in_JavaScript_1.6#Array_extras">Arrayの拡張メソッド</a>を使って
<pre><code>var total = 0;
$x("//li/a/sup").map ( function (a) {
 return total += parseInt( a.textContent );
} ).pop(); // 210
</code></pre>
と書くことも可能です。<span style="text-decoration: line-through;">(Firebug consoleではjavascript1.6までしか使えないようなので<code>reduce</code>は使えません)。</span>Firefox3であれば使えます(ref. <a href="http://twitter.com/nanto_vi/statuses/896206713">Twitter / TOYAMA Nao: @ku http://tinyurl.com/5qf4...</a>)。

<h3>CSSセレクタを使う</h3>
上の例ではXPathを使って要素を選択しましたが、XPathはクラス名を指定して何か取り出したい時には <code>div[@class="pager"]</code> というふうに書かないといけません。それに比べてCSSセレクタなら同じことを <code>div.pager</code> とかけるのですごく楽です。

こんどは<a href="http://b.hatena.ne.jp/entry/http://labs.gmo.jp/blog/ku/2008/03/_firebug_tips.html">はてなブックマーク - いまさら人に聞けない Firebug tips</a>につけられたタグを全部リストアップしてみましょう。つけられている数が少ないタグは"タグ"のところにはでてこないので、各ブックマークにつけられたタグを全部とりだします。
<div><img src="http://labs.gmo.jp/blog/ku/Picture%2011-7.png" height="252" width="404" hspace="4" vspace="4" alt="Picture 11-7" class="border" /></div>
こういうHTMLなので、こんどはCSSセレクタを使って取り出します。

<pre><code>var stash = {};
$$("a.user-tag").map ( function (a) {
    stash[a.textContent] = stash[a.textContent] ? 
            stash[a.textContent] +1 : 1;
} );
stash
</code></pre>
これで、タグ名をキー、タグの総数が値になったハッシュを取り出すことができます。
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%2012-6.png" height="52" width="401" hspace="4" vspace="4" alt="Picture 12-6" class="border" /></div>

<h3>作った文字列をクリップボードにコピーする</h3>
値を取り出せても所詮はjavascriptでそれをファイルに保存することができません。これは致命的です。が、Firebugコンソールには<code>copy()</code>という、渡された文字列をOSのクリップボードにコピーする関数があります。

さっき作ったタグ名とタグの数ハッシュを、タブ区切りのファイルにして保存したい、というのを想定してみましょう。
<pre><code>copy(
  keys(stash).map ( function (k) {
    return [k, stash[k]].join("\t")
  } ).join("\n")
);
</code></pre>
Firebugには<code>keys()</code>というperlの<code>key</code>に相当する関数があり、ハッシュからキーだけを配列にして返してくれます。これを使って全部のキーと値を取り出して、<code>join()</code>で改行をはさんで連結してクリップボードに保存しています。

クリップボードにデータが入ったら適当なエディタに張り付けて保存すればタブ区切りのファイルのできあがりです(タブがみにくくてすみません)。
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%2013-4.png" height="608" width="680" hspace="4" vspace="4" alt="Picture 13-4" class="border" /></div>
スクリプトからは扱いにくいHTMLからはFirebug+javascripを使えば、自分でログインするためのコードを書いたりする必要もなくこうやってデータを取り出すことができます。TSVなどの扱いやすいデータになればあとはエクセルに入れてグラフを作ることも、ほかのスクリプト言語で読み込むことも簡単にできます。


<h3>つづく</h3>
今回はウェブ上にあるデータを、Firebug consoleを使って効率よく編集する方法をご紹介しました。
Firebugを使えば、HTMLに使われるすべてのタグのリストをjavascriptの配列として持っておきたい、というときにも<a href="http://www.asahi-net.or.jp/~SD5A-UCD/rec-html401j/index/elements.html.ja.sjis">Index of the HTML 4 Elements</a>を開いて
<pre><code>$$('table a').map ( function (a) { return a.textContent })
</code></pre>と入力するだけで、簡単に取り出すことができます。


ウェブとコンピュータとを繋ぐブラウザは現代のコマンドラインです。ちょっとでもコマンドラインを使えると飛躍的に細かい作業がはかどるのと同じように、Firebugで細かい作業ができるようになると、ウェブの情報を簡単に利用することができるようになります。


次回はもう一歩進めてFirebugからHTTPリクエストを送ったりします。それではまた。]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/08/firebug_1.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/08/firebug_1.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">Firefox</category>
											<category domain="http://www.sixapart.com/ns/types#tag">javascript</category>
					
				
				<pubDate>Fri, 22 Aug 2008 21:00:07 +0900</pubDate>
			</item>
					<item>
				<title>日時表記のフォーマットの名前とperl/PHP/javascriptでのつくりかた</title>
				<description><![CDATA[毎回毎回日時を出力する時にどうすればいいのか思い出せなくて毎回毎回調べていてばかみたいなのできちんとまとめてみます。


<h3>日時のフォーマット名</h3>
まず、よく使われる日時表記の名前を把握することが大切です。名前がわからないと検索のしようがありません。

<h4>ISO8601</h4>
ISO8601はたぶん一番なじみが深いものだと思います。
MySQLのdatetime型の表記 <code>2008-08-06 19:38:56</code> はISO8601です。

ISO8601は日時を表すだけでなく、年だけや時刻だけを表せるほか、期間を表すことができます。また、多様な書き方を許していて、間にある記号を省略して<code>20080806 193856</code>というのもISO8601として正しい日時になります。英語のwikipediaの<a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>が詳しいです。


<h4>W3CDTF</h4>
W3CDTFはatomのフィードで使われている <code>2003-12-13T18:30:02Z</code> みたいなやつです。
この書式は実はISO8601に含まれているのですが(なのでISO8601がパースできるパーサならW3CDTFもパースできます)、仕様を決める時に日付の書式をISO8601にすると、ISO8601が多様な書式を許していることで実装が煩雑になってしまいます。それを避けるために導入されたものだそうです。

<h4>RFC2822/822</h4>
RFC2822/822は日本人には読みにくい <code>Mon, 23 Apr 2007 11:10:53 +0900</code> という形式のものです。個人的には、曜日はいらないだろ、と毎回思っています。メールやHTTPのヘッダ、RSS2.0の日時表記に使われています。

<h4>ctime</h4>
ctimeはPOSIXで規定されている<code>asctime</code>というCの関数が出力するフォーマットです。こんな <code>Thu Feb 3 17:03:55 GMT 1994</code> かんじでRFC2822/822と似ていますが、月の名前が月の日付よりも先にきているものです。

ブラウザ上で何かする時にはjavascriptの<code>Date</code>オブジェクトの<code>toString()</code>の結果くらいでしかお目にかかることはありませんが、ブラウザ以外のC言語で実装されているソフトウェアではよく使われています。身近なところではapacheのエラーログの日付表記がctimeになっています。

<h3>各言語での作り方</h3>
以下は自分が普段よく使うperl, PHP, javascriptでどうやったら各日付のフォーマットを作れるかのメモです。もっといい方法、ほかの言語ではどう書くか、など教えていただけたらうれしいです。
<h4></h4>

<table border=1>
  <tbody>
    <!-- Results table headers -->
    <tr>
      <th></th>
      <th>ISO8601<br />(2008-08-08 01:11:05)</th>
      <th>W3CDTF</th>
      <th>RFC2822</th>
      <th>ctime</th>
    </tr>
    <tr>
      <td>perl</td>
      <td>$_ = DateTime<nobr>-&gt;</nobr>now<nobr>-&gt;</nobr>datetime; s/T/ /; </td>
      <td>DateTime::Format::W3CDTF<nobr>-&gt;</nobr>
format_datetime(  DateTime<nobr>-&gt;</nobr>now )</td>
      <td><nobr>DateTime::Format::Mail<nobr>-&gt;</nobr>
format_datetime(  DateTime<nobr>-&gt;</nobr>now )</td>
      <td></td>
    </tr>
    <tr>
      <td>PHP</td>
      <td>preg_replace('/T|[\+Z].+/', ' ', date('c'))</td>
      <td>date('c')</td>
      <td>date('r')</td>
      <td></td>
    </tr>
    <tr>
      <td>javascript</td>
      <td>ISO8601DateUtils.jsm(extensionのみ)<br /></td>
      <td>ISO8601DateUtils.jsm(extensionのみ)<br /></td>
      <td></td>
      <td>(new Date).toString()</td>
    </tr>
  </tbody>
</table>

テーブルは<a href="http://tablegen.nfshost.com/">Generate HTML Tables Clean and Fast</a>でつくりました。地味ですが、タブで隣のセルに移動できたり、地味に便利でした。


<h3>参考</h3>
<ul>
<li><a href="http://www.kanzaki.com/docs/html/dtf.html">日付の表記に関するノート</a></li>
<li><a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601 - Wikipedia, the free encyclopedia</a></li>
<li><a href="http://cyber.law.harvard.edu/rss/rss.html">RSS 2.0 Specification (RSS 2.0 at Harvard Law)</a></li>
<li><a href="http://www2.airnet.ne.jp/sardine/docs/NOTE-datetime-19980827.html">NOTE-datetime</a></li>
<li><a href="http://www.ietf.org/rfc/rfc4287.txt">RFC4287 The Atom Syndication Format</a></li>
<li><a href="http://ido.nu/kuma/2007/05/09/datetimeformat-summary/">CPAN DateTime::Formatまとめメモ « ku</a></li>
</ul>]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/08/perlphpjavascript.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/08/perlphpjavascript.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">PHP</category>
											<category domain="http://www.sixapart.com/ns/types#tag">javascript</category>
											<category domain="http://www.sixapart.com/ns/types#tag">perl</category>
					
				
				<pubDate>Fri, 08 Aug 2008 19:00:16 +0900</pubDate>
			</item>
					<item>
				<title>大切じゃないパスワードを記録するためのiPhone用ブックマークレットとclient-side storageの可能性</title>
				<description><![CDATA[<a href="http://jsgt.org/mt/archives/01/002154.html">JavaScript++かも日記: 【iPhone】iPhone用 JavaScriptデータベースプログラミング入門 (1)</a>でiPhoneのSafariでもclient-side storageが実装されていてjavascriptからSQLite3を利用できる、というのを知りました。

Firefox3に載っているsqliteよりもちょっとバージョンが古いので<a href="http://labs.gmo.jp/blog/ku/2008/03/sqlite3firefox3mozistorageservice.html">Firefox3でSQLite3の全文検索機能を使って日本語を検索する</a>のはまだできないようですが、できるようになるのも時間の問題でしょう。ためしにちょっと触ってみようと思ってclient-side storageをバックエンドにして、入力したパスワードを保存して次回から入力を楽にするためのbookmarkletをつくりました。

<em>
client-side storageはHTMLに書かれたjavascriptから読み出すことができ、データベースに書き込んだときのドメインと同じドメインにあるjavascriptからはURLが違っていても読み出すことができます。このブックマークレットは、ユーザ名とパスワードを平文で保存するので本当に重要なユーザ名/パスワードを保存しないでください。
</em>

<h3>ブックマークレット</h3>
<a href="javascript:void(( function () { var d = document; var e,i,j; function p () { l=d.location;return l.host + l.pathname } function _X(xpath, ctx) { exp = ctx.ownerDocument.createExpression(xpath, null); rs = exp.evaluate(ctx, XPathResult.ANY_TYPE, null); ns = []; while (i = rs.iterateNext()){ns.push(i);} return ns; } tgfm = null; for ( i = 0; i < d.forms.length ; i++ ) { f = d.forms[i]; e = f.elements; for ( j = 0; j < e.length; j++) { if ( e[j].type == 'password' ) { tgfm = f; break; } } if ( tgfm ) break; } ifnd = 0; n = 'dumbkeychain'; db = openDatabase(n,'1.0',n); db.t = db.transaction; db.t(function(tx) { tx.executeSql('SELECT * FROM acs WHERE host = ?', [p()], function(tx, rs) { row = rs.rows.item(0);row.account.split(/\x02/).map ( function ( p ) { v = p.split(/\x01/); tgfm[v[0]].value = v[1]; } ); ifnd = 1; }); }); db.t(function(tx) { tx.executeSql('CREATE TABLE acs (id INTEGER PRIMARY KEY, host TEXT, account TEXT)'); } ); if ( !ifnd ) { tgfm.addEventListener( 'submit' , function (e) { ctx = d.documentElement; v = []; f = tgfm; for ( j = 0; j < f.elements.length; j++) { i = f.elements[j]; t = i.type ; if ( t.match(/^text|password$/)) { v.push( [i.name , i.value].join('\x01') ); } } v = v.join('\x02'); db.t(function(tx) { l = d.location; tx.executeSql('INSERT INTO acs VALUES(NULL, ?, ?)', [p(), v ] ); } ); return 1; }, true); } } )())">dumbkeychain</a>

<h3>つかたかた</h3>

いちどSafariにブックマークレットを入れて、iTunesからブックマークを同期するとiPhone/iPod touch上にブックマークレットを送ることができます。手で入力するのはムリです。

今回は<a href="http://30d.jp/">30日間限定オンラインアルバム | 30days Album™</a>で動作を確認しました。30daysは、Flashでアップロードしたい写真をはじめに全部いっぺんに選んであとは待っておくだけでアップロードできて、誰でも見られるようにしたくないときにも見てほしい人にアカウントを作ってもらったりする必要もなく、メールでURLをパスワードを送るだけですみます。なにより写真がきれいに見えるデザインになっています！

flickrでいいじゃんと思うかもしれませんが、一般的にはflickrあまり知られてなくて、英語だからアカウントを作ってもらうのも面倒がられたりして、アップローダをダウンロードしてこないとたくさんアップロードするのが大変で、そのアップローダがうまく動かなかったりするので、いろんなひとに写真を見てほしい時にはぜんぜん使えません。

今知ったけど<a href="http://30d.jugem.jp/?eid=14">モバイル版</a>もリリースされています！裏側も<a href="http://30d.jugem.jp/?eid=13">30days Album Information | 30days Album を支える技術 #0 〜 サーバ構成概要</a>によればperbal+lighty+rails/catlyst+MogileFS+Gearman/TheSchwartz+MySQLと手堅くおもしろい作りになっています。

機会があったらぜひお試しください！


30daysは今年自分が最も感銘を受けたサービスなので、つい話がおおきくそれてしまいましたが、ブックマークレットの話に戻ります。
ログイン画面でdumbkeychainブックマークレットを呼び出してからフォームを送信すると、そのとき送信したユーザ名とパスワードがsqliteに保存され、次回から同じログイン画面(URLのパスをキーにしてデータを保存しているので、ログインするページのパスが変わるものはうまく機能しません)でdumbkeychainブックマークレットを呼び出すと
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%201-30.png" height="417" width="320" hspace="4" vspace="4" alt="Picture 1-30" class="border" /></div>
<div>
sqliteに保存されているユーザ名とパスワードが自動的に入力されます。
あとは送信ボタンを押すだけでログインできます。
<div><img src="http://labs.gmo.jp/blog/ku/Picture%202-28.png" height="355" width="320" hspace="4" vspace="4" alt="Picture 2-28" class="border" /></div>

上に書いたように、このブックマークレットはあまり安全でないので、ちゃんとしたいのなら<a href="http://lifehacking.jp/2008/04/1password/">最強のパスワード管理ソフト 1Password [Mac OS X] | Lifehacking.jp</a>で紹介されている<a href="http://agilewebsolutions.com/products/1Password">1Password</a>がいいとおもいます。


<h3>HTML+js+client-side storageでなにができるか</h3>
今回はブックマークレットからclient-side storageを利用しましたが、これはウェブアプリケーションで使う時に威力を発揮します。
ウェブベースのRSSリーダを考えてみましょう。携帯端末の通信速度もかなり向上しましたが、通信をはじめる時にはわりと時間がかかります(基地局との接続確確立に時間がかかるのでしょうか？)。電車に乗っているときには、電車がトンネルや地下にはいってしまうと通信できなくなります。

しかしclient-side storageを使うことでこれらの問題を解決することができます。

ユーザが記事を読んでいる間に<code>XMLHttpRequest</code>を使ってバックグラウンドでデータを読み込んでsqliteに書き込んでおけば、端末のメインメモリを消費することもなくデータを保存することができ、ユーザがデータを要求した時には容易に取り出すことができるのでユーザのアクションに対するレスポンスを大きく改善することができます。バックグラウンドでデータをあらかじめダウンロードしてsqliteにいれていれば、電車が圏外に移動してもsqliteからデータを読み出すことができます。うまくいけば駅に停車している間に圏外になっている間に読むだけの量をあらかじめ読んでおくことができるかもしれません。


UIの作り込みをのぞいたロジック部分はOSX上のsafari+javascriptで開発することができるので、あんまり互換性のないiPhoneシミュレータ+自分でメモリを解放しないといけないObjective-CでiPhoneのネイティブアプリケーションを書くのに比べると、圧倒的に短期間で容易に作ることができます。


<blockquote cite="http://fladdict.net/blog/2008/07/iphoneapp.html" title="fladdict» ブログアーカイブ » iPhone APPの開発は待ちか？">
モーション検地やマルチタッチさえ捨てれば、開発コストを考えると、JavaScriptでスゴイ完成度高いiPhone用のwebアプリを作るほうが楽そうな気もする。フィードバックや更新、配布コストの面から考えても。
<cite><a href="http://fladdict.net/blog/2008/07/iphoneapp.html">fladdict» ブログアーカイブ » iPhone APPの開発は待ちか？</a></cite>
</blockquote>
で書かれているとおり、開発が困難で時間のかかるネイティブアプリケーションでしかできないことは、カメラや加速度センサといったデバイスの機能を利用したいときだけです。

client-side storageが加わったことでHTML+javascriptだけでできることも大きく広がるので、同じだけの開発努力を注ぐならHTML+javascriptが正解という場合も多そうです。


<h3>参考</h3>
<ul>
<li><a href="http://d.hatena.ne.jp/amachang/20080327/1206607704">Safari 3.1 に実装された「Client-side database storage (SQL API)」とは何か？ - IT戦記</a></li>
<li><a href="http://jsgt.org/mt/archives/01/002154.html">JavaScript++かも日記: 【iPhone】iPhone用 JavaScriptデータベースプログラミング入門 (1)</a></lI>
<li><a href="http://d.hatena.ne.jp/i_ogi/20071001/1191254738">iPod touchで無線LANのブラウザ認証を楽にするbookmarklet - おぎろぐはてな</a></li>
</ul>


<h3>コード</h3>
<pre><code>( function () {
    var d = document;
    var e,i,j;
    function p () {
        l=d.location;return l.host + l.pathname
    }
    function _X(xpath, ctx) {
        exp = ctx.ownerDocument.createExpression(xpath, null);
        rs = exp.evaluate(ctx, XPathResult.ANY_TYPE, null);
        ns = [];
        while (i = rs.iterateNext()){ns.push(i);}
        return ns;
    }
    tgfm = null;
    for ( i = 0; i &lt; d.forms.length ; i++ ) {
        f = d.forms[i];
        e = f.elements;
        for ( j = 0; j &lt; e.length; j++) {
            if ( e[j].type == 'password' ) {
                tgfm = f;
                break;
            }
        }
        if ( tgfm )
            break;
    }
    ifnd = 0;
    n = 'dumbkeychain';
    db = openDatabase(n,'1.0',n);
    db.t = db.transaction;
    db.t(function(tx) {
        tx.executeSql('SELECT * FROM acs WHERE host = ?', [p()], function(tx, rs) {
            row = rs.rows.item(0);
            row.account.split(/\x02/).map ( function ( p ) {
                v = p.split(/\x01/);
                tgfm[v[0]].value = v[1];
            } );
            ifnd = 1;
        });
    });
    db.t(function(tx) {
        tx.executeSql('CREATE TABLE acs (id INTEGER PRIMARY KEY, host TEXT, account TEXT)');
    } );
    if ( !ifnd ) {
        tgfm.addEventListener( 'submit' , function (e) {
            ctx = d.documentElement;
            v = [];
            f = tgfm;
            for ( j = 0; j &lt; f.elements.length; j++) {
                i = f.elements[j];
                t = i.type ;
                if ( t.match(/^text|password$/)) {
                    v.push( [i.name , i.value].join('\x01') );
                }
            }
            v = v.join('\x02');
            db.t(function(tx) {
                l = d.location;
                tx.executeSql('INSERT INTO acs VALUES(NULL, ?, ?)', [p(), v ] );
            } );
            return 1;
        }, true);
    }
} )()</code></pre>

]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/08/iphoneclientside_storage.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/08/iphoneclientside_storage.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">Safari</category>
											<category domain="http://www.sixapart.com/ns/types#tag">javascript</category>
											<category domain="http://www.sixapart.com/ns/types#tag">サービス</category>
					
				
				<pubDate>Fri, 01 Aug 2008 18:34:29 +0900</pubDate>
			</item>
					<item>
				<title>XPathで書いたルールに従って複数ページにまたがったデータをあつめるjavascript製クローラ(失敗作)</title>
				<description><![CDATA[ページからデータをとりだそうとするとき、詳細な情報が書かれているページは別に用意されていて、もとのページと詳細ページのデータを混ぜ合わせないと全部のデータが得られないことがあります。


例として<a href="http://twitter.com/">Twitter</a>が挙げられます。
タイムラインのページでは、発言が長い時は発言の末尾が省略されて ... になるため、発言の個別ページにいって発言全体を取得する必要があります(と思って今確認したら、今は長くても発言全体が表示されるようになってました...)。

以前に作った<a href="http://labs.gmo.jp/blog/ku/2008/02/webscraperjavascriptwebscraperjs.html">Web::Scraperのjavascriptバージョンwebscraper.js</a>はひとつのページからしかデータが取れなかったので、複数のページからもデータが取れるもの急いで作りました。

Firefoxであればブックマークレットから呼び出したり、Firebugコンソールで使えます。

<h3>スクリプト</h3>
<a href="http://labs.gmo.jp/blog/ku/jscrawler.js">jscrawler.js</a>

<h3>つかいかた</h3>
Twitterのタイムラインのページ( <a href="http://twitter.com/home">http://twitter.com/home</a> など )で

<pre><code>make_deferred_document(document, {
    paragraph: 'id("content")//tr',
    data: {
        name: './/a[@class="url"]/img/@alt',
        id: './/td[@class="content"]/strong/a',
    },
    subRequest: {
        url: './/a[@class="url"]/@href',
        paragraph: '.',
        data: {
            bio: './/span[@class="bio"]',
            url: './/div[@class="user_icon"]/a/@href'
        }
    }
} ).next( extractor ).next( function (data) {
        console.log(data);
} )
</code></pre>

こんなかんじで取り出す部分を指定して使います。


そうすると
<div>

<img src="http://labs.gmo.jp/blog/ku/Picture%201-29.png" height="611" width="459" hspace="4" vspace="4" class="border" />

</div>
タイムラインのページからリンクをたどってユーザの個別ページにある<code>bio</code>(自己紹介のところです)部分といっしょに、ユーザのIDと表示名を取得することができます。と、ここまでは一見よさそうなんですけど....

<div>

<img src="http://labs.gmo.jp/blog/ku/Picture%202-27.png" height="325" width="480" hspace="4" vspace="4" class="border" />

</div>
よくみると全部に同じデータが入っていて、しかもタイムラインのページで取り出している<code>id,name</code>と個別ページから取り出している<code>bio</code>のデータとが同じ人のものではなくほかの人のものが混ざっている状態なのでした....


途中まで、時間的都合で再起的にたどるのはあきらめて2段階しかたどれない実装でいいやと思って書いていて、そのときはちゃんと動いてたのですが、やっぱりルールに従って何段階でもたどれるようにしたいと思って書き直したのが失敗でした。
こういう、ほかのページを<code>XMLHttpRequest</code>で取得して全部のページのロードと処理が終わってから非同期でなにかを処理するときには<a href="http://gihyo.jp/assets/files/event/2007/wdpress-tm2007/data/WDB-TechMTG-01-amano/cy/index.html">JSDeferred</a>が便利です。<a href="http://www.mochikit.com/doc/html/MochiKit/Async.html">MochiKitのDeferred</a>も同じように使えます(がJSDeferredに比べてファイルのサイズが大きいのでちょっとしたのが書きたい時にはちょっとおおげさなかんじがします)。


javascriptで複数ページからデータを集めてくるのは、ずっとまえから欲しかったので再度挑戦したいと思います....]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/07/xpathjavascript.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/07/xpathjavascript.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">Firefox</category>
											<category domain="http://www.sixapart.com/ns/types#tag">data</category>
											<category domain="http://www.sixapart.com/ns/types#tag">javascript</category>
					
				
				<pubDate>Sat, 26 Jul 2008 00:14:28 +0900</pubDate>
			</item>
					<item>
				<title>Firefoxのヒストリぜんぶをfaviconにして見る！</title>
				<description><![CDATA[最近はほとんどどのサイトにfaviconが置かれるようになりました。Ruby on Railに似せて作られたPHPのアプリケーションフレームワーク<a href="http://www.cakephp.org/">CakePHP</a>ではデフォルトで<a href="http://www.cakephp.org/favicon.ico">cakePHPのfavicon</a>が入るようになっていたりします。

サイトのfaviconは、ブラウザのブックマークから目的のサイトを探す時のように、大量にあるリストから自分の探しているものを見つける手がかりになります。ロシアで最大のシェアを誇るサーチエンジン<a href="http://www.yandex.ru/">yandex</a>の検索結果にはfaviconが一緒に表示されるようになっています。
<div><a href="http://yandex.ru/yandsearch?text=favicon"><img src="http://labs.gmo.jp/blog/ku/Picture%207-9.png" height="577" width="543" hspace="4" vspace="4" alt="Picture 7-9" class="border" /></a></div>
yandexで表示されているfaviconのURLは<code>http://favicon.yandex.net/favicon/ru.wikipedia.org</code>という形式になっていて、faviconよりあとの部分を変えればそのURLのfaviconが表示されるようになっていました。が、さすがにロシアローカルのサーチエンジンなので日本のサイトはほとんどfaviconがでてきません。そこでURLを渡すとそのfaviconを返してくれるAPIというのがないのか探してみたら<a href="http://favicon.aruko.net/">Favicon API (ファビコン) α版</a>というのがありました。


Firefox3にも<a href="http://developer.mozilla.org/en/docs/nsIFaviconService">nsIFaviconService</a>というなまえの"URLを渡すとそのfaviconを返してくれる"機能が入っています。これを使ってブラウザのヒストリをfaviconにして表示してみたらおもしろかったりするかなーと思ってやってみました。


履歴からプライベートじゃないデータを全部捨てて、残ったURLのfaviconを並べてできたのが下のものです。
<div>
<img src="http://labs.gmo.jp/blog/ku/faviconhistory.png" height="477" width="569" hspace="4" vspace="4" alt="Faviconhistory" /></div>
大半が検索の履歴になっているのはいいとして(ウェブの検索以外に<a href="http://www.google.com/codesearch?q=&amp;hl=en">Google Code Search</a>をよく使っています)、なんかやたらflickrにアクセスしてるかんじがしますねー。

というのは文字で書いてある履歴を眺めていてもなかなかわかりませんが、こうしてfaviconにして並べてみると一目瞭然です。


<h3>つくりかた</h3>
Firefoxの履歴にアクセスするにはFirefoxの内部にアクセスするためのchrome特権というものが必要なのでFirefoxのToolsにあるError Consoleを使います。
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%201-27.png" height="246" width="356" hspace="4" vspace="4" class="border" />
</div>
javascriptやCSSのエラーが表示されるところです。このダイアログの<em>Code:</em>の部分にjavascriptを書くとchrome特権つきでコードを実行することができます(参考: <a href="http://d.hatena.ne.jp/teramako/20080611/p1">JavaScriptコンソールからパスワードぶっこ抜き - hogehoge</a>)。
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%206-13.png" height="330" width="590" hspace="4" vspace="4" />
</div>
ここに下のテキストエリアにあるコードをコピーして実行してください。
<textarea cols="60">var m="@mozilla.org/";var c=Components;var cc=c.classes;var ci=c.interfaces;var hs = cc[m+"browser/nav-history-service;1"].getService(ci.nsINavHistoryService);var result = hs.executeQuery(hs.getNewQuery(), hs.getNewQueryOptions());var root = result.root;root.containerOpen = true;var a = [];for (var i=0; i &lt; root.childCount; ++i) { try { var node = root.getChild(i); var fs = cc[m+"browser/favicon-service;1"].getService(ci.nsIFaviconService); var ios = cc[m+'network/io-service;1'].getService(ci.nsIIOService); var u= ios.newURI(node.uri, null, null); var favicon = fs.getFaviconForPage( u); a.push(favicon.spec); } catch (ex) { }}var cs = cc[m+"browser/nav-history-service;1"].getService(ci.nsINavHistoryService);cc[m+"widget/clipboardhelper;1"].getService(ci.nsIClipboardHelper).copyString("&lt;"+a.map ( function ( n ) { return "img src='" + n+ "' width='16' height='16'";} ).join("/&gt;\n&lt;")+"/&gt;");</textarea>
このコードは以下のコードからいらないスペースや改行を削って作ったものです。

<pre><code>var m="@mozilla.org/";
var c=Components;var cc=c.classes;var ci=c.interfaces;
var hs = cc[m+"browser/nav-history-service;1"].getService(ci.nsINavHistoryService);
var result = hs.executeQuery(hs.getNewQuery(), hs.getNewQueryOptions());
var root = result.root;root.containerOpen = true;
var a = [];
for (var i=0; i &lt; root.childCount; ++i) {
    try {
        var node = root.getChild(i);
        var fs = cc[m+"browser/favicon-service;1"].getService(ci.nsIFaviconService);
        var ios = cc[m+'network/io-service;1'].getService(ci.nsIIOService);
        var u= ios.newURI(node.uri, null, null);
        var favicon = fs.getFaviconForPage( u);
        a.push(favicon.spec);
    }
    catch (ex) {    }
}
var cs = cc[m+"browser/nav-history-service;1"].getService(ci.nsINavHistoryService);
cc[m+"widget/clipboardhelper;1"].getService(ci.nsIClipboardHelper).copyString("&lt;"+a.map ( function ( n ) {
    return "img src='" + n+ "' width='16' height='16'";
} ).join("/&gt;\\n&lt;")+"/&gt;");</code></pre>

これを実行するとクリップボードにfaviconがたくさん書かれたHTMLがコピーされるのであとは適当なテキストエディタにはってHTMLとして保存したあと、ブラウザで開けばあなたの履歴をfaviconでみることができます。

]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/07/firefoxfavicon.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/07/firefoxfavicon.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">Firefox</category>
											<category domain="http://www.sixapart.com/ns/types#tag">visualization</category>
					
				
				<pubDate>Fri, 18 Jul 2008 16:31:22 +0900</pubDate>
			</item>
					<item>
				<title>libxmlのHTMLパーサ+XPathでid関数が使えなくてはまる</title>
				<description><![CDATA[さいきんlibxmlでHTMLを読み込んでXPathで要素を取り出すのが一部(<a href="http://d.hatena.ne.jp/tasukuchan/20080706/1215344211">[xml][libxml2][c]XMLをHTTPで取得して、XPathで指定された中身をC言語で取り出す方法 - グニャラくんのグニャグニャ備忘録@はてな</a>)ではやっています。


これにちょっと癖があってはまりました。
<pre><code>&lt;html&gt;&lt;body&gt;
&lt;ul id="list"&gt;
  &lt;li&gt;hello&lt;/li&gt;
  &lt;li&gt;world&lt;/li&gt;
&lt;/ul&gt;
&lt;/body&gt;&lt;/html&gt;</code></pre>
こういうHTMLファイルを読み込んで <code>id("list")/li</code> というXPathを評価させるとなぜか何にもマッチしないのです。でもこれを <code>//*[@id="list"]/li</code> に置き換えると<code>hello</code>と<code>world</code>を囲んでいる<code>li</code>ふたつにマッチするようになるので、パース自体は問題ないようです。

で、いろいろいじっていたらなんでもいいのでHTMLに<code>DOCTYPE</code>をいれると<code>id</code>関数が使えるのに気がつきました。
<pre><code>&lt;!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"&gt;
&lt;html&gt;&lt;body&gt;
&lt;ul id="list"&gt;
  &lt;li&gt;hello&lt;/li&gt;
  &lt;li&gt;world&lt;/li&gt;
&lt;/ul&gt;
&lt;/body&gt;&lt;/html&gt;</code></pre>
にすると<code>id("list")/li</code>で<code>li</code>ふたつがマッチするようになりました。

<a href="http://xmlsoft.org/html/libxml-HTMLparser.html">Module HTMLparser from libxml2</a>には<q>this module implements an HTML 4.0 non-verifying parser with API compatible with the XML parser ones.</q> と書かれていて、ベリファイしないけどHTML4.0のパーサというのがどういう意味なのかわかりませんが(HTML4.0で定義されている要素であれば、その論理構造を考慮した上でwell-formedにしてくれるということでしょうか)、とりあえず<code>id</code>関数が使えるようになりました。]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/07/libxmlhtmlxpathid.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/07/libxmlhtmlxpathid.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">C/C++</category>
											<category domain="http://www.sixapart.com/ns/types#tag">XML</category>
											<category domain="http://www.sixapart.com/ns/types#tag">XPath</category>
					
				
				<pubDate>Fri, 11 Jul 2008 19:32:29 +0900</pubDate>
			</item>
					<item>
				<title>クローンサービスのAPIデザイン</title>
				<description><![CDATA[最近トラブル続きの<a href="http://twitter.com/">Twitter</a>のかわりに<a href="http://wassr.jp/">Wassr</a>を使いはじめたひとたちにフォローしていただいてたくさんメールが来ました。

去年たくさんできたtwitterみたいなサービスの中で、自分が注目していたのは<a href="http://mogo2.jp/">もごもご</a>と<a href="http://wassr.jp/">Wassr</a>です。このふたつのサービスにはTwitter同様APIが用意されているだけでなく、そのAPIがTwitterのAPIと互換になっていると書かれていたからです。


1年前に書けばよかったのにとおもいつつ、今日はサービスのAPIがほかのサービスと互換にすることについて書きます。

<h3>いいところ</h3>
既存のサービスと完全に同じAPIにすると、そのサービス用に作られたツールを少し改造すればそのまま新しいクローンのサービスで使うことができるようになります。

Twitterのクローンであれば、そのサービスにTwitterとおなじAPIを用意すれば、Twitter用に作られた多数のツールをそのまま流用することができます。

実例として、Twitterのタイムラインを見たり投稿したりできるFirefoxの拡張機能<a href="https://addons.mozilla.org/ja/firefox/addon/5081">TwitterFox</a>を使って<a href="http://mogo2.jp/">もごもご</a>のタイムラインを表示、投稿できるかためしてみました。
<code>twitternotifier@naan.net/components/nsITwitterNotifier.js</code>の中にあるtwitter APIのサーバ名が書かれている
<pre><code>const TWITTER_API_URL = "https://twitter.com/";
</code></pre>
を、もごもごのAPIのサーバ名
<pre><code>const TWITTER_API_URL = "http://api.mogo2.jp/";
</code></pre>
に書き換えるだけで、Twitterでいう<code>friends_timeline</code>(ともだちの発言一覧)を得ることができました。もごもごにともだちがいないので自分一人ですけど....

<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%203-27.png" height="424" width="637" hspace="4" vspace="4" class="border" />
</div>

Twitterfoxからもごもごに投稿することもできました。が、投稿はされていてページには反映されるもののTwitterfoxにはエラーが発生したと表示されるので、投稿のレスポンスがTwitterのAPIとは微妙に違っているのかもしれません。


Firefoxの拡張機能はたいていはjavascriptで書かれているので直接編集することができますが、Windowsで人気な<a href="http://cheebow.info/chemt/archives/2007/04/twitterwindowst.html">Twit</a>のようにOSネイティブのアプリケーションはバイナリエディタでしかAPIのエンドポイントを書き換えたりできません。そのアプリケーションがもともとアクセスするサーバの名前よりも、自分の新しいサービスのAPIのサーバの名前の方が長いとバイナリエディタで上書きすることができなくなってしまうので、同じ長さにするか、もしくはそれより短くしておくとバイナリの書き換えが不可能、ということがなくなります。


<h3>よくないところ</h3>
ほかのサービスとAPIを同じにするのはときとして大変です。FlickrのようにAPIの仕様が安定しているサービスもあれば、TwitterのようにAPIの仕様をたびたび変えているようなサービスとの互換性を維持していくのは面倒な作業になります。<a href="http://mogo2.jp/api.shtml">もごもごAPI</a>も<a href="http://wassr.jp/help/api">Wassr API Document</a>もTwitterと互換のAPIと書かれていますがTwitterfoxでアクセスしてみると(どこに原因があるのかはきちんと調べていませんが)やっぱりどこかで互換でなくなっているようでうまく動きませんでした。

元のサービスにない新しい機能を追加した時に、元のサービスのAPIデザインとは別に設計した方が効率的にできることもあるでしょう。もとのサービスのデザインがいまいちだということもあるでしょう。



<h3>まとめ</h3>
いいところもわるいところもありますが、既存のサービスと似たサービスのAPIを設計する時、既存のサービス向けに作られた豊富なクライアントアプリケーションを使えるように完全互換にすることも検討してみてはいかかでしょうか。



<h3>他の事例</h3>
<a href="http://del.icio.us/">del.icio.us</a>クローンの<a href="http://faves.com/">Faves</a>(以前はblue dotsという名前でした)は、はじめ<a href="http://faves.com/BlueDotApi.aspx">SOAPのAPI</a>しかありませんでしたが、あとになって<a href="http://faves.com/DeliciousApi.aspx">del.icio.us互換のAPI</a>が実装されました。

その結果del.icio.usのために書かれたAPIライブラリのホスト名を書き換えるだけでFavesのデータにアクセスできるようになって、非常に便利になりました。]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/07/api.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/07/api.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">API</category>
											<category domain="http://www.sixapart.com/ns/types#tag">design</category>
					
				
				<pubDate>Fri, 04 Jul 2008 21:40:43 +0900</pubDate>
			</item>
					<item>
				<title>TheSchwartzで仕事をあとにまわす</title>
				<description><![CDATA[<a href="http://d.hatena.ne.jp/tokuhirom/20071017/1192589429">web2.0 時代のジョブキューサーバー Gearman と TheSchwartz の関係について - TokuLog 改めB日記</a>に書かれているとおり、<a href="http://search.cpan.org/~bradfitz/Gearman/">Gearman</a>は仕事を投げられたらすぐやって返す前提になっていて<em>今やりたくないけどあとでやる</em>みたいなのができません。

たとえば、10分後にならできるんだけど、という仕事が来たとします。
このときGearmanのワーカの中でsleepして10分待つと、後から来たほかの仕事を一切しないで10分待つことになってしまって、後から来た仕事が今すぐできるものだったとしても10分待たれさるのでGearmanだと今来た仕事のためにsleepして待つわけにはいきません。

結果として<a href="http://search.cpan.org/~bradfitz/Gearman/">Gearman</a>だとやってきた仕事を今すぐやるか、絶対やらないか、の二択になってしまいます。


それだと困るので、もう一方の<a href="http://search.cpan.org/~bradfitz/TheSchwartz/">TheSchwartz</a>だと、今やりたくないけどあとでやる、ことができるのかなーと思って調べてみたらできました。GearmanとくらべてTheSchwartzのほうはどうやって使うかあまり書かれてなかったので、TheSchwartzを使ってやってきた仕事を後に回す方法を書きます。

<h3>MySQLにテーブルを作る</h3>
Gearmanが仕事のキューをメモリ上に持つのに対して、TheSchwartzはデータベースに保存します。だからはじめにデータベースにテーブル作ったりするんだろうなと思ったのですが<a href="http://search.cpan.org/~bradfitz/TheSchwartz-1.03/lib/TheSchwartz.pm">TheSchwartz - reliable job queue - search.cpan.org</a>にも<a href="http://d.hatena.ne.jp/tokuhirom/20070501/1177997739">TheSchwartz をためす - TokuLog 改めB日記</a>にもテーブルを作ることについて触れられていません。

なければ勝手に作ってくれるのかなと思って<a href="http://search.cpan.org/~bradfitz/TheSchwartz-1.03/lib/TheSchwartz.pm#SYNOPSIS">SYNOPSIS</a>のコードを実行してみたけどやっぱり自動でできたりはしなそうだったので調べたら<a href="http://jshirley.vox.com/library/post/catalyst-and-theschwartz-reliable-jobqueue-in-a-great-framework.html">Catalyst and TheSchwartz: Reliable JobQueue in a great framework - Vox</a>に<a href="http://search.cpan.org/src/BRADFITZ/TheSchwartz-1.04/doc/schema.sql">schema.sql</a>を使うといいと書いてありました。このスキーマをmysqlで実行すればTheSchwartzのキューを管理するためのテーブルが出来上がります。

<h3>コード</h3>
セットアップができれば後はGearmanとおなじかんじです。workerで仕事の名前を登録して、仕事を待ちます。
Gearmanとちがうところは、関数を登録するのではなく<code>TheSchwartz::Worker</code>のサブクラスを作って、そのクラス名を登録するところ。仕事が来た時には作ったサブクラスの<code>work</code>メソッドが呼び出されます。

TheSchwartzのサーバは、デフォルトでは5秒間隔でキューをチェックして、仕事があった時にはその仕事の名前に応じたワーカを起動します。<code>work</code>はふられた仕事に対して<a href="http://search.cpan.org/~bradfitz/TheSchwartz-1.03/lib/TheSchwartz/Job.pm#WORKING">WORKING</a>にある4つのメソッドのうちのどれかを呼び出して終了します。

<dl>
<dt>completed()</dt><dd>
仕事を問題なくこなした時に呼び出します。TheSchwartzでは問題なく終わった時は、その結果をどこかに保存したりはできないようで、なにも引数はありません。そういうときはわざとfailしたことにするのかな。
</dd>
<dt>failed($msg, $exit_status)</dt>
<dd><p>やったけどだめだったときに呼び出します。<code>failed</code>を呼ぶと、失敗した時刻、ジョブのID, <code>$msg, $exit_status</code>がdbの<code>error</code>テーブルと<code>exitstatus</code>テーブルに記録されます。perlの実行時エラーでなどで失敗した時も同様に<code>error</code>テーブルに記録されるようになっています。</p>
<p><code>max_retries</code>が0以上に設定されている時は<code>failed</code>を呼んでもジョブはキューから削除されずに、<code>max_retries</code>の値に達するまで即時再度実行されるようになっています。常に失敗するような状態になっている時に<code>max_retries</code>が大きな値になっているとすごい勢いでループしてCPUを浪費するので注意が必要です。</p>
</dd>
<dt>permanent_failure( $msg, $exit_status )</dt>
<dd>
リトライしてもだめなエラーの時には<code>permanent_failure</code>を呼び出すと<code>max_retries</code>に関わらずリトライされません。<code>$exit_status = 1</code>で呼び出すとさらにキューからジョブを削除されます。
</dd>
<dt>replace_with</dt><dd>
今のジョブをキューから削除してほかのジョブで置き換えたい時に呼び出します。

たとえば、ログインしないとアクセスできないページのデータを読み込みたい時、持っているクッキーがまだ有効だと思ってアクセスしたらクッキーが無効でログインが必要になった場合、ページを読み込むといういまのジョブを
<ol><li>ログインページにアクセス</li>
<li>アカウント情報をpost</li>
<li>データを読み込みたいページにアクセス</li>
</ol>
という3つのジョブと入れ替えたい、というような時に使います。
</dd>
</dl>

今回はこの最後の<code>replace_with</code>を使って今きた仕事を明日(24時間後)にするサンプルを作りました。
TheSchwartzには時間を指定してジョブを実行させることができるようになっていたりして、やってきた仕事を今すぐやるかやらないかしかないGearmanにくらべて柔軟な管理ができます。

Gearmanに比べるとスペルが覚えられないのとタイプしにくいのが難点です。

<h4>worker.pl</h4>
<pre><code>package MyWorker;
use base qw( TheSchwartz::Worker );

sub work {
    my ($class, $job) = @_;

    my $deferredJob = TheSchwartz::Job-&gt;new(
        funcname =&gt; $job-&gt;funcname,
        arg =&gt; $job-&gt;arg,
        run_after =&gt; time + 24 * 3600
    );

    $job-&gt;replace_with( $deferredJob );
}

package main;
use TheSchwartz;

my $client = TheSchwartz-&gt;new(
    databases =&gt; [{dsn =&gt; 'dbi:mysql:the_schwartz', user =&gt; 'root', pass =&gt; ''}],
    verbose =&gt; 1
);
$client-&gt;can_do('MyWorker');
$client-&gt;work();</code></pre>


<h4>client.pl</h4>
<pre><code>use TheSchwartz;
use JSON;

my $client = TheSchwartz-&gt;new(
    databases =&gt; [{dsn =&gt; 'dbi:mysql:the_schwartz', user =&gt; 'root', pass =&gt; ''}],
    verbose =&gt; 1
);

my $arg = {
    hello =&gt; "world",
    fizz =&gt; "buzz",
};
$client-&gt;insert('MyWorker' =&gt; to_json($arg) );</code></pre>]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/06/theschwartz.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/06/theschwartz.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">perl</category>
					
				
				<pubDate>Wed, 25 Jun 2008 18:38:34 +0900</pubDate>
			</item>
					<item>
				<title>サービスのread/writeとコンテンツのfav</title>
				<description><![CDATA[Permalinkという概念が浸透して、比較的最近作られたようなウェブのサービス上にあるリソースには、そのリソースを一意に表すことのできるURLが割り当てられているようになりました。このブログの記事を表すURLは<code>http://labs.gmo.jp/blog/ku/2008/06/fav.html</code>で、このURLにアクセスすればこのページがなくなったりしない限りは"多くのサービスに見られるコンテンツに対するfavという概念"について書かれた文章が得られます。このブログにある記事が増えたり減ったりしても、この記事を表すURLは変わりません。

<h3>read/write</h3>
ひとつのリソースに普遍的なひとつのURLが割り当てられていれば、そのURL自体をIDにしてリソースを読み書きすることができます。以前<a href="http://labs.gmo.jp/blog/ku/2007/11/fuserestfssiteinfo.html">デバイスドライバ/FUSEのrestfs/SITEINFOの役割比較</a>で触れた<a href="http://blog.tkmr.org/tatsuya/show/352-rest-web-rest-fuse">RESTfs</a>はその前提の上に立つことで、ウェブのサービスをファイルとして読み書きする機能を提供しています。

RESTのAPIが提供されていないウェブのサービスであっても、ユーザがウェブ上でコンテンツを公開したりするためのサービスは、コンテンツひとつひとつを表すURLと、そのコンテンツに対する読み込みと書き込みの概念が存在します。

コンテンツの読み込みは、ふつうにブラウザでそのコンテンツがあるページを閲覧することです。コンテンツの書き込みは、そのサービスが受け付けることのできる形式のリソースをサービスにアップロードすることです。<a href="http://youtube.com/">YouTube</a>であればビデオを投稿することが書き込みにあたります。<a href="http://flickr.com/">Flickr</a>であれば写真のアップロード、<a href="http://twitter.com/">twitter</a>であればなにかつぶやいてみること、ブログであれば記事を書いて投稿すること、<a href="http://del.icio.us/">del.icio.us</a>であればブックマークを新しく作ることです。

このように多くのウェブのサービスはread/writeすることができます(writeはともかくreadできないサービスはないでしょう)。


<h3>fav</h3>
多くのウェブのサービスではread/writeのほかにもうひとつコンテンツに対して広く行われている操作があります。何がきっかけなのかわかりませんが、いまでは多くのサービスでそのコンテンツが気に入ったことを表すために<em>favorite</em>マークをつけることができるようになっています。

<dl>
<dt>YouTube</dt><dd>
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%204-17.png" height="52" width="171" hspace="4" vspace="4" alt="youtube fav" title="youtube fav" class="border" />
</div>
YouTubeにはそのビデオが好きだった時に♡マークのFavoriteボタンを押すと、自分のお気に入りのビデオのリストに追加されるようになっています。
</dd>
<dt>Flickr</dt>
<dd><div><img src="http://labs.gmo.jp/blog/ku/Picture%205-16.png" height="47" width="86" hspace="4" vspace="4" alt="Picture 5-16" class="border" /></div>
Flickrには☆マークのFAVESボタンがあります。これも自分のお気に入り写真のリストに追加されます。
</dd>
<dt>Twitter</dt>
<dd><div><img src="http://labs.gmo.jp/blog/ku/Picture%206-10.png" height="73" width="148" hspace="4" vspace="4" alt="Picture 6-10" class="border" /></div>
Twitterにも☆マークで"Favorite this update"というボタンがあります。星をつけると自分のお気に入りリストに入ります。<a href="http://favotter.matope.com/">ふぁぼったー</a>というtwitterのお気に入りを集計しているサイトもあります。
</dd>
<dt>ブログ</dt>
<dd>ブログには上に挙げたサービスのように、その投稿が気に入ったことを表すための機能はありません。そもそもその気に入った投稿が書かれているブログが自分のブログでない限りは、書き込むことができません。そのため、ブログの投稿を自分のお気に入りとしてリストしていくには、原理的に外部のサービスに頼る必要があります。

そのお気に入りのブログの投稿を管理するための外部のサービスが、古くからあるブラウザのブックマークであり、現代的なものではdel.icio.usのようなソーシャルブックマーク、<a href="http://s.hatena.ne.jp/">はてなスター</a>や<a href="http://digg.com/">digg</a>や<a href="http://www.stumbleupon.com/">stumbleupon</a>のような、おもしろかったらボタンを押す、サービスがあります(stumbleuponとdiggには"おもしろくなかった時に押すためのボタンもあります)。
</dd>
<dt>del.icio.us</dt>
<dd>ひとつ上のブログのところで述べたように、del.icio.usがfav機能を持たないコンテンツに対してfavするためのサービスとして存在していて、投稿されるものがコンテンツであるという認識に立っていないからか、del.icio.us自体にはコンテンツである投稿されたブックマークひとつひとつに対するfavに相当する機能はありません。それはdel.icio.usがfavできないサービスに対するfavを保存しておくためのサービスだと考えることができるからかもしれません。del.icio.usが持っている、ひとつひとつのブックマークというコンテンツに対してfavをつけることはできないものの、ブックマークをしている人を "add to your network" することができます。
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%207-7.png" height="72" width="167" hspace="4" vspace="4" alt="Picture 7-7" class="border" /></div>
人を自分のネットワークに追加すると、<a href="http://del.icio.us/network/ku0522">your network</a>というページでネットワークに入っている人がポストしたブックマークの一覧に表示されるようになります。

"ひとつのURLに結びつけられたリソースに対する操作としてのfav"ではありませんが、そのユーザをひとつのリソースとしてとらえればfavとして考えることもできます。
</dd>
</dl>

サービスにfavという概念がないかで見てみるとamazonにも気に入った商品のリストを作るための<a href="http://www.amazon.co.jp/gp/registry/wishlist/">ほしい物リスト</a>があり、gmailにもメールに☆をつける機能があります。
<div><img src="http://labs.gmo.jp/blog/ku/Picture%208-7.png" height="66" width="167" hspace="4" vspace="4" alt="Picture 8-7" class="border" /></div>
<a href="http://www.last.fm/">last.fm</a>でも"Express love for this track"があり、<a href="http://friendfeed.com/">FriendFeed</a>にも"like"があります。

<h3>まとめ</h3>
だからなにということもなくそれだけですが、ファイルシステムとしてマウントすることが可能なくらいファイルシステムと同じ性質を持っているインターネット上のサービスに、ファイルシステムには存在しないfavという概念が広く存在していることがおもしろいなと思いました。

RESTfsでそれぞれのサービスのread/writeをラップして一元的にread/writeできるのと同じように、それぞれのサービスにあるfavをラップして一元的にfavすることもできそうです。]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/06/readwritefav.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/06/readwritefav.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">API</category>
					
				
				<pubDate>Fri, 20 Jun 2008 19:17:14 +0900</pubDate>
			</item>
					<item>
				<title>WebKitでサポートされているJavaScript1.6のべんりなArrayメソッド</title>
				<description><![CDATA[常識なのかもしれないですけど、先日たまたまJavaScript1.6で追加された<a href="http://developer.mozilla.org/ja/docs/New_in_JavaScript_1.6#Array_extras">Arrayの拡張メソッド</a>がWebKitでもサポートされているのに気がつきました。

ちゃんとした文書をみつけられなかったのですが<a href="http://lists.macosforge.org/pipermail/webkit-reviews/2006-August/008927.html">[webkit-reviews] review denied: [Bug 6252] JavaScript 1.6 Array.lastIndexOf : [Attachment 10095] lastIndexOf javascript patch</a>というのが出てきたので、WebKitでも正式にJavaScript1.6がサポートされてるようです。


<h3>こういうときにべんり</h3>
追加されたメソッドで最も便利なのが<a href="http://developer.mozilla.org/ja/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:map">map()</a>です。<code>map</code>は引数に関数を渡して使います。配列の各要素を引数にして渡された関数を実行して、関数の返した値を配列として返してくれます。<code>map</code>は戻り値として配列を返すのに対して、何も返さない<code>forEach</code>もあります。何も返さないぶん<code>forEach</code>のほうが高速なんだと思いますが、タイプするときに難しいので、値を返さないときでも私はもっぱら<code>map</code>を使っています。


<code>map</code>の何が便利なのか、例としてjavascriptでURLのクエリ文字列をパースして、ひとつのオブジェクトにパラメータの名前をキーにして値を保持する処理を考えてみましょう。<code>map</code>を使わずにふつうに<code>for</code>を使って書くとこうです。
<pre><code>var params = {};
var q = "q=prototype.js&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a";
var pairs = q.split(/&/);
for(var i = 0; i &lt; pairs.length; i++ ) {
    var pair = pairs[i];
    var nv = pair.split(/=/);
    var n = decodeURIComponent( nv[0] );
    var v = decodeURIComponent( nv[1] );
    params[n] = v;    
}
</code></pre>
ループをまわすために<code>for</code>を書いたり、URLエンコードされているかもしれない名前と値をデコードするために一度変数にいれたりしないといけません(<code>params[ decodeURIComponent(nv[0]) ] = decodeURIComponent(nv[1])</code>って書くのはちょっと嫌ですよね)。


おなじ処理を<code>map</code>を使って書くとこんなにすっきりします。

<pre><code>var params = {};
var q = "q=prototype.js&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a";
q.split( /&/ ).map( function (pair) {
    var nv = pair.split(/=/).map( decodeURIComponent );
    params[ nv[0] ] = nv[1];
} );</code></pre>

<code>map</code>の引数にした関数に配列の要素の値が直接渡されるので、インデックスのための変数が必要なくなるのと、パラメータの名前と値をデコードするのに2回<code>decodeURIComponent</code>を書かなくてよくなるのですっきりします。


今回はわりとなじみがありそうな例としてクエリ文字列のパースをあげました。関数を引数に渡すのはなんか難しそうですが、<code>for</code>でループで書くのにくらべて<code>map</code>だと書かないといけない量が少ないのでとりあえず楽ができます。


ただけっきょくIEでサポートされてるわけではないので、SafariでJavaScript1.6のArray拡張が実装されていてもiPhone/iPod touch用のサイトくらいしか使える機会がないのが悲しいところです...

<h3>thisに注意!</h3>
ただクラスの中で<code>map</code>を使うときには注意です。引数に渡した関数の中では<code>this</code>の値が変わっています(<code>window</code>オブジェクトになっています)。さっきのコードがもし下のようにクラスメンバの<code>params</code>に結果を代入するようになっていると<code>Parser.params</code>ではなく<code>window.params</code>に結果が入ってしまいます。
<pre><code>var Parser = {
    params: {},
    parse: function (q) {
        q.split( /&/ ).map( function (pair) {
        var nv = pair.split(/=/).map( decodeURIComponent );
            <span style="color: #f08">this</span>.params[ nv[0] ] = nv[1];
        } );
    }
}
</code></pre>

これを避けるには2つ方法があります。


よく使われているのが、<code>map</code>の前に<code>this</code>を<code>self</code>に代入しておく方法です。一時的に代入しておくための変数は<code>self</code>でなくても何でもいいですが、慣習として<code>self</code>が使われます。
<pre><code>var Parser = {
    params: {},
    parse: function (q) {
        <span style="color: #f08">var self = this;</span>
        q.split( /&/ ).map( function (pair) {
            var nv = pair.split(/=/).map( decodeURIComponent );
            <span style="color: #f08">self</span>.params[ nv[0] ] = nv[1];
        } );
    }
}
</code></pre>


もうひとつは<code>map</code>の第2引数に<code>this</code>になるオブジェクトを指定する方法です(さっき知りました...)。関数内の<code>this</code>で示されるオブジェクトを第2引数で変更することができます。<pre><code>var Parser = {
    params: {},
    parse: function (q) {
        q.split( /&/ ).map( function (pair) {
            var nv = pair.split(/=/).map( decodeURIComponent );
            <span style="color: #f08">this</span>.params[ nv[0] ] = nv[1];
        }, <span style="color: #f08">this</span> );
    }
}
</code></pre>
前者はスコープチェインをたどって<code>self</code>という名前が解決されるのにくらべて、後者の方は同じスコープに名前があるぶん処理が軽いでしょう。WebKitでも同様に第2引数で関数内の<code>this</code>を変更することができます。

<h3>JavaScript1.8だとさらに短く書けます!</h3>
WebKitでサポートされているのはJavaScript1.6のでですが、FirefoxのJavaScript1.8では<a href="http://developer.mozilla.org/ja/docs/New_in_JavaScript_1.8#.E3.81.95.E3.82.89.E3.81.AA.E3.82.8B_Array_.E3.81.AE.E6.8B.A1.E5.BC.B5">さらなるArrayの拡張</a>というのがサポートされています。

上のクエリ文字列のパースは、さらにJavaScript1.7で導入された<a href="http://developer.mozilla.org/ja/docs/New_in_JavaScript_1.7#.E5.88.86.E5.89.B2.E4.BB.A3.E5.85.A5">分割代入</a>とさらなるArrayの拡張に含まれている<a href="http://developer.mozilla.org/ja/docs/Core_JavaScript_1.5_Reference:Objects:Array:reduce">reduce</a>を使うとさらにすっきりさせることができます。

<pre><code>
var q = "q=prototype.js&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a";
var params = q.split( /&/ ).reduce( function (_params, pair) {
    var [n, v] = pair.split(/=/).map( decodeURIComponent );
    _params[ n ] = v;
    return _params;
}, {} );</code></pre>

というふつうに便利なのでIEでも使えるようになるといいですね。

]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/06/webkitjavascript16array.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/06/webkitjavascript16array.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">WebKit</category>
											<category domain="http://www.sixapart.com/ns/types#tag">javascript</category>
					
				
				<pubDate>Fri, 13 Jun 2008 22:41:53 +0900</pubDate>
			</item>
					<item>
				<title>javascriptで作るGearmanクライアント</title>
				<description><![CDATA[Webアプリケーションの中には、ときどき時間がかかる処理をしないといけない部分があります。アップロードされた画像のサムネイルを多数生成するときや、クライアントからのリクエストに応じてさらにリモートのサーバからファイルを取得してきたりするとき、ふつうに作るとユーザは処理が終わるまでの数秒~数十秒間待たされるのでイライラしてきます。


こんなときには昨年のYAPCで<a href="http://www.danga.com/words/2007_yapc_asia/yapc-2007.pdf">LiveJournal: Behind The Scenes</a>の中で紹介されている、perlで書かれた非同期処理サーバのGearmanが役に立ちます。Webアプリケーションから時間のかかる処理を別プロセスで動いているGearmanに依頼しておくことで、時間のかかる処理が終わるまで待つことなくユーザにレスポンスを返すことができます。
ほんとうは、時間のかる処理の性質と、必要とされる機能に応じてGearmanとTheSchwartzとを使い分けるべきです。細かいことは <a href="http://d.hatena.ne.jp/tokuhirom/20071017/1192589429">web2.0 時代のジョブキューサーバー Gearman と TheSchwartz の関係について - TokuLog 改めChumbyとどきました日記</a> をご参照ください。

Gearmanはperlからだけでなく<a href="http://bugs.joestump.net/code/Net_Gearman/">PHP</a>や<a href="http://code.sixapart.com/svn/gearman/trunk/api/ruby/lib/">ruby</a>や<a href="http://code.sixapart.com/svn/gearman/trunk/api/python/lib/gearman/">python</a>にもポーティングされていて、ジョブを追加することができます。


Firefoxからも直接追加したいことがあったので、javascriptでGearmanにジョブを追加するコードを書きました。
<pre><code>var Pack = {
    VERSION: "0.0.3",
    _range: function (n) {
        var a = [];
        while (n--) { a.push(n) }
        return a;
    },
    _bytes: function (value, bytes) {
        return this._range(bytes).map( function(n) {
                    return String.fromCharCode( (value & (0xFF << ( 8 * n )) ) >> (8 * n) );
        } );
    },
    _16: function (n) { return this._bytes(n, 2) },
    _32: function (n) { return this._bytes(n, 4) },
    N: function (n) { return this._32(n).join(""); },
    n: function (n) { return this._16(n).join(""); },
    v: function (n) { return this._16(n).reverse().join(""); },
    C: function (n) { return String.fromCharCode( n ); }
};

	
function dispatch_background (method, uniqid, arg, host, port ) { 
	host = host || "localhost";
	port = port || 7003;
	
	var sts = Components.classes["@mozilla.org/network/socket-transport-service;1"].
            getService(Components.interfaces.nsISocketTransportService);
	
	var t = sts.createTransport (null, 0, host, port, null );
	var os = t.openOutputStream(0,0,0)

	var payload = [method, uniqid, arg].join("\0");
	var data = [
		'',
    	    [
   	 	"REQ",
    		Pack.N(18), // submit_job_bg
		Pack.N( payload.length ),
		payload
	    ].join("")
	].join( "\0" );

	os.QueryInterface(Components.interfaces.nsIAsyncOutputStream);
	os.write(data, data.length)
	os.close()

}

// dispatch_background("ticket", uniqid, '{"name":"ku"}');

</code></pre>

GearmanのサーバはTCPのポート7003をlistenしています。ここに<code>Gearman/Util.pm</code>の
<pre><code>sub pack_req_command {
    my $type_arg = shift;
    my $type = $num{$type_arg} || $type_arg;
    die "Bogus type arg of '$type_arg'" unless $type;
    my $arg = $_[0] || '';
    my $len = length($arg);
    return "\0REQ" . pack("NN", $type, $len) . $arg;
}</code></pre>を参考に、同じパケットを作って送れば、どんな言語からでもジョブを追加することができます。


]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/06/javascriptgearman.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/06/javascriptgearman.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">Firefox</category>
											<category domain="http://www.sixapart.com/ns/types#tag">javascript</category>
											<category domain="http://www.sixapart.com/ns/types#tag">perl</category>
					
				
				<pubDate>Thu, 05 Jun 2008 15:13:05 +0900</pubDate>
			</item>
					<item>
				<title>Firefox3の新機能 SmartBookmarkを使ってブックマークや履歴を一定の基準でリストアップする</title>
				<description><![CDATA[(たしか)Firefox3beta5から、ブックマークツールバーに"Most Visited"や"Recently Bookmarked"という項目がデフォルトで入っているようになりました。プロパティをみてもタイトルしか表示されず、どういう仕組みになっているのかわかりません。表示する情報をいろいろ変えられるのならおもしそうだなーと思って、これの作り方を調べました。

<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%204-15.png" height="273" width="476" hspace="4" vspace="4" alt="smartBookmarks" title="smartBookmarks" class="border" />
</div>

以前からあるlivemarkと同じようなみためで、自分のブラウザの履歴やブックマークから一定の基準でフィルタされたデータがリスト表示されます。

ちなみにlivemarkはSafariやIE7にもついているRSSをブックマークのフォルダとして登録できて、RSSのアイテムひとつひとつがブックマークとして表示される機能のことです。ほかにもFirefoxにはmicrosummariesというXPathでページの特定の部分を取り出して表示するめずしい機能があったりします。<a href="http://blog.livedoor.jp/hakin/archives/51112131.html">しげふみメモ:livedoor Reader購読者数のライブタイトル</a>などをご覧ください。

<h3>作り方</h3>
ソースコードをみてみると、このブックマークはFirefoxをインストールしたときに<code>browser/components/nsBrowserGlue.js</code>で作られるようになっていました。

Firefox3で新しく導入された<a href="http://developer.mozilla.org/ja/docs/Places:Annotation_Service">annotation service</a>というのが持っている機能のようです。コードをみると<code>place:queryType=1&sort=12&maxResults=10
</code>のような<code>place:</code>ではじまるURLに似た文字列を<a href="http://www.xulplanet.com/references/xpcomref/ifaces/nsINavBookmarksService.html#method_insertBookmark">nsINavBookmarksService#insertBookmark</a>に渡してブックマークを作った後、<a href="http://www.xulplanet.com/references/xpcomref/ifaces/nsIAnnotationService.html#method_setItemAnnotation">nsIAnnotationService#setItemAnnotation</a>を設定しています。



どうやって作るかわかったところで試しにひとつ作ってみましょう。Firefox3では、ブックマークにタグがつけられるようになっているので、タグに<em>tosee</em>とつけたブックマークを新しいもの順で10件表示するsmarkBookmarkを作ってみます。

<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%205-14.png" height="374" width="282" hspace="4" vspace="4" />
</div>

特権の必要なコードを実行するために、まず<a href="chrome://browser/content/browser.xul">chrome://browser/content/browser.xul</a>を開きます。ウインドウで<em>chrome</em>のアドレスを開いているときはFirebugのコンソールで特権の必要なコードを実行することができるようになります。

<pre><code>
var SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
var bookmarksToolbarIndex = 0;

var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
                getService(Ci.nsINavBookmarksService);
var annosvc = Cc["@mozilla.org/browser/annotation-service;1"].
                getService(Ci.nsIAnnotationService);

var instance =  {
      _uri: function(aSpec) {
        return Cc["@mozilla.org/network/io-service;1"].
               getService(Ci.nsIIOService).
               newURI(aSpec, null, null);
      },
    run: function () {
      var smartBookmark = {
                     queryId: <span style="color: #f08">"ToReadBookmarks"</span>, 
                     itemId: null,
                     title: <span style="color: #f08">"toread"</span>,
                     uri: this._uri(
                                    <span style="color: #f08">"place:queryType=" +
                                    Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
                                    "&sort=" +
                                    Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
                                    "&terms=toread" +
                                    "&maxResults=" + 10 </span>
                     ),
                     parent: bmsvc.toolbarFolder,
                     position: bookmarksToolbarIndex};

      smartBookmark.itemId = bmsvc.insertBookmark(smartBookmark.parent,
                                                      smartBookmark.uri,
                                                      smartBookmark.position,
                                                      smartBookmark.title);
      annosvc.setItemAnnotation(smartBookmark.itemId,
                                    SMART_BOOKMARKS_ANNO, smartBookmark.queryId,
                                    0, annosvc.EXPIRE_NEVER);
    }
};
instance.run()
</code></pre>


コードは長いですが、重要なのは<span style="color: #f08">マゼンダ</span>で色を付けてある部分だけです。これで"toread"という名前のsmartBookmarkがブックマークツールバーに作られて、<em>tosee</em>というタグをつけたブックマークが表示されるようになります！(正確にはタグだけでなく、URLやページのタイトル、自分で書いた説明文にtoseeという文字列を含んでいるブックマークが表示されます)
<div>
<img src="http://labs.gmo.jp/blog/ku/Picture%206-8.png" height="142" width="413" hspace="4" vspace="4" class="border" />
</div>

<em>place:</em>で始まるパラメータをかえればほかのものを表示させることもできます。パラメータの解説は <a href="http://developer.mozilla.org/en/docs/Places_query_URIs">Places query URIs - MDC</a> にあります。ざっとみたところ、ブックマークと履歴のデータをもとにして、特定の基準でデータをリストアップさせることができるようになっています。

<code>twitter.com</code>ドメインでよくアクセスしているURL順で10件表示というようなこともできるので、何か面白い使い方ができると思います。GUIで簡単に設定したりできないのがもったいないところです....

]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/05/firefox3_smartbookmark.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/05/firefox3_smartbookmark.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">Firefox</category>
					
				
				<pubDate>Fri, 30 May 2008 17:16:31 +0900</pubDate>
			</item>
					<item>
				<title>jqueryのpackedバージョンは読み込みに(80msくらい)時間がかかる</title>
				<description><![CDATA[先日<a href="http://mtl.recruit.co.jp/blog/2008/04/the_jui_2008_tokyo.html">The JUI 2008 Tokyo</a>に参加させていただいて、その中でGreasemonkeyの中で使うならjQueryが便利だよ！というLightning Talkを聞きました。たぶんその発表をされていた内山さんが以前に書かれた<a href="http://blog.fulltext-search.biz/articles/2008/03/01/jquery-loader-for-greasemonkey">We Ain't Seen Nothin' Yet. : GreasemonkeyスクリプトにjQueryを読み込む汎用スクリプト</a>を今朝読んで<code>XPCNativeWrapper</code>があるのでグローバルのネームスペースにいろいろ設定したりするほかのライブラリだと使いにくいというのもあるのを知りました。

<h3>コードの評価にどれくらい時間がかかるのか</h3>
自分も以前に<a href="http://mochikit.com/">MochiKit</a>の非同期処理をラップしてくれる<a href="http://mochikit.com/doc/html/MochiKit/Async.html">Deffered</a>を使いたくてMochiKitをまるまるスクリプトの中に入れて使ったことがありました。そのときMochiKitをまるまるいれたら読み込むのに100msくらいかかるようになった印象があったので、実際どれくらい時間がかかるのか測ってました。

ライブラリ本体がブラウザのメモリキャッシュに入っている状態で、ライブラリ本体を読み込む<code>script</code>タグの前と後とでFirebugの機能である<code>console.time()</code>/<code>console.timeEnd()</code>を使って時間を計りました。
<pre><code>&lt;html&gt;
&lt;head&gt;
&lt;script type="text/javascript" charset="utf-8"&gt;
console.time("t");
&lt;/script&gt;
&lt;script type="text/javascript" charset="utf-8" src="./jquery-1.2.5.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" charset="utf-8"&gt;
var t = console.timeEnd("t");

var s = globalStorage['localhost'];
s.n = s.n ? parseInt(s.n) +1 : 1;
s.sum = s.sum ? parseInt(s.sum) +t : t;
console.log(s);

&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;&lt;/body&gt;
&lt;/html&gt;</code></pre>
何回もリロードして値の平均を取ったりするときは、いまのところFirefoxにしか実装されていませんが<a href="http://developer.mozilla.org/en/docs/DOM:Storage">DOM:Storage - MDC</a>を使って記憶しておくとページをリロードした後でもDOMストレージから同じ値をjavascriptのオブジェクトとして読み出せるので便利です。Firefox2から実装されています。

<h3>評価結果</h3>
scriptタグの読み込みオーバーヘッドを知るために、dataスキームを用いて空のjavascriptも評価させました。環境は2.16GHz Intel Core2Duo OSX10.5.2+Firefox3RC1です。

<table><tr>
  <td>ライブラリ名</td>
  <td>ファイルサイズ</td>
  <td>実行時間(20回の平均)</td>
</tr>

<tr><td>data:text/javascript;base64,</td><td>0</td><td>24.9ms</td></tr>
<tr><td>jquery-1.2.5.js</td><td>98k</td><td>25.1ms</td></tr>
<tr><td>jquery-1.2.5-packed.js</td><td>31k</td><td>108.3ms</td></tr>
<tr><td>MochiKit.js(1.3.1 packed version)</td><td>111k</td><td>112.8ms</td></tr>
<tr><td>prototype-1.6.0.2.js</td><td>124k</td><td>118.5ms</td></tr>
</table>

結果からはjQueryの初期化処理がとても速いことがわかります。(オーバーヘッドを差し引くとほとんど0msで処理が終わっているのは、わりと測定値がばらついたので20回の計測だと誤差の範囲内に実行時間が収まってしまったからです。jQueryの本体のはじめと終わりで測ってみたら4ms程度でした)。

ファイルサイズを小さくするために中身がこんな
<pre><code>
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCh
arCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function
(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e
(c)+'\\b','g'),k[c]);return p}('(G(){I w=19.4G,3k$=19.$;I D=19.4G=19.$=G(a,b){H 2t D.16.5r(a,b)};I
 u=/^[^<]*(<(.|\\s)+>)[^>]*$|^#(\\w+)
</code></pre>
ふうに圧縮されているjQueryのpackedバージョンは<code>eval</code>しているのが災いしてなのか、ファイルサイズは1/3なのに実行時間はふつうのjQueryにくらべて80msくらい長くなってしまって、ほかのライブラリと同じくらい読み込みに時間がかかっていました。

<h3>まとめ</h3>
ページをロードするたびに毎回実行されるGreasemonkeyスクリプトの中で使用するなら、読み込みが圧倒的に速いjQueryが適しています。jQuery-packedはファイルサイズは小さいですが、読み込みは80msくらい遅くなってしまいます。Greasemonkeyスクリプトや、ブラウザキャッシュに永続的に残る設定でjQueryを使うのならpackedでないバージョンのほうが読み込み処理が速くなります。

<div><h3>追記 2008.5.23</h3>
<a href="http://twitter.com/monjudoh/statuses/817251225">Twitter / 文殊堂: @ku minifiedバージョンの方を使うべし＞jQ...</a>でminifiedバージョン(改行やインデント等の不要なスペースを削っただけのもの)があるのを教えていただいたので、試してみました。通常バージョンと同じくらいの時間(4~5ms)で読み込まれてファイルサイズも少し小さい(51k)ので、Greasemonkey等に組み込むならminifiedバージョンが最適です。


</div>


]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/05/jquerypacked80ms.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/05/jquerypacked80ms.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">experiments</category>
											<category domain="http://www.sixapart.com/ns/types#tag">javascript</category>
					
				
				<pubDate>Thu, 22 May 2008 20:24:59 +0900</pubDate>
			</item>
					<item>
				<title>infra-RED LED throwies をセンサバーのかわりにしてWiiリモコンを動かす</title>
				<description><![CDATA[みなさま<a href="http://graffitiresearchlab.com/">Graffiti Research Lab</a>をご存じでしょうか。

ハイテク(とかローテクとか)を駆使して最先端のグラフィティをテーマに活動している集団で、ビルにレーザーで落書き映像を投影する<a href="http://graffitiresearchlab.com/?page_id=76">L.A.S.E.R. Tag</a>が有名です(<a href="http://graffitiresearchlab.com/?page_id=138">MoMAで展示</a>もあったそうで)。Graffiti Research Labにはいろんな作品があるんですが(ページの右側にリストされています)、その中で個人でもわりとお手軽に楽しめるのが<a href="http://graffitiresearchlab.com/?page_id=6">Graffiti Research Lab » LED Throwies</a>です。
<div>
<object width="425" height="355"><param name="movie" value="http://www.youtube.com/v/TrnLF04qmsM&hl=en"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/TrnLF04qmsM&hl=en" type="application/x-shockwave-flash" wmode="transparent" width="425" height="355"></embed></object>
</div>

<h3>LED throwies の作り方</h3>
3Vのボタン型リチウム電池にLEDを挟んで磁石と一緒にテープでくっつけたらできあがり。あとは身近にある金属の部分がある何かにくっつけて楽しみます。部品の調達方法から組み立てかたまで、詳しいことは<a href="http://www.instructables.com/id/LED-Throwies/">LED Throwies - Instructables</a>に載っています(日本だとリチウム電池と磁石が安く手に入らなくてひとつあたりの原価が100円を越えてしまいます。もし安く手に入るところがあったらぜひ教えてください！)。



自分もちょっと前に作って遊んだことがあって、今日はそのとき買ったLEDを引っ張りだしてきました。下の写真は磁石なしバージョンです。
<div>
<img src="http://labs.gmo.jp/blog/ku/DSC05715.jpg" height="480" width="640" hspace="4" vspace="4" alt="LED throwies" title="LED throwies" class="border" /></div>

磁石でくっつけなくても透明なテープで貼ってしまえばどこにでも貼付けられます。どれくらいの間光ってるかはLEDの種類にも依りますが、青系で1日くらい、赤系だと2~3日くらい持ちます。

<h3>赤外線LED throwies</h3>
ここでいきなりはなしがきれいなLED throwiesからWiiのセンサバーのはなしになります。

Wiiのセンサバーは、センサとは名ばかりで実際にはセンサバーの中に入っている赤外線LEDから出た光をWiiリモコンで受け取ってリモコンの向きを検知しているので、赤外線を出すものであればなんでもセンサバーの変わりにできます。

<a href="http://japanese.engadget.com/2006/11/28/wii-candlelight/">Wiiのセンサーバーはろうそくで代用可能 - Engadget Japanese</a>ならば、なんでもいいんじゃないの？と思って赤のLEDをセンサバーに見立ててWiiリモコンが使えないか試してみたけどさすがにふつうの赤のLEDでは代用できず。秋葉原で950nmをピークに持つ東芝の赤外線LED(スペックシートは行方不明...)を買ってきました(ちなみにピークは950nmでなくても920~970nmの範囲では問題なく動作しました)。

それで作ったのが下の写真の赤外線LED throwiesです。
<div>
<img src="http://labs.gmo.jp/blog/ku/DSC05716.jpg" height="480" width="640" hspace="4" vspace="4" alt="infra-red LED throwies" title="infra-red LED throwies" class="border" />
</div>

赤外線は目に見えないのでまったく光ってるように見えませんがこれで光っています(デジカメは赤外線の波長にも感度があるので映るのですが、そんなにはっきりとはうつりません)。


<div>infra-RED LED throwies + Darwiin remote</div>
OSXには<a href="http://sourceforge.net/projects/darwiin-remote/">DarwiinRemote</a>というWiiリモコンでマウスを操作したりするツールがあります(<a href="http://labs.gmo.jp/blog/ku/2008/03/wiifirefoxjavascriptwiiremocom_firefox3.html">WiiRemoCom Firefox3対応版</a>でもDarwiinRemoteの基盤である<a href="http://sourceforge.net/project/showfiles.php?group_id=183966&amp;package_id=214973">WiiRemoteFramework</a>を利用しています)。これを使えばOSX上で簡単にWiiリモコンを使ってクリックしたりはできるのですが、センサバーを持ち歩くわけにもいかないのでマウスの操作が加速度センサでしかできないのが問題でした。

が、この赤外線LED throwiesを Mac Book Pro の上にテープで貼って固定すればちゃんとセンサバーのかわりとして機能します！
<div>
<img src="http://labs.gmo.jp/blog/ku/DSC05718.jpg" height="480" width="640" hspace="4" vspace="4" alt="infra-RED LED throwies + Darwiin remote" title="infra-RED LED throwies + Darwiin remote" class="border" /></div>

きれいに撮れてないものの、画面の中にある黄色い■がWiiリモコンのIRセンサを認識している証拠でWiiリモコンを動かすとマウスが動いてボタンを押せばクリックできるので完全にマウスの代わりに使うことができます(ドラッグアンドドロップとかはできなかったかも)。

赤外線LED throwiesはほんもののセンサバーと違ってテープでどこにでも貼ることができるので、いつでもどこでもWiiリモコン用のセンサバーとして使えます。プレゼン前にスクリーンの下に貼っておけばスクリーンをレーザーポインタで指し示す感覚でマウスを動かすことできるでしょう！

ちなみにwindowsでも<a href="http://onakasuita.org/wii/">WiinRemote</a>でWiiリモコンをマウスの代わりに使えるそうなので、赤外線LED throwiesと組み合わせてWiiリモコンで指し示しながらのプレゼンが可能です。]]></description>
				<link>http://labs.gmo.jp/blog/ku/2008/05/infrared_led_throwies_wii.html</link>
				<guid>http://labs.gmo.jp/blog/ku/2008/05/infrared_led_throwies_wii.html</guid>
				
															<category domain="http://www.sixapart.com/ns/types#tag">tips</category>
											<category domain="http://www.sixapart.com/ns/types#tag">wii</category>
					
				
				<pubDate>Fri, 16 May 2008 20:35:50 +0900</pubDate>
			</item>
		
	</channel>
</rss>
