2008/07/08

はてな
http://subtech.g.hatena.ne.jp/cho45/20080708/1215450151
http://subtech.g.hatena.ne.jp/miyagawa/20080708/1215473551
#!/usr/bin/env python
# -*- coding: utf-8 -*-

class X:
  def __init__(self):
    self.s = "ひだまり"

  def __lt__(self, v):
    print "%s %s" % (self.s, v)
    return self
 
  def __div__(self, v):
    if isinstance(v, int):
      self.s += "スケッチ"
    else:
      self.s += "365"
    return self

X = X()
_ = 1

X / _ / X < "来週も見てくださいね!"
ごめんなさい。それが何かしらない...orz

2008/06/27

はてな
TheSchwartzも良いのですが、MySQLが必要だったりポーリングのタイムラグがあったりと、要件に合わない場合というのも出て来たりします。こんな時はどうするか、解決策の一つにメッセージバスがあります。
メッセージバスとして一般的に有名なのが、DBusです。Linux等では数多くのアプリケーションが採用しており、GNOMEもBonoboからの移行を表明、KDE4でも採用される様です。ちなみに私が愛用している軽量デスクトップROXは当初に採用しており、ちょっとしたpythonのコードでリッチなアプリケーション間通信が実現出来ています。他にも既に色々なプロジェクトでDBusが使われています。
さて今日はNet::DBusを使い、TheSchwartzと似たような事が出来ないかをWindows上でやってみようかと思います。
まず、DBusのWindows実装であるwinDBusをインストールします。winDBusはこちらで配布されておりバイナリもダウンロード出来ます。
ここでまず、DBusの動作を確認します。winDBusを展開したディレクトリの中のbinフォルダに移動し以下の様にしてデーモンを起動します。
C:¥winDBus¥> dbus-daemon.exe --config-file=C:/winDBus/dbus/bin/session.conf
winDBusを"C:¥Programme"に展開された場合には
C:¥Programme¥> dbus-daemon.exe --session
とするだけでも動作します。winDBusの場合、unixドメインソケットをサポートしていませんので、上記session.confに書かれている
<listen>tcp:host=localhost,port=12434</listen>
の値を環境変数「DBUS_SESSION_BUS_ADDRESS」に設定します。
set DBUS_SESSION_BUS_ADDRESS=tcp:host=localhost,port=12434
その上でdbus-daemon.exeと同じ位置にあるtest_names.exeを実行し
Successfully acquired name 'org.freedesktop.DBus.Test'
Successfully acquired name 'org.freedesktop.DBus.Test-2'
Successfully acquired name 'org.freedesktop.DBus.Test_2'
と表示されれば動作確認は終了です。
次にNet::DBusをインストールします。まずNet::DBusはビルドにpkg-configを使うので`pkg-config --cflags dbus-1`が扱えないVCではビルドに難があります。私の場合はMinGWを使用しました。
まず、winDBusに同梱されていなかったdbus-1.pcを作成します。winDBusをインストールしたディレクトリを指定して
prefix=C:/winDBus/dbus
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

Name: D-Bus
Description: D-Bus Protocol Library
Version0.90
Libs: -LC:/winDBus/dbus/lib -ldbus-1
Cflags: -IC:/winDBus/dbus/include
という内容でdbus-1.pcを保存します。次に環境変数PKG_CONFIG_PATHにこのdbus-1.pcを保存したディレクトリを設定し、cpanからインストールします。おそらくこれでインストール出来るかと思いますが、出来なかった方はVCのコンパイラのパスを外し、MinGWのパスを設定して確認して見て下さい。
これでようやくperlからDBusを使える準備が揃いました。
さて、サーバのソース。DBusのセッションからバスを取得し、サービスをエクスポート、ワーカーの役目をするオブジェクトを作成します。ワーカーオブジェクトにはJobStartというメソッドを定義します。
#!/usr/bin/perl

use strict;
use warnings;
use threads;
use YAML;
use Net::DBus;
use Net::DBus::Service;
use Net::DBus::Reactor;

package MyWorker;
use base qw(Net::DBus::Object);
use Net::DBus::Exporter qw(net.kaoriya.mattn.DBusWorker);

sub new {
    my $class = shift;
    my $service = shift;
    my $self = $class->SUPER::new($service, "/MyWorker");
    bless $self, $class;
    
    return $self;
}

dbus_method("JobStart", [["dict", "string", "string"]], [["dict", "string", "int32"]]);
sub JobStart {
    my $self = shift;
    my $job = shift;
    my $thread = threads->new(sub {
        my $job = shift;
        printf "processing job%03d : %s\n", threads->tid, $job->{request};
        sleep 10;
        printf "done job%03d\n", threads->tid;
    }, $job);
    return { "id" => $thread->tid };
}

package main;

my $bus = Net::DBus->session();
my $service = $bus->export_service("net.kaoriya.mattn.DBusWorker");
my $object = MyWorker->new($service);
Net::DBus::Reactor->main->run();
DBusは非同期通信も提供していますが、リクエストを連続で送った際サーバがビジー状態だとクライアントが側が応答を待ってロックします。
そこで上記ソースではスレッドを起こし、重たい処理をさせる様になっています。スレッド内では"threads->tid"で言わばジョブ番号の役割をするスレッドIDを使ってユニークな処理が出来るかと思います。
さらにクライアントのコード。
#/usr/bin/perl

use strict;
use warnings;

use Net::DBus;
use Net::DBus::Reactor;
use Net::DBus::Callback;
use Net::DBus::Annotation qw(:call);

my $request = shift @ARGV || 'request at '.time();
my $bus = Net::DBus->session();
my $service = $bus->get_service("net.kaoriya.mattn.DBusWorker");
my $object = $service->get_object("/MyWorker");
my $res = $object->JobStart({ request => $request });
printf "job was accepted as job%03d\n", $res->{id};
DBusセッションからもらったMyWorkerオブジェクトのJobStartメソッドにリクエスト文字列を指定して送信します。
まずクライアントの実行結果は
C:¥winDBus¥test¥> perl client.pl
job was accepted as job001
C:¥winDBus¥test¥> perl client.pl
job was accepted as job002
C:¥winDBus¥test¥> perl client.pl
job was accepted as job003
クライアントではjob001を実行した後、10秒待ってjob002、job003を投入しました。そしてサーバの実行結果
processing job001 : request at 1214560175
done job001
processing job002 : request at 1214560192
processing job003 : request at 1214560195
done job002
done job003
おぉ!非同期ですね!
DBusでは、引数の構成および戻り値の構成をdbus_methodにて指定でき、文字列(string)や数値(int32等)、配列(array)、タプル(tuple)、構造体(struct)を引き渡したり、戻り値として返したり出来ます。
さらに、Net::DBusに同梱されている"example-client-async.pl"の様に非同期送信結果を受け取り、完了イベントで結果を受け取るという事も出来ます。
#/usr/bin/perl

use warnings;
use strict;

use Net::DBus;
use Net::DBus::Reactor;
use Net::DBus::Callback;
use Net::DBus::Annotation qw(:call);

my $bus = Net::DBus->session();

my $service = $bus->get_service("org.designfu.SampleService");
my $object = $service->get_object("/SomeObject");

print "Doing async call\n";
my $reply = $object->HelloWorld(dbus_call_async, "Hello from example-client.pl!");

my $r = Net::DBus::Reactor->main;

sub all_done {
    my $reply = shift;
    my $list = $reply->get_result;
    print "[", join(", ", map { "'$_'" } @{$list}), "]\n";

    $r->shutdown;
}

print "Setting notify\n";
$reply->set_notify(\&all_done);

sub tick {
    print "Tick-tock\n";
}


print "Adding timer\n";
$r->add_timeout(500, Net::DBus::Callback->new(method => \&tick));

print "Entering main loop\n";
$r->run;
今回はWebで扱いやすい様に、非同期で結果を待機する処理ではなくサーバ側でスレッドを実行していますが、デスクトップクライアントを作る際にはdbus_call_asyncを使う事になるかと思います。

TheSchwartzも面白いですが、DBusも面白いですね。
perlの他にも、C(GLib)はもちろんpythonやJava等でも実装されています(参照)。
皆さんも試してみては如何でしょうか。

2008/06/26

はてな
最近TheSchwartが賑わってる様ですね。
TheSchwartzで仕事をあとにまわす - bits and bytes

なければ勝手に作ってくれるのかなと思ってSYNOPSISのコードを実行してみたけどやっぱり自動でできたりはしなそうだったので調べたらCatalyst and TheSchwartz: Reliable JobQueue in a great framework - Voxschema.sqlを使うといいと書いてありました。このスキーマをmysqlで実行すればTheSchwartzのキューを管理するためのテーブルが出来上がります。

http://labs.gmo.jp/blog/ku/2008/06/theschwartz.html

Twitter / ku: TheSchwartzおもしろかった sqliteで動...

TheSchwartzおもしろかった sqliteで動くようにしたいけどsqliteようのschemeかきかたわかんない

http://twitter.com/ku/statuses/843135311

って事でSQLiteでも動く様に出来ないか調べてみました。
schema.sqlは、MySQL用のスキーマで
CREATE TABLE funcmap (
        funcid         INT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,
        funcname       VARCHAR(255) NOT NULL,
        UNIQUE(funcname)
);

CREATE TABLE job (
        jobid           BIGINT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,
        funcid          INT UNSIGNED NOT NULL,
        arg             MEDIUMBLOB,
        uniqkey         VARCHAR(255) NULL,
        insert_time     INTEGER UNSIGNED,
        run_after       INTEGER UNSIGNED NOT NULL,
        grabbed_until   INTEGER UNSIGNED NOT NULL,
        priority        SMALLINT UNSIGNED,
        coalesce        VARCHAR(255),
        INDEX (funcid, run_after),
        UNIQUE(funcid, uniqkey),
        INDEX (funcid, coalesce)
);

CREATE TABLE note (
        jobid           BIGINT UNSIGNED NOT NULL,
        notekey         VARCHAR(255),
        PRIMARY KEY (jobid, notekey),
        value           MEDIUMBLOB
);

CREATE TABLE error (
        error_time      INTEGER UNSIGNED NOT NULL,
        jobid           BIGINT UNSIGNED NOT NULL,
        message         VARCHAR(255) NOT NULL,
        funcid          INT UNSIGNED NOT NULL DEFAULT 0,
        INDEX (funcid, error_time),
        INDEX (error_time),
        INDEX (jobid)
);

CREATE TABLE exitstatus (
        jobid           BIGINT UNSIGNED PRIMARY KEY NOT NULL,
        funcid          INT UNSIGNED NOT NULL DEFAULT 0,
        status          SMALLINT UNSIGNED,
        completion_time INTEGER UNSIGNED,
        delete_after    INTEGER UNSIGNED,
        INDEX (funcid),
        INDEX (delete_after)
);
というSQLなので、これをSQLite用に書き換えます。
cl.pocari.org - SQLite で auto-increment なフィールドを作成する方法

つまり,SQLite で auto-increment なフィールドを作りたければ,INTEGER PRIMARY KEY を指定してあげればいいらしい.

http://cl.pocari.org/2006-02-12-1.html
こちらの記事でも書かれている通り、SQLiteではINTEGER PRIMARY KEYを指定すればok。
※ちなみにUNSIGNEDとかNOT NULLとか付けると動かないです。

で書き換えたSQLは以下の通り
CREATE TABLE funcmap (
        funcid         INTEGER PRIMARY KEY,
        funcname       VARCHAR(255) NOT NULL,
        UNIQUE(funcname)
);

CREATE TABLE job (
        jobid           INTEGER PRIMARY KEY,
        funcid          INTEGER NOT NULL,
        arg             MEDIUMBLOB,
        uniqkey         VARCHAR(255) NULL,
        insert_time     INTEGER UNSIGNED,
        run_after       INTEGER UNSIGNED NOT NULL,
        grabbed_until   INTEGER UNSIGNED NOT NULL,
        priority        SMALLINT UNSIGNED,
        coalesce        VARCHAR(255),
        UNIQUE(funcid, uniqkey)
);
CREATE INDEX job_idx_1 ON job (funcid, run_after);
CREATE INDEX job_idx_2 ON job (funcid, coalesce);

CREATE TABLE note (
        jobid           INTEGER NOT NULL,
        notekey         VARCHAR(255),
        value           BLOB
);

CREATE TABLE error (
        error_time      INTEGER UNSIGNED NOT NULL,
        jobid           INTEGER NOT NULL,
        message         VARCHAR(255) NOT NULL,
        funcid          INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX error_idx_1 ON error (funcid, error_time);
CREATE INDEX error_idx_2 ON error (error_time);
CREATE INDEX error_idx_3 ON error (jobid);

CREATE TABLE exitstatus (
        jobid           INTEGER PRIMARY KEY,
        funcid          INTEGER NOT NULL DEFAULT 0,
        status          SMALLINT UNSIGNED,
        completion_time INTEGER UNSIGNED,
        delete_after    INTEGER UNSIGNED
);
CREATE INDEX exitstatus_idx_1 ON exitstatus (funcid);
CREATE INDEX exitstatus_idx_2 ON exitstatus (delete_after);
あとはコレを
# sqlite3 the_schwartz < theschwartz_schema.sql
としてDBファイルを作る。
さてサーバのコードはDSNを"dbi:SQLite:the_schwartz"にするだけ。
#!/usr/bin/env perl
package MyWorker;
use strict;
use utf8;
use warnings;
use base qw( TheSchwartz::Worker );
use Data::Dumper;

binmode STDERR, ":encoding(cp932)" if $^O eq "MSWin32";

sub work {
    my ($class, $job) = @_;
    warn "お仕事ですよ! @{[ Dumper($job->arg) ]}\n";
    $job->completed();
}

package main;
use strict;
use warnings;
use TheSchwartz;

my $client = TheSchwartz->new(
    databases => [ +{ dsn => 'dbi:SQLite:the_schwartz' } ]
);
$client->can_do('MyWorker');
$client->work();
さらにクライアントのコード
#!/usr/bin/env perl
use strict;
use warnings;
use TheSchwartz;

my $client = TheSchwartz->new(
    databases => [ +{ dsn => 'dbi:SQLite:the_schwartz' } ]
);
$client->insert('MyWorker' => +{ hoge => "fuga" });
実行すると
お仕事ですよ! $VAR1 = {
          'hoge' => 'fuga'
        };
という風にサーバ側で表示されます。 ちなみに、DBD::SQLiteのバグで
closing dbh with active statement handles at .../Data/ObjectDriver/Driver/DBI.pm line 566.
という表示が出る様ならばココにある"issue-17603.tar.gz"のパッチを当てるといいです。どうやらこのバグ、デグレっぽいですね。

ま、結局SQLiteでやっちゃったら同一マシンだし負荷分散にはならない(UIロックを避けるという意味では有用)んですけどね。

2008/06/19

はてな
グダグダの文章ですみません。
perl-monger.orgってはてなグループじゃダメなの? - DTP+印刷営業メモ

なんとなくだが近所の人がperl-monger.orgな話をしていてさっきページを見ていたんだけど、これってはてなグループ(なんとか部みたいな)のじゃダメなのかなぁ、と思った。

...snip...

っていうかWikiに色々なコンテンツを追加していくてのはダメなんだろうかと普通に思ったんだけど。

Wikiでもログイン制にしてしまえば同じような事は出来るし、弄れば同じ風貌に出来なくもないと思う。ただWikiだと筆者に掛かる責任ぽい物だとか、評価だとか、「perl-mongers」っていう組織感だとかが表現し辛いんじゃないかなぁ。
例えば「Journal of miyagawa」がWikiだったら少し感じが変ってくる様な気がする。(私だけかもしれない)

記事を原稿用紙に書くのかフリースケッチに描くのか...そんな程度の違いなのかもしれないけど、私は今の形がCoolだと思う。

答えになってないなぁ。

追記
組織感ってのは、例えばsubtechみたいなもんかな。

はてな
面白いもの見つけた。(via ku's twitter post)
Tumblr, RMagick and a Photo Frame! - igvita.com

Over the course of the past year or so, I got myself into a habit of using Tumblr for storing insightful snippets and quotes from my daily endeavours on the internet. Usually, this entails saving a paragraph (too long for tweet, and not enough for del.icio.us) so I can revisit it later.

However, having Tumblr to save the quotes is nice, but I rarely (if ever) went back to read them. Pondering this dilemma, I spotted my Phillips digital photo frame, and a quick weekend project was born: Tumblr API, RMagick to render the images, and an SD card full of wisdom and quotes for easy consumption!

どっちかって言うと、書いてる理由が面白い。要は「Tumblrの引用は面白いけど一々見に行くのめんどいよね!ならって事でデジタルフォトスタンドを使っちゃおうと思った」て感じかな。

動かすにはrubyのImageMagick拡張rmagickが必要。
私はPhillips digital photo frameは持ってないけど、chumbyとか使うと良く似た事出来るのかな?
ソースはそのまま使えました。日本語はImageMagickがghostscriptを使ってるのでTrueTypeフォント直接指定すれば動きます。
--- render-tumblr.rb.orig   Sat Jun 07 19:32:46 2008
+++ render-tumblr.rb    Thu Jun 19 12:02:57 2008
@@ -62,7 +62,7 @@
        img.background_color = "transparent"
        img.pointsize = 23
        img.antialias = true
-       img.font = "Helvetica"
+       img.font = "c:\\windows\\fonts\\meiryo.ttc"
    end
    quote.write "text.png"
    
@@ -72,12 +72,12 @@
        img.background_color = "transparent"
        img.pointsize = 18
        img.antialias = true
-       img.font = "Helvetica"
+       img.font = "c:\\windows\\fonts\\meiryo.ttc"
    end
    source.write "source.png"  
 end
 
-doc = Hpricot.XML(open("http://igrigorik.tumblr.com/api/read?type=quote&num=5"))
+doc = Hpricot.XML(open("http://mattn.tumblr.com/api/read?type=quote&num=5"))
 (doc/'post').each do |post|
    text = CGI::unescapeHTML((post/'quote-text').inner_html).gsub(/&[^;]*;?/,'')
    source = CGI::unescapeHTML((post/'quote-source').inner_html).gsub(/\<.*?\>/, '').gsub(/&[^;]*;?/,'').strip

実行するとquotesディレクトリに以下の様なファイルが出来上がりました。
render-tumblr-34978042

render-tumblr-36274314

render-tumblr-37077963

render-tumblr-37111519

render-tumblr-38695135

面白いですね。あとは何とかしてこれをchumbyとかで動かせればいいのですが、いかんせんchumby持ってない!chumbyでrubyが動くかどうかも知らない。
って事で誰か後はよろしく。

2008/06/18

はてな
blog chart.jp

「ブログチャート」なら、今注目のブログのヒットチャートや木になるブログの影響力レベルがすぐに分かります。

もちろんあなたのブログも登録可能。自分のブログの特徴を他のブログと比較してみて下さい。

http://blogchart.jp/
404 Blog Not Found:"ブログ"とは"\xa5\xd6\xa5\xed\xa5\xb0"ですかとblogchart.jp

文字文字化け化けなmailが来たので調べてみたら...

  • Content-Type:ヘッダーもMime-Version:ヘッダーも不在
  • Subject:ヘッダーもMIME header encodingされていない
  • 全部EUC-JPのまま

もうしわけないけど、最近のSPAMだってこのあたりはきちんとしてるよ!

http://blog.livedoor.jp/dankogai/archives/51067631.html
メールの文字化けもなんだけど、届いたブログチャート貼り付け用スクリプトが
<script type="text/javascript" src="http://blogchart.jp/js/blogparts.js"></script>
<script type="text/javascript"><!--
id="1784";blogurl="http://mattn.kaoriya.net";partstype="b";viewBlogparts();
// --></script>
思いっきりグローバル汚染。...orz

2008/06/17

はてな
元ネタ
M.C.P.C.: はてなお気に入りAPIを使ってブログパーツ作例

はてなお気に入りAPIを使ってブログパーツ?を作ってみた...

http://blog.dtpwiki.jp/dtp/2007/09/api_60ed.html
ソースは以下
jquery.hatenaFav.js
// --------------------------------------------------------------
// jquery.hatenaFav.js : hatenaお気に入り画像表示ブログパーツ
// based on: http://blog.dtpwiki.jp/dtp/2007/09/api_60ed.html
// (required: jquery.js)
// --------------------------------------------------------------

(function($){
  $.fn.hatenaFav = function(options){
    return this.each(function(index){
      var it = this, $this = $(this);
      it.opts = $.extend({}, $.fn.hatenaFav.defaults, options);
      if (it.opts.loader) $('<img/>').attr('src', it.opts.loader).appendTo($this);
      else $this.html('loading...');
      $.ajaxSetup({cache:true});
      $.getJSON('http://s.hatena.ne.jp/' + it.opts.user + '/favorites.json?callback=?', function(data) {
        $this.html('');
        $.each(data.favorites, function(index, item) {
          if (index > it.opts.max) {
            $('<a/>').attr('href', '#').click(function() {
              options.max = data.favorites.length;
              $this.hatenaFav(options);
              return false;
            }).attr('class', 'hatena-fav-more').css('font-size', '0.8em').text('more...').appendTo($this);
            return false;
          }
          $('<img/>').attr('src', 'http://www.hatena.ne.jp/users/' + item.name.slice(0, 2) + '/' + item.name + '/profile_s.gif')
          .css({
            width: it.opts.size,
            height: it.opts.size,
            border: it.opts.border,
            title: 'id:' + item.name,
            alt: 'id:' + item.name
          }).wrap('<a/>')
            .parent().attr('href', 'http://d.hatena.ne.jp/' + item.name + '/').attr('target', it.opts.target).appendTo($this);
        });
      });
    });
  };
  $.fn.hatenaFav.defaults = {
    user:   'jkondo',
    size:   '16px',
    border: '0px',
    target: '_blank',
    max:    10,
    //loader: 'http://mattn.kaoriya.net/images/ajax-loader.gif'
  };
})(jQuery);
使い方はこんな感じ
<style type="text/css"><!--
.hatena-fav-container {
    font-family: meiryo, osaka;
    font-weight: bold;
    text-align: left;
    width: 200px;
    border: 1px solid gray;
}
.hatena-fav-container a, .hatena-fav-container a:visited {
    color: blue;
}
.hatena-fav-title {
    background-color: #ddd;
    text-align: center;
}
.hatena-fav-icons img {
    margin: 1px;
}
--></style>
<script type="text/javascript" src="jquery-latest.js"></script>
<script type="text/javascript" charset="utf-8" src="jquery.hatenaFav.js"></script>
<script type="text/javascript"><!--
$(function() {
    $('.hatena-fav-icons').hatenaFav({ user: 'mattn', size: 16 });
});
--></script>
<div class="hatena-fav-container">
    <div class="hatena-fav-title">
        <a href="http://mattn.kaoriya.net/">はてなお気に入り</a>
    </div>
    <div class="hatena-fav-icons"></div>
</div>
hatenaFavメソッドの引数に渡すuserとして、はてなのユーザIDを入れるとそのユーザのお気に入りユーザが表示されます。
実行結果は↓

続きを読む...


新規投稿