コマンドラインのFlash開発環境を整える
HTML5への流れが加速している昨今ですが、このところ仕事の中でFlashに触る機会が多くなってきました。
せっかくなので、忘れないうちにActionScriptによるFlash開発環境を整える手順をメモしておこうと思います。
前提条件
ポイントは以下の2つ。
- コマンドラインで完結すること
- 無料でできること
さすがに動作確認はブラウザを使ってやりますが、その他の作業は基本的にSSH接続したターミナル上で行います。
サーバは CentOS 5.5 64bit版、クライアントは Windows 7 64bit版です。サーバ機にはGUI環境は入っていません。
Java実行環境の準備
後述するFlex SDKを動作させるために、Java実行環境(JRE)が必要になります。
上記ページから、今回は Linux x64 RPM をダウンロードし、以下のコマンドでインストールします。2010年11月20日現在の最新版は Version 6 Update 22 でした。
$ sudo sh jre-6u22-linux-x64-rpm.bin ... $ which java /usr/bin/java $ java -version java version "1.6.0_22" Java(TM) SE Runtime Environment (build 1.6.0_22-b04) Java HotSpot(TM) 64-Bit Server VM (build 17.1-b03, mixed mode)
コンパイラの準備
コンパイラにはAdobeが公開している Flex SDK を利用します。
Flex SDKには2種類あり、オープンソースプロダクトのみで構成された Open Source Flex SDK と、それにオープンソースでないものを含めた Free Adobe Flex SDK が用意されています。どちらも無料で使うことができます。
今回自分が想定している用途ではオープンソース版で特に不足はないので、ライセンスも馴染みのあるMPLですし、オープンソース版を使わせてもらうことにしました。
上記ページ中の Open Source Flex 4 SDK リンクからダウンロードできます。2010年11月20日現在の最新版は Flex 4.1 Update Release (4.1.0.16076) でした。
ZIPパッケージになっていますので、適当な場所に展開し、中のbinディレクトリにPATHを通しておきます。
$ unzip flex_sdk_4.1.0.16076_mpl.zip -d ~/flex ... $ echo 'export PATH=$HOME/flex/bin:$PATH' >> ~/.bashrc $ . ~/.bashrc $ which mxmlc ~/flex/bin/mxmlc $ mxmlc -version Version 4.1.0 build 16076
サンプルプログラムの作成
早速なにか作ってみます。
package { import flash.display.Sprite; import flash.display.Bitmap; import flash.display.BitmapData; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; import flash.events.Event; [SWF(width="640", height="480", frameRate="30")] public class HelloWorld extends Sprite { private var base:Sprite; public function HelloWorld():void { var txtFld:TextField = new TextField(); txtFld.autoSize = TextFieldAutoSize.LEFT; txtFld.text = 'Hello World!'; var fmt:TextFormat = new TextFormat(); fmt.size = 32; txtFld.setTextFormat(fmt); var bmpData:BitmapData = new BitmapData(txtFld.width, txtFld.height); bmpData.draw(txtFld); var bmp:Bitmap = new Bitmap(bmpData); bmp.smoothing = true; bmp.x = -(bmp.width / 2); bmp.y = -(bmp.height / 2); base = new Sprite(); base.x = 320; base.y = 240; base.addChild(bmp); addChild(base); addEventListener(Event.ENTER_FRAME, onEnterFrame); } private function onEnterFrame(event:Event):void { base.rotationY += 4; } } }
上のコードをHelloWorld.asというファイル名で保存し、以下のコマンドでコンパイルします。
$ mxmlc HelloWorld.as
コンパイルが完了すれば、同じディレクトリにHelloWorld.swfというファイルが作成されています。あとはこれをブラウザで読み込むだけです。
せっかくサーバ上で開発しているのですから、コンテナとなるHTMLファイルも一緒に作り*1、そのままApache経由で参照できるようにしてしまうのが楽でしょう。
ちなみに、上記のサンプルは単に "Hello World!" が回転するだけのシンプルなFlashです。
コンパイルオプション
上記のコマンドでコンパイルすると警告メッセージが出ますが、これは -static-link-runtime-shared-libraries オプションを付けることで回避できます。
$ mxmlc -static-link-runtime-shared-libraries HelloWorld.as
コンパイルオプションは、上記のようにコマンドラインから指定する他に、設定ファイルに書いておくこともできます。
設定ファイルには、${FLEX_ROOT}/frameworks/flex-config.xml に配置されている共通設定ファイルと、コンパイル対象ファイルごとのローカル設定ファイルがあります。
ローカル設定ファイルは、コンパイル対象のASファイルと同じディレクトリに -config.xml サフィックスをつけて置いておきます。例えば先程の HelloWorld.as の場合、HelloWorld-config.xml というファイルを同じディレクトリに置いておくと、コンパイル時に特に指定をしなくても読み込んでくれます。
他にも、load-config オプションによって明示的に設定ファイルを指定することもできます。
設定ファイルのフォーマットは、一番単純な形だと以下のようになります。
<?xml version="1.0" encoding="UTF-8"?> <flex-config> <static-link-runtime-shared-libraries>true</static-link-runtime-shared-libraries> </flex-config>
オプション指定の優先度は「コマンドラインオプション > load-config で指定した設定ファイル > ローカル設定ファイル > flex-config.xml」となっています。
オプションの簡単なリストは以下のコマンドで確認できます。
$ mxmlc -help advanced list details
Makefile
頻繁にコンパイルを行う場合、make や ant などのビルドツールを使うことになるかと思います。自分は使い慣れた make を利用しています。
シンプルなswf生成用Makefileは以下のような形になります。
MXMLC = mxmlc MXMLCFLAGS = TARGETS = HelloWorld.swf all: $(TARGETS) clean: $(RM) $(TARGETS) .SUFFIXES: .as .swf .as.swf: $(MXMLC) $(MXMLCFLAGS) $<
traceログの出力
デバッグ版の Flash Player では trace() 関数を使ってファイルへのログ出力を行うことができます。
上記ページからデバッグ版プレイヤーをダウンロードしてインストールします。
スタンドアロン版もありますが、Firebugと組み合わせて使えると便利なので、自分は上記ページ中の "Windows Flash Player 10.1 Plugin content debugger (for Netscape-compatible browsers)" をインストールしてFirefoxで動かしています。*2
次に mm.cfg ファイルに以下の設定を加えます。ファイルが無ければ新規作成します。
ErrorReportingEnable=1 TraceOutputFileEnable=1
mm.cfg ファイルと trace() 関数からの出力が記録されるログファイルの場所は以下のページを参照してください。Windows 7 の場合は Windows Vista と同じ場所に配置されています。
trace() 関数によるログ出力を有効にするには、swfをdebugフラグ付きでコンパイルする必要があります。
設定ファイルで指定する場合は以下のようになります。
<?xml version="1.0" encoding="UTF-8"?> <flex-config> <compiler> <debug>true</debug> </compiler> <static-link-runtime-shared-libraries>true</static-link-runtime-shared-libraries> </flex-config>
JavaScriptコンソールへのログ出力
またはもっと簡単に console.log() を使ってFirebugやGoogle ChromeのJavaScriptコンソールにメッセージを出力することもできます。
ActionScriptからJavaScriptを呼び出すには ExternalInterface クラスを使います。以下のようなメソッドをどこかに定義しておくと便利かと思います。
import flash.external.ExternalInterface; public function log(message:Object):void { ExternalInterface.call('console.log', message.toString()); }
これで log() メソッドに渡した引数がFirebugやGoogle ChromeのJavaScriptコンソールに表示されます。
fdbによるリモートデバッグ
もっと本格的なデバッグにはActionScriptのデバッガである fdb が利用できます。fdb は Flex SDK に同梱されています。
開発環境と実行環境が同一ホストであるパターンがもっともシンプルなのですが、今回はfdbをサーバ上で、プレイヤーをクライアント上で動作させた状態でデバッグを行います。
まず、traceログ出力の際に行ったように、debugフラグ付きでswfをコンパイルし、クライアント上のデバッグ版プレイヤーで起動します。
次にサーバ上で以下のコマンドを実行し、fdbを接続待ち受け状態にします。
$ fdb Adobe fdb (Flash Player Debugger) [ビルド 16076] Copyright (c) 2004-2007 Adobe, Inc. All rights reserved. (fdb) run Player が接続するのを待っています
クライアント上のデバッグ版プレイヤーを右クリックし、メニュー中の「デバッガー」をクリックしてパネルを表示、接続先に「ほかのコンピューター」を選んでサーバのIPアドレスを入力します。
「接続する」を選択すると、デバッグ版プレイヤーが待ち受け状態のfdbに接続し、デバッグセッションが開始され、fdbによるデバッグが可能な状態になります。
なお、この際、サーバはクライアントに対して TCP/7935 のポートを開けておく必要があります。
$ fdb Adobe fdb (Flash Player Debugger) [ビルド 16076] Copyright (c) 2004-2007 Adobe, Inc. All rights reserved. (fdb) run Player が接続するのを待っています Player が接続されました。セッションを開始しています。 ブレークポイントを設定して「continue」と入力し、セッションを再開してください。 [SWF] /sample/HelloWorld.swf - 2,287 バイト (解凍後) (fdb)
処理の最初からデバッグを行ないたい場合、セッションを開始した状態でデバッグ版プレイヤーが動作しているページを再読み込みすればOKです。
fdbによるデバッグの詳細は以下のページが参考になります。
その他
あとはリファレンスやその他資料を参考にしながら開発を進めていきます。リファレンスには様々なパッケージが記載されていますが、Flex SDK で利用可能なのは flash, mx, spark の3つのパッケージだけですので注意してください。
他にFlashを開発していく上で詰まりやすそうなのは、クロスドメインポリシーまわりでしょうか。
Flashでクロスドメインの通信を行う場合、通信先のサーバにクロスドメインポリシーファイル "crossdomain.xml" が設置されており、かつ適切な設定がなされている必要があります。これは URLLoader/URLRequest のような単発リクエストの場合も、Socket/XMLSocket のような持続的接続のような場合も同様です。
Flash開発はまだまだ触り始めたばかりで慣れていませんが、ぼちぼち色々試していこうと思います。
いろんな画像をAA Quine化するよ!
この辺の流れを見て楽しそうだなぁと思ったので真似してみました。
- RubyでうどんげQuine(とAA型Quineの作り方講座) - ぬいぐるみライフ(仮)
- 404 Blog Not Found:perl - Quine.pm で(ほぼ)あらゆるPerl Scriptをquineに
- うどんげQuineに対抗して遊んでみた - As Sloth As Possible
ミクさんQuine
まずはこちらから。
※2010年9月20日追記:ミクさんを更新しました。Gistのコードも更新済みです。詳しい更新内容は記事末尾の追記欄にて。
eval$s=%w~ a=->(b,c, d,e,f){%`#{(c)?"re quire'zlib';":' '}g=Marshal.load(#{ (c)?'Z lib::Inflate.infl ate(':''}'#{b}'. unpack('m')[0]#{(c)?')': ''});h= 'eval$s =%w'<<126<<($s*#{d} );i=' ';j=- 1; #{ e*f}.times{|k|i<< (g[ k]== 1? h[j+=1]:32);i<<10i f (k% #{e} ==#{e-1})};i[-7,6] =' ' < <126<< '.join';puts(i)#`}; $*[ 0]? ( require' RMagick';include(Ma gi ck );l=$*[0];m =($*[1]||80).to_i;n= Qu a ntu mRange*( $*[2]||0.5).to_f;o= Im a g eL ist.new(l ).flatten_images;p= o. c olu mns; q=o.rows;r=->(o){(p > q ) ? o .re size (m ,m*q/p/2):o.resize (m * p / q ,m /2)}; o=( p>m||q>m)?r[o.bile v el_ch an ne l (n )] :r [o].b ile vel_channel(n);e= o. columns ;f= o.r o w s;rais e('INVALID_ IMAGE_DATA')if(e<0||f <0 );s=''; o.ea ch _pixel {|t|s<<((t.red< n)?'1':'0')};s[0,10] ='1 '*10;s[ -6,6 ]= '1'*6; u= s.count('1');v=Marshal.dump(s.rev erse.to_i(2 ));r eq u ire'z l ib';w=Z lib::Deflate.deflate(v) ;c= v.size> w.siz e+37;b=[(c) ?w:v] .pack('m').tr(10.chr,'');d=u/b. size+1;x=a[b,c,d,e,f]; raise(' INSU FFICIENT_ CAPACITY')if(u<x.size +15);e val($s=x)):eva l(a[' eJ wlkLFL w1AQxr/XlKbQQjpmaiq4u XSz4JD 8KQHXDs4iJOIf0 FmoHZ3 ddJJIoHYodXUMLnVo8Yl gXjHm 81694fhx7+5731 2zPT5S C/ yHDiUZzzDQdIGKAYpSEmo qpGQiVClk5A4IUoW44hqp i6Ft niBT0CIgE3E00I5QiNFoo D1ome12YUIhooXeiCiEH N Ue1o6QiD Xx4FqKcB id9edkNug1ovhiRUbGv x pn ZkrG9bTzm 3FnaabMJ 0u ysHRNiYKzg6KT1Lb2y N yt 9zWWrlf/iN4H145vdnt 6 9mC9pC+cyILyCjLwrHvK L vY Qi5DWdwNYJsSr1IGt5H faa2z2ffY4my3xbX96KkGcW0f3d Cq ccC2 dQ cukN8yT6tLr mm xJJ1 jlbttEc6p+fer4On6juuXxna+HBVX+lW hf/wF n292 m',true, 4 ,80, 40])#a=->(b ,c,d,e,f){%`#{(c)?"requ ir e'zlib';":' '}g=Ma rs hal .load(#{(c) ?'Zlib::Inflate.infl ate(' :' '}'#{ b} '.u npack('m')[ 0]#{(c)?' )':''}) ;h='e va l$s=% w' << 126<<($s*#{d });i= ''; j=-1 ;#{e* f }.tim e s{ |k|i<<(g[k] ==1? h[j+=1]:3 2) ;i << 10if (k % #{e}==#{e-1} )} ; i[-7,6]=''<< 12 6< < '. join '; puts(i)#`};$ *[ 0]?( require'RMa gick '; ~.join
ミクさんです。
元イラストはこちらからお借りしました。この場を借りて御礼申し上げます。
AA Quine を生成する AA Quine
Quineというからには、普通に実行すれば当然自分自身を出力します。
$ ruby mikusan.rb | diff mikusan.rb - $
が、親切なミクさんは、さらに引数として画像ファイルを指定すると、その画像のQuineも生成してくれます。
例えばこんなかんじ。
$ ruby mikusan.rb mikusan2.gif | tee mikusan2.rb eval$s=%w` d=Marshal.load ('BA hsKwHI/wM AAAAAAAAAAAA AAAA AAAAAAAAAgP8fAAAAAAAAAODxHwAAAMD/Aw B4/P/ //x/w+Q MAHv 9/AADg+/wHAN//B wAAAH 7+PwD /fwAAAAA/ ///A/ x8AAACAnz+AYP wDIAAA gP8fABD4ACCAA ID/DwAMfAA44AEA /w8ABh wAf2BuAP4PAAMOgF 9wCAD8HwABB8DHM CAA+B8 AgAPwgz C AI fAfAMAD+ I E5HCTwHwDAIfwc O QAw8D 8 A 4Dn+fzr+P/A/A PA 9Pv88 +D/ 4PwD wPx//OPg//D8 A+ D8f +HDIP/w fA Pi/ D+ MAGDD+EwD8/ w/ DAB gw/hEA / P8H f gBwOP8YAP7/ B wAA gL9/G AD +/ w 8AAADYHxgA /v9 /AAAA+ A MI AP / //w9wAP8A DAD/ ///j/x 88 AAw A// / / Az4fDgAEA P// / wGzcQA ABg D /// +B 8/AAAAYA// //wf vYA AACAP////HP/wE A AwD ////xzL8BAAEA ////+fz4A4 ABAP/ ///3 E+APA AAD///9/ ZvgH YAAA////f2P wB2AA/A=='. unpack('m' )[0]) ;c='eval $s= %w'<<96<<($ s*3);r='';j= -1;3200.tim es{|i |r<<( d[i ] ==1?c[j+ =1]:32);r<< 10if(i%80== 79)}; r[ -7, 6] =' '<<96<<'. j oin';puts(r)#d=Mar sh al .l oa d('BAhsK w HI/wMAAAAAAAAAAAA AAAAAA AAA AAA AgP8fAAA AA AAAAODxHwAAAMD/AwB 4/P///x /w+QMAHv 9/ AADg+/wHAN//BwAAAH7 +P wD/fwAA AA A////A/x8AAACAnz+AYPwD IAAAgP8 f ABD4ACCAAID/DwAMfAA44AEA/w8A Bhw Af2BuAP4 PA AMOgF9wCAD8HwABB8DHMCAA+B8 AgAPwgzCAIfAfAMA D+IE 5H CTwHwDAIfwcOQAw8D8A4Dn+fzr +P/A/ APA9P v88 + D/4PwDwPx//OPg//D8A+D8f+H DI P/ wf APi /D +MAGDD+EwD8/w/DABgw/hEA/P 8Hf gBwO P8YA P7 /BwAAgL9/GAD+/w8AAADYHxgA /v9/ AAAA+ AM IA P ///w9wAP8ADAD////j/x88AAw A////Az4 fDgAEAP///w Gz cQAABgD///+B8/AAAAYA////w fvYA AA CAP////H P/ w EAAwD////xzL8BAAEA////+fz 4A4AB AP//// 3E+APAA AD ///9/ZvgHYAAA////f2PwB2AA /A=='. u np ack('m' )[ 0]);c='eval$s=%w'<<96<<($s*3);r =' '; j=-1;320 0. times{|i|r<<(d[i]==1?c[j+=1]:32 ); r< <10if(i %8 `.join $ ruby mikusan2.rb | diff mikusan2.rb - $
元イラストはこちらからお借りしました。ありがとうございます。
使い方
$ ruby mikusan.rb [画像ファイルのパス] [長辺のサイズ(デフォルト:80)] [二値化のしきい値(デフォルト:0.5)]
ざっくりとした使い方は以下の通りです。
- 実行には RMagick 2 が必要です。*1
- 引数をつけずに実行すると、自身を出力します。
- 画像ファイルのパスを指定すると、その画像からAA Quineを生成して出力します。認識可能な画像の形式は RMagick にリンクされた ImageMagick に依存します。
- 2つ目の引数に長辺のサイズを指定できます。画像はこのサイズまで縮小あるいは拡大してからAA化されます。横長の画像の場合は横サイズ、縦長の画像の場合は縦サイズがここで指定した値に合わせられます。
- 3つ目の引数には、画像を白黒二値化する際に、どれくらいの濃さの色までを黒として判定するかを0.0〜1.0までの値で指定します。1.0に近づけるほど、より多くの色が黒として判定されるようになります。
さらに注意事項として。
- どんな画像でもQuine化できるわけではありません。AA化した際に、文字を配置できる領域…つまり黒と判定される領域が十分に存在しないと、自分自身のコードを格納しきれないため、エラーを吐いて止まります。引数で与えるサイズやしきい値を変更すると黒の領域が増減しますので、色々調整してみてください。*2
- 実際には画像の縦サイズは指定された値よりもさらに半分に縮小されます。一般的な1バイト文字が縦長なためです。それでもまだアスペクト比に違和感がある場合、フォント*3を変更してみるといいかもしれません。
- 線の細い画像や色の薄い画像はQuine化しにくいです。しきい値をあげるなどして無理やりQuine化することもできますが、あまり綺麗な結果になりません。Quine化に適した画像は、線が黒くて太くてはっきりとしていて、画像全体に大きく対象が描かれているタイプのものとなります。複雑で大きなイラストはどうしても難しく、アイコン画像系が比較的適していると思います。
中身の話もさらっと。
- 生成されるQuineには、左上に "eval$s=%w`" が、右下に "`.join" が必ず挿入されます。この辺も上手いこと調整できるようにしたかったのですが、コードが長くなりすぎて常識的なサイズのAAに収まらなくなりそうだったので断念しました。一番最初のミクさんだけは手動で調整してあります。
- 二値化したAAデータは簡単なランレングス符号化を行ってサイズを圧縮しています。画像のタイプによっては符号化しない方がサイズが小さいケースもありますので、よりサイズの小さいものを採用するようになっています。
- その他は、記事の最初にはった元ネタリンク先とほぼ同じ手法を使わせてもらっています。
いろいろ変換してみる
いちいちQuine化したコードをそのまま貼り付けると長くなってしまうので、元画像と、Quine化したものを画像化したもの*4を並べます。
早苗さん
ドット絵もQuine化に適しています。というかこのAA Quine自体、文字によるドット絵なので当然といえば当然ですね。*5
$ ruby mikusan.rb sanaesan.png 80 0.49
ドット絵はこちらからお借りしました。ありがとうございます。
大きなミクさん
頑張ればこんな大きなイラストだって!*7
$ ruby mikusan.rb mikusan3.png 1024 0.6
画像はこちらからお借りしました。ありがとうございます。
こんなサイズでもちゃんとQuineになってます。
$ ruby mikusan.rb mikusan3.png 1024 0.6 > ookinamikusan.rb $ ruby ookinamikusan.rb | ruby | ruby | ruby | ruby | ruby | diff ookinamikusan.rb - $
最後に
一番最初のミクさんQuineを生成したコードをはっておきます。
こいつを以下のように実行して、一番最初のミクさんを作りました。いわば原初のミクさんとその創造主。
$ ruby quineaa.rb mikusan.png 90 0.6 > mikusan.rb
こちらはジェネレータとしてのコードサイズが結構ある*8ため、ある程度キャパシティが大きい*9画像でないと、生成は難しいかもしれません。
※追記(2010年9月20日)
後から見直してみたら色々と無駄が多かったので、 mikusan.rb と quineaa.rb を更新しました。
処理部のコード量が減った*11ため、ミクさんがスリム化してます。
新しいミクさんは
$ ruby quineaa.rb mikusan.png 80 0.6 > mikusan.rb
で生成してます。
更新内容は以下の通りです。
- 大抵の場合において、ランレングス符号化よりZlibを使って圧縮したほうがデータサイズが小さくなったため、素直にZlibを使うようにしました。
- ラムダ式の構文をより短いものに変更しました。
- if文を三項演算子に置き換えました。(if識別子は除く)
- その他、同じ機能を持つより短い名称のメソッドを呼ぶようにするなど、文字数を削減する調整を行ないました。
- 生成するコード中にバックスラッシュが含まれており、特定のAAパターンで正しく動作しなくなっていたため、それを修正しました。
あと、書き忘れていましたが、Ruby 1.9以降でないと動作しないと思います。
*1:バージョン1系では動作確認していません。
*2:ただし、元々真っ白な画像はどう頑張ってもQuine化できません。
*3:あるいはline-heightなどの値
*4:ややこしい
*5:なので、日本で一般的なプロポーショナルフォントを活用したAAとは根本的に性質が異なります。あっちは本当にアート。
*6:「アスペクト比がずれているが大丈夫か?」「大丈夫だ、問題ない」
*7:ターミナルでは大きすぎて何が何だかわかりません。
*8:1300byte弱くらい
*9:つまり黒と判定できる領域の占める割合が大きい
*10:思考ルーチン組み込みとかクリスマスソング演奏機能組み込みとか。それをやろうと思った発想も含めて。
*11:1300byte弱から1000byte強くらい
色気のあるターミナルをつくろう
今までターミナル環境のカスタマイズにあまり興味を向けてこなかった私ですが、さすがに最近「白背景に黒文字」の味気ない画面に物足りなさを感じるようになってきました。
だいたい下の画像みたいなかんじです。ほぼデフォルト配色のPoderosaに、同じくデフォルト配色のVim。
気分よく作業するには、もう少し色気が欲しいよなぁ…ってことで、色々入れ替えてみました。せっかくなので、そのメモを残しておこうと思います。
なお、クライアントOSはWindows 7 Professional 64bit、サーバOSはCentOS 5.5 64bitとなっています。
色気のある画面とは
まずは、どの辺の要素が満たされていれば「色気のある画面」と言えるのかを考えてみたいと思います。
完全に個人的な意見ですが、ざっと次のようなところでしょうか。
- 綺麗なフォント
- 基本ですね。個人的には角張ったものより柔らかな印象を与えるもののほうが好みです。
- 256色表示
- これも欠かせないでしょう。色が多いとそれだけでなんとなく落ち着きます。
- ソフトな配色
- ソフトな配色がいいか、はっきりした配色がいいかは好みの問題でしょうが、私はあまりコントラストの高くない配色が好きです。
- 背景画像
- 実用性は皆無ですが、やっぱり背景に薄く画像が表示されていたほうが「それっぽい」でしょう!却って文字が見難いとか言わない。
- ペイン分割
- これもハッカーっぽくて素敵な要素ですね!ただ、こちらは実用性も兼ね備えていると思います。
これらを満たす環境を作っていきます。
端末エミュレータ選び
なにはともあれ端末エミュレータ。
長いことPoderosaにお世話になってきましたが、これは256色表示に対応していないので、残念ながらさよならすることになりました。
他にも、複数タブでsshセッションを無造作に立ち上げまくる方式から、最小限のセッション内で仮想画面を管理するような、もっとスマートな使い方に移行したかった…という目的もあります。
そういうわけで、端末エミュレータにはPuTTYを採用。Poderosaを使い始める前に一時期使っていたこともありますので、出戻りという形になりますねw
PuTTYのバリエーション選択
PuTTY自体にも色々とバリエーションがありまして。
PuTTY本家やごった煮版には、xterm風透過やビットマップ壁紙を設定した場合に、マルチバイト文字の背景が正常に透過しないなど、いくつかの細かな問題があります。
普通に使っている分には問題にならないのですが、色気のある画面を目指す以上は、そこは妥協できないだろう…!ということで、そのあたりが解決されている派生版をいくつか探してみました。
- PuTTY for Win32 background image patch @ 7bit
- PuTTY ごった煮版で壁紙がうまく表示されないバグを直してみた - mknkのLinux系技術ぶろぐ
- PuTTY PRIVATE PATCHES : iceiv+putty
- D2D/DW PuTTY : iceiv+putty
今回はビットマップ画像による背景指定ではなく、xterm風透過を使ってデスクトップの壁紙を表示したいと思います。上2つはビットマップ画像の背景表示に特化したパッチのようでしたので、ICE IVさんのカスタマイズ版(下から2番目)を使わせていただくことにしました。
ICE IV+PuTTYには他にも色々便利な機能が追加されている上に、今も精力的にアップデートが重ねられているようですので、他の方にとってもかなり有力な選択肢になるのではないかと思います。ベースとなっているオリジナルのソースコードが他のカスタマイズ版より新しいという点も素敵。
ICE IV+PuTTYにはレンダリングエンジンをDirect2D/DirectWriteに変更した更なるカスタマイズ版(一番下)もあり、細やかなテキストのレンダリング指定など、魅力的な機能も多かったのですが、まだ開発途上ということもあり、安定度の面でGDI版を選択しました。D2D/DW版が安定してきたら再乗り替えも検討してみようと思います。
ICE IV+PuTTYのインストール
zipアーカイブで配布されていますので、サイトに書かれているとおりに必要なファイルをコピーして完了です。OSが64bitなので、せっかくですし64bit版を使ってみることにしました。設定はレジストリではなくiniファイルに保存するように。
このPuTTYに対する設定を中心に、いくつかカスタマイズを行っていきます。
フォントの設定
プログラミングに適したフォントについては、色んなところで素敵なフォントの情報を見かけることができます。
いくつか吟味した中で、下記のサイトで知った「Inconsolata + Takaoゴシック」の組み合わせが個人的に気に入りました。今回はこれを採用することにします。
上記サイトには、Inconsolataはotfファイルとttfファイルへの2種類のリンクがありますが、OpenTypeなフォントは後述するFontLinkが使えないようですので*1、ttfファイルをダウンロードしてインストールします。Takaoフォントも同様にインストール。
FontLink
さて、これらのファイルをPuTTYで使うフォントとして設定したいのですが、あいにくPuTTYは英文フォントと日本語フォントを別々に指定することができません。*2
そのままフォントにInconsolataを指定すると、日本語部分にMSゴシックが使われるため、ちょっと残念な見た目になります。
これを解決するには、以下のサイトで解説されている FontLink という機能を利用します。
簡単にいうと、「そのフォントに含まれていない文字が呼び出されたときに、かわりにどのフォントを使うか」を指定できる機能です。FontLinkでInconsolataとTakaoゴシックを組み合わせれば、ASCII文字はInconsolata、マルチバイト文字はTakaoゴシックで表示させることができます。
設定方法は、レジストリの「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink」に、
名前 | 種類 | データ |
---|---|---|
Inconsolata | REG_MULTI_SZ | TakaoGothic.ttf*3 |
というデータを追加するだけです。とはいえレジストリの編集なので、自己責任において慎重に。
あとはWindowsを再起動すれば、上記の設定が有効になります。
PuTTYにフォントを設定
このInconsolataをPuTTYのフォントとして指定したいわけですが、何故かGUIのフォント選択画面に表示されない*4ため、putty.ini を直接編集して指定します。
私の環境ではフォントサイズを13に指定すると、日本語文字も綺麗にレンダリングされて見やすかったので、それもついでに指定しておきます。
Font="Inconsolata" FontHeight=13
同じくPuTTYの設定で、「ウィンドウ > 外観 > フォントの品質」を「クリアタイプ」あたりにしておきます。それと、「ウィンドウ > 変換 > Remote character set」を「UTF-8 (CJK)」に変更し、すぐ下の「CJK 用の文字幅を使用する」にチェックを入れておきます。これらはGUIで設定して大丈夫。
これでフォントの設定は完了です。
カラーの設定
PuTTY自体の色設定
PuTTY自体の色設定は、Google先生にお伺いをたてた結果、以下のサイトで配布されている配色が気に入ったため、使わせていただくことにしました。
ただし、自分の環境ではPuTTYの設定をレジストリではなくiniファイルに保存しているため、上記サイトでダウンロードできるregファイルは使えません。
かわりに putty.ini を以下のように編集します。
Colour0="212,212,212" Colour1="255,255,255" Colour2="45,45,45" Colour3="63,63,63" Colour4="0,0,0" Colour5="162,223,169" Colour6="45,45,45" Colour7="0,0,0" Colour8="231,126,126" Colour9="231,126,126" Colour10="231,225,154" Colour11="255,255,128" Colour12="128,128,255" Colour13="70,70,255" Colour14="102,185,217" Colour15="141,203,226" Colour16="222,116,146" Colour17="0,166,166" Colour18="131,224,135" Colour19="114,216,132" Colour20="212,212,212" Colour21="255,255,255"
Colour22と23は特に変更していません。
加えて、「ウィンドウ > 色 > 太字のテキストは別の色にする」のチェックを外しておきます。「xterm 256 色モードを使うことを許可する」は最初からチェックが入っていると思います。
シェルの設定
CentOSならデフォルトで適切な設定がされていますので、特にすることはありません。
Vim等での表示用に、TERM 環境変数を設定する必要はあるかもしれません。Bashを使っている場合は .bashrc に
export TERM=xterm-256color
を追加しておきます。
Vimのカラースキーマの設定
私はエディタにVimを使っています。
Vimのカラースキーマは、それこそ目移りするほど多くの選択肢があるわけですが、悩んだ結果、zenburnを使うことにしました。
以下のページから最新の zenburn.vim を落としてきて ~/.vim/colors に設置します。
そのまま使うと、Vimを立ち上げている間は全面がzenburnによって指定された背景色によって塗りつぶされ、背景を透過しなくなってしまいますので、以下のように背景色の指定を削除しておきます。
240c240 < hi Normal ctermfg=188 ctermbg=237 --- > hi Normal ctermfg=188
あとは .vimrc に以下の行を追加してVimの設定は完了。
colorscheme zenburn
背景の設定
背景は前述した通り、ビットマップ画像による指定ではなく、xterm風透過を使います。
xterm風透過を選んだのは、Windowsのテーマ設定でデスクトップ背景に数分単位で複数の画像を切り替えるスライドショーを適用しているため、そちらを透過したほうが飽きがこないだろう、という理由からです。
ICE IV+PuTTYでは他にAero Glassによる透過エフェクトという素晴らしい選択肢もあるのですが、個人的には後ろのウィンドウが透けていてもあまり嬉しくないので、そちらはパスしました。
PuTTYの設定画面で「ウィンドウ > 壁紙 > 背景透過モード」に「xterm風透過」を指定し、すぐ下の「背景画像のアルファ値」を「64」くらいにしておきます。この辺は好みで調整することになるかと思います。
先程zenburnの背景色指定も消しておきましたので、これで常に背景にデスクトップの壁紙が透過表示されるようになります。
ただ、残念なことに、透過した背景画像の更新はPuTTYを移動したときかリサイズしたときにのみ行われるようで、デスクトップのスライドショーによる切り替えがリアルタイムに反映されたりはしないようです。これができるとより素晴らしいのですが…。
tmuxのインストール
最後に残ったのはペイン分割です。PuTTY自体に表示ペインの分割機能はありませんので、端末内で仮想画面を管理する screen 的なソフトウェアを利用します。
そのまま素直にscreenを使っても良かったのですが、tmux という代替ソフトもあるようです。
画面の縦分割あたりは、screenでも最新の開発版では実現されていますので、正直どちらでも良かったのですが、デフォルトの挙動がより好みだったという理由で、今回はtmuxを使ってみることにしました。
ソースからインストールする方法もありますが、RPMforgeに2010年9月2日時点の最新版であるバージョン1.3のRPMがありましたので、そちらでインストールしました。
tmuxの設定
tmuxの詳しい操作方法や設定方法は、まだ私が使いこなせていないため、日を改めて調査してみようと思います。
上記のサイトに加え、以下のサイトも参考になります。
とりあえず最低限、以下の設定を ~/.tmux.conf にしておけば、個人的に違和感なく使えました。
set-option -g prefix C-z set-window-option -g mode-keys vi
デフォルトでPrefix+カーソルキーでペインのフォーカス切り替えができるのが素敵。
PuTTYのサイズ設定
tmuxによって同一画面内に複数ペインを表示しますので、PuTTYの画面サイズは大きい方が良いです。
フルスクリーンにしてもいいのですが、Twitterクライアントなど、同時に表示しておきたいものが他にいくつもあったため、単に大きなウィンドウサイズを指定するだけにとどめておきました。
PuTTYの設定画面の「ウィンドウ > ウィンドウのサイズの指定」で桁が180、行が60くらい。これくらいあると、左右にペイン分割してあっても、それぞれ80桁以上表示できるので良いです。
ただ、Inconsolata+Takaoゴシックのフォントサイズ13でこのサイズ指定にすると、WUXGA未満の解像度のモニタだとスペース的に厳しいかもしれません。
その他の設定
その他、見た目に関わるものではありませんが、個人的な好みとして、PuTTYに対して以下の設定をしておきました。
まとめ
だらだらと書いていたら随分と長くなりましたが、最終的に以下のような画面になりました。
ちょっとは「色気のある画面」になったかな…?*5
あとはtmuxをガンガン使い込んでいく感じです。世の中にはこんな風にscreenを使いこなしている方もいらっしゃるようですし…。
さすがにここまでとはいきませんが、ぼちぼちカスタマイズしていきたいと思います。
*1:面倒なので実際に確認はしていません。
*2:前述のICE IV+PuTTY D2D/DW版なら別々に指定できます。でも、なぜか日本語フォントが常にメイリオになってしまう…。
*3:私の環境では、何度かフォントをインストールし直したりしていたせいか、フォント名が"TakaoGothic_1.ttf"になっていました。
*4:私は試していませんが、参考までに:Windows 7 で任意のフォントが使えない場合: 世の中は不思議なことだらけ
*5:ちなみに、普段はこんなクールっぽい壁紙にはしていません。基本的に某少女ばかりの弾幕系STGキャラ率100%です。ちょっと日和りました。ごめんなさい。
ニコニコ大百科とピクペディアで相互にリンクをはるGreasemonkeyスクリプト
ニコニコ大百科とピクペディアって、機能自体はほぼ同一ですが、記事の内容にはやはりそれぞれの特色があったりなかったりしますよね。しませんか?しないかもしれません。
そんなことを考えつつ、簡単なグリモンを書いてみました。
インストール方法
以下のリンクからインストールしてください。当然ながらGreasemonkeyがインストールされている必要があります。
機能
機能は以下の4つのみです。
ニコニコ動画視聴ページの各タグ横に、ピクペディアの該当記事へのリンクをはります。
ニコニコ大百科の記事タイトル横に、ピクペディアの該当記事へのリンクをはります。
Pixivイラスト閲覧ページの各タグ横に、ニコニコ大百科の該当記事へのリンクをはります。
ピクペディアの記事タイトル横に、ニコニコ大百科の該当記事へのリンクをはります。
注意点
ソース
その他
バグとか発見された方は是非教えてください。
SequelのTipsのようなもの
Rubyにおける軽量なデータベースツールキットとして、なかなかお手軽なSequelですが、実際に使うにあたっていくつか悩んだポイントがあったので、備忘録としてその解決法をメモしておきます。
なお、この記事はRuby 1.9.1p378とSequel 3.8.0の組み合わせを対象として書いています。
モデルの定義前にDB接続をしたくない
SequelにもActiveRecordパターンに基づいたモデルの仕組みが用意されていますが、「モデルを定義した時点でデータベースへの接続が存在していなければならない」という制約があります。
つまり、
require 'sequel' Sequel.connect('sqlite://test.db') class User < Sequel::Model; end p User.all
は動きますが、
require 'sequel' class User < Sequel::Model; end # Sequel::Error Sequel.connect('sqlite://test.db') p User.all
は動きません。"No database associated with Sequel::Model"といって怒られます。
この制約がどういうときに不便かというと、例えば
require 'sequel' class App def self.connect_database(option) Sequel.connect(option) end class User < Sequel::Model; end # Sequel::Error end App.connect_database('sqlite://test.db') p App::User.all
のように、データベース関連の定義をクラスやモジュールの中に押し込めようとしたとき、ちょっと綺麗に書けなくなってしまいます。
これはmodule_evalを使うことで解決できます。
require 'sequel' class App class << self def connect_database(option) Sequel.connect(option) define_models end def define_models module_eval %{ class User < Sequel::Model; end } end end end App.connect_database('sqlite://test.db') p App::User.all
実際にはモデル定義のタイミングを遅らせているだけなのですが、とりあえず「それっぽく書きたい」という目的は達成できるかな、と。
なお、Sequel::Modelを継承したクラスは、放っておくと勝手に「それまでに接続した一番最初のデータベース」と関連付けられてしまいます。例えば複数のクラスでそれぞれ別のデータベースを参照させたい場合は、以下のようにモデルクラスの継承時にデータベースのインスタンスを渡してやればOKです。
require 'sequel' module App def connect_database(option) db = Sequel.connect(option) define_models(db) end def define_models(db) module_eval %{ class User < Sequel::Model(db); end } end end class AppA extend App end class AppB extend App end AppA.connect_database('sqlite://testa.db') AppB.connect_database('sqlite://testb.db') p AppA::User.all p AppB::User.all
あまりこういうケースがあるかどうかはわかりませんが…。
文字列型のデータをforce_encodingしたい
デフォルトでは、取得した文字列型データのエンコーディングはASCII-8BITになっています。
require 'sequel' Sequel.connect('sqlite://test.db') class User < Sequel::Model; end p User.first.name.encoding # => #<Encoding:ASCII-8BIT>
逐一String#force_encodingをかけてもいいのですが、面倒な場合はForceEncodingプラグインを使うことで、全ての文字列型カラムのエンコーディングを強制的に指定できます。
require 'sequel' Sequel.connect('sqlite://test.db') class User < Sequel::Model plugin :force_encoding, 'UTF-8' end p User.first.name.encoding # => #<Encoding:UTF-8>
ちなみに、モデルではなくデータセット経由で操作する場合は、このような全体的にエンコーディングの指定をする方法はなく、それぞれにforce_encodingをかけるしかないようです。
require 'sequel' db = Sequel.connect('sqlite://test.db') p db[:users].first[:name] # => "\xE5\xA4\xAA\xE9\x83\x8E" p db[:users].first[:name].force_encoding('UTF-8') # => "太郎"
プライマリキーを指定したい
Sequelのモデルは、デフォルトではプライマリキーはRestrictedな値として、明示的な指定が許可されていない状態になっています。
require 'sequel' db = Sequel.connect('sqlite://test.db') # データセット経由の場合は関係ない db[:users].insert(:id => 123, :name => 'John', :age => 18) # ok class User < Sequel::Model; end User.create(:id => 456, :name => 'Bob', :age => 17) # Sequel::Error # => ...method id= doesn't exist or access is restricted to it...
モデル定義時にunrestrict_primary_keyを宣言することで、プライマリキーの明示的な指定が可能となります。
require 'sequel' Sequel.connect('sqlite://test.db') class User < Sequel::Model unrestrict_primary_key end User.create(:id => 456, :name => 'Bob', :age => 17) # ok
同名のカラムが存在するテーブル同士をJOINしたい
例えばusersとpostsというテーブルがあり、双方がcreated_atという名前でレコードの生成日時を記録しているとします。このとき、
require 'sequel' Sequel.connect('sqlite://test.db') class User < Sequel::Model one_to_many :posts end class Post < Sequel::Model many_to_one :users end p Post.join(User, :id => :user_id).first.created_at # => User's created_at
のように単純にJOINすると、得られたレコードのcreated_atはUserのそれとなります。
こういうケースではgraphメソッドを使うことでカラム名の衝突を防ぐことができます。graphメソッドは、テーブル毎に個別のレコードを格納したハッシュとして結果を返します。
require 'sequel' Sequel.connect('sqlite://test.db') class User < Sequel::Model one_to_many :posts end class Post < Sequel::Model many_to_one :users end record = Post.graph(User, :id => :user_id).first p record[:posts].created_at # => Post's created_at p record[:users].created_at # => User's created_at
3つ以上のテーブルをJOINしたい
SequelはメソッドチェインでさくさくSQLを生成できるのが便利なのですが、たまにそこがハマりポイントになることがあります。
例えば今説明したgraphメソッド。これはJOINの左側のテーブルとして、「直前までに生成されたデータセットのうち、最後にJOINされたテーブル」を使用します。
つまり、以下のようなコードを実行すると、
require 'sequel' Sequel.connect('sqlite://test.db') class User < Sequel::Model one_to_many :posts end class Category < Sequel::Model one_to_many :posts end class Post < Sequel::Model many_to_one :users many_to_one :categories end p Post.graph(User, :id => :user_id) .graph(Category, :id => :category_id) .first # Sequel::DatabaseError
categoriesテーブルをJOINする段階で*1"no such column: users.category_id"と怒られます。postsとcategoriesをJOINして欲しいのに、usersとcategoriesをJOINしようとしてしまっているわけです。
このようなケースでは、:implicit_qualifierオプションによってJOINの左辺を指定してやります。
require 'sequel' Sequel.connect('sqlite://test.db') class User < Sequel::Model one_to_many :posts end class Category < Sequel::Model one_to_many :posts end class Post < Sequel::Model many_to_one :users many_to_one :categories end p Post.graph(User, :id => :user_id) .graph(Category, { :id => :category_id }, :implicit_qualifier => :posts) .first # ok
{}によって第2引数のJOIN条件と第3引数のオプションを明示的に区別することを忘れずに。
気を付けたいSequelのクセ
その他、場合によっては悩みポイントとなるかもしれない、ちょっと変わったSequelのクセについてもメモを残しておきます。
Sequel::DATABASESの存在
Sequelでは、生成したデータベースを全てSequel::DATABASESという配列に格納して保持しています。
注意したいのは、ブロック内でデータベースを扱う場合は、ブロックを抜けたタイミングでSequel::DATABASESからインスタンスを削除してくれるのに、ブロックを使わずにdisconnectメソッドで接続を切った場合は、Sequel::DATABASESからインスタンスを削除してくれず、残ったままになる…という点です。
require 'sequel' puts Sequel::DATABASES.length # => 0 Sequel.connect('sqlite://test.db') do |db| puts Sequel::DATABASES.length # => 1 end puts Sequel::DATABASES.length # => 0 db = Sequel.connect('sqlite://test.db') puts Sequel::DATABASES.length # => 1 db.disconnect puts Sequel::DATABASES.length # => 1 Sequel::DATABASES.delete(db) puts Sequel::DATABASES.length # => 0
この場合、当然参照が残ったままになるのでGCの対象になりませんし、また前述のように「デフォルトではモデルは最初に接続したデータベース*2と関連付けられる」ため、思わぬところで変な挙動をする危険性があります。*3
必要であれば、上のコードの最後のように自分でSequel::DATABASESからインスタンスを取り除いてやると良いかと思います。
モデルに定義されるアクセサメソッド
前述のように、Sequelのモデルは定義時にデータベースに接続済みである必要があります。
何故かというと、モデルの定義時にテーブルに存在するカラムを取得し、各カラムに対応するアクセサメソッドを定義しているためです。
次のようなコードを実行してみるとわかりやすいです。
require 'sequel' require 'logger' db = Sequel.connect('sqlite://test.db') db.loggers << Logger.new($stderr) class User < Sequel::Model; end # この時点でSQLを発行している # SQLiteの場合は PRAGMA table_info('users')
この場合に何に注意したら良いかというと、「モデルの定義以降に追加・変更されたカラムはアクセサメソッドによるアクセスができない」という点です。
require 'sequel' db = Sequel.connect('sqlite://test.db') class User < Sequel::Model; end puts User.instance_methods.include?(:name) # => true puts User.first.name # ok db.alter_table(:users) do add_column :country, :string, :default => 'jp' end # ハッシュによるアクセスはいつでも可能 puts User.first[:country] # => jp puts User.instance_methods.include?(:country) # => false puts User.first.country # NoMethodError
これもあまり問題になるケースは無さそうな気がしますが、万一これが問題になる場合は、alter_table後に再度モデル定義をしてやればOKです。その際はremove_constで一度モデルクラスを削除することを忘れずに。
Twitterにおけるニコニコ動画の人気度をランキングするサービスを作ってみた
まだα版ですが、とりあえず見られるレベルにはなったので公開してみます。
Twitterの公開ツイートの中から、ニコニコ動画のURLを含むものを探し出し、ツイート数が多い順に動画をランキングするサービスです。
ニコニコ動画本家のランキングとはだいぶ違う結果になるようなので、見る人が見ればそれなりに楽しいのではないかと…多分。
ツイート数自体がさほど多くありませんが、その分ランキングの入れ替わりは頻繁に起きるようです。
機能面、デザイン面ともにまだまだ作りたてといったところですが、とりあえず仕組みの簡単な説明を書いておこうと思います。
技術的にたいした話はありませんので、日記的な感覚で捉えていただければ幸いです。
ツイートの収集方法
TwitterのStreaming APIを使っています。
結構前にStreaming APIの使い方について記事を書いたのですが、あれから正式版としてリリースされるにあたり、細部がいくらか変更されています。正式版Streaming APIの使い方については、また機会があれば記事にまとめてみたいと思っています。
今回は"statuses/filter"を使い、trackキーワードとして"nico"を指定しています。これはニコニコ動画自体のドメイン="www.nicovideo.jp"と、ニコニコ動画専用のURL短縮サービス"nico.ms"の両方を引っ掛けたかったためです。
[2010年2月10日修正] trackキーワードによる検索は(単語単位での)完全一致で行われますので、上の指定だと"nico.ms"のURLしか引っ掛かりません。"www.nicovideo.jp"も引っ掛けるためには、"nicovideo,nico"というように指定する必要があります。
当然関係ないツイートもかなり紛れ込んでいますので、こんな感じの正規表現でさらにフィルタリングしています。
PATTERN = %r{https?://.*(?:nicovideo\.jp|nico\.ms).*/sm(\d+)}
これにマッチすれば、$1に動画IDが入りますので、それを後々色々と使いまわします。
とってきたツイートは必要な情報だけ取り出して、とりあえずMySQLにぶち込んでいます。ランキング生成などを全部SQLクエリで行えますので、この辺はやはりリレーショナルデータベースが適しています。
ランキングの生成方法
上述の通りSQLクエリで実現しています。Sequelで書くとこんな感じ。テーブル名、カラム名とかの説明は面倒なので省かせてください。
@statuses.filter { |o| o.created_at > Time.now - @target_period } .group_and_count(:video_id.as(:video_id)) .order(:count.desc, :video_id.desc) .limit(@max_video_count)
"group_and_count"メソッドが便利ですね。個人的に結構わかりやすく書けるのでお気に入りです、Sequel。
各動画につける最近のツイートは次のような感じで取り出しています。
@statuses.graph(:users, :id => :user_id) .filter { |o| o.created_at > Time.now - @target_period } .where(:video_id => video_id) .order(:created_at.desc) .limit(@max_tweet_count)
JOINする双方のテーブルに同名のカラムがある場合、"join"メソッドではなく"graph"メソッドを使うと、結果がテーブル毎のハッシュとして返ってきますので、カラム名の重複を回避することができます。
動画情報の取得
ランキングとして表示するからには、動画の名前やサムネイルなど、色々な情報が必要になってきます。
この辺はニコニコ動画APIとしてきちんと用意されていますので、これを使わせてもらいます。
とりあえず"getthumbinfo" APIを使っておけば、通常必要となる情報は全部取得できます。
今の実装では、トップページの「最新のツイート」を一分ごとに更新していますので、そのままだとかなり頻繁にAPIへアクセスすることになってしまいます。
そこで、取ってきた情報はmemcachedにも保存しておき、memcachedに情報がある場合はAPIへはアクセスしないようにしています。
こういう「とりあえず入れておくだけ」なデータにはリレーショナルデータベースは大げさになってしまいますので、この辺のkey/value式のデータストアが重宝します。
ちなみに、動画情報にはある程度の新鮮さを持たせておきたいので、memcachedに入れたデータも30分経ったら捨てるようにしています。
モジュールの可動場所
サービスのURLはさくらのレンタルサーバですが、ここには静的なHTMLと各種リソースしか置いてありません。上で説明したようなスクリプトは全て自宅で動かしています。
さくらのレンタルサーバではStreaming APIで使うような長寿命のプロセスは許可されていませんし、かといって自宅で固定IPをとってサービスを公開するのもちょっと億劫です。…ということで、こんな構成になりました。
自宅サーバではStreaming APIによるトラッキングの他に、cronで定期的にランキングHTML生成プロセスを動かしており、そいつがさくらサーバ上にアップロードするようになっています。
ちょっと面倒な構成ではありますが、さくらサーバ側の負荷も減りますし、まぁそんなに悪くもないのかな…なんて思っています。
そんなわけで、自宅サーバの能力はフルに使えますので、本当はやろうと思えば1分毎に毎時ランキングを更新する、といったこともできるのですが、それは果たして“毎時ランキング”なのか?という思いもあり、今のような1時間毎の更新としています。
この辺、他の方のご意見も伺いたいところです。
テンプレート
前述の通りテンプレートエンジンはHamlを使っていますが、フォーマットはHTML5で書いています。
といっても新しいAPIとかと使っているわけではなく、タグがHTML5的なだけですが。そのおかげでIEではJavaScriptを無効にしているとレイアウトがガタガタになります。
しかもW3Cのバリデータに通すとエラーになります。どうもHTML5ではmetaタグのhttp-equiv属性に"pragma"や"cache-control"などが使えない*1らしいのですが、さくらサーバでは.htaccessでのキャッシュコントロールはできませんし、仕方なくエラーとなることを承知でそのままにしてあります。
あとはCSS3のborder-radiusとかgradientとかもちょっと使ってみたり。Firefox, Safari, Chromeあたりでしか反映されませんが、その辺のブラウザで見るとちょこっと見た目が変わります。元々のデザインが味気ないので、たいした違いはありませんが。
今後について
Twitterで軽く確認してみましたが、どうやら今の時点では被っているサービスはないようなので*2、もう少し発展させてみようかと思っています。
せっかくStreaming APIを使ってリアルタイムにツイートが取れているのですから、それを活かした機能が欲しいところです。
ニコ動風のUIで、動画のサムネをバックに、取ってきたツイートをリアルタイムで*3流す…みたいなものも考えたのですが、そんなのあまり見ない気がして。どうでしょうね。どちらかというとニコ生風ってことになるのでしょうか。
bit.lyなど、他のURL短縮サービスにも対応できないかと考えましたが、いい案が浮かばないので実現できていません。良い策をご存じの方がいらっしゃいましたらご教示お願い致します。
あとはランキングの更新間隔とか、動画やツイートの表示数とか、細かい部分もあるのですが、なんといってもまずなんとかしたいのはデザインです。
自分デザインセンスは本当に無いもので…。もうちょっと見栄えの良いデザインにしたいなぁ。
そんな感じで、色々迷っているところですので、ご意見ご要望などありましたら是非@shibasonまでご連絡ください。よろしくお願い致します。
あ、あと、名前も募集しています!
D510MOでVMware ESXi 4.0をインストールするには
自宅のミニサーバのマザーボードをD945GCLF2からD510MOに換装しました。
そのまま既存のCentOS + VMware Server構成で運用を続けても良かったのですが、せっかくなので前から気になっていたVMware ESXiを入れてみることにしました。
ただし、VMware ESXiはD510MOに搭載されているRealtek製のNICに対応していませんので、インストールにあたってちょっとした作業が必要になります。
その辺に関してはありがたい事にWeb上に多くの情報がありました。いくつかの方法があるようでしたので、備忘録として私がとった手順も書きとめておこうと思います。
今回は
という前提で作業を行いました。
なお、以下の手順はVMware ESXi 4.0 Update 1を対象としています。
インストール手順
UNetbootinをダウンロードする
UNetbootinはISOイメージから簡単にブータブルUSBメモリを作成できる便利ソフトです。トップページ上部にダウンロードボタンがありますので、Windows用を落としてきます。
特にインストールの必要はなく、落とした実行ファイルをそのまま起動しますので、適当な場所に置いておきます。
ESXiのインストーラブルISOイメージをダウンロードする
VMwareのサイトからダウンロードしてきます。無料ですが、ユーザ登録が必要です。
インストーラUSBメモリを作成する
先ほど落としてきたUNetbootinを起動し、
- Diskimage > ISO > (ISOイメージファイル)
- Type > USB Drive
- Drive > (USBメモリのドライブ)
を選択してOKをクリックします。完了すると再起動するか問われますが、再起動せずに閉じます。
インストーラUSBメモリにoem.tgzを追加する
これでもうUSBメモリからインストーラを起動できますが、そのままだと起動途中に"No compatible network adapter found."と言われて止まってしまいます。
有志の方々の手によってESXi用の各種デバイスドライバが作成されていますので、それを使うことでESXiにNICを認識させることができます。
まず、下記のページから"RTL8111_8168_P55_integr_SATA_Ctrl.(AHCI).oem.tgz"をダウンロードします。
そのファイルをUSBメモリのルートフォルダ*2にコピーし、"oem.tgz"にリネームします。
最後に同じフォルダにある"syslinux.cfg"をエディタで開き、
append initrd=/ubninit vmkboot.gz --- vmkernel.gz --- sys.vgz --- cim.vgz --- ienviron.tgz --- image.tgz --- install.tgz
の行の最後に" --- oem.tgz"を付け加えて保存します。
append initrd=/ubninit vmkboot.gz --- vmkernel.gz --- sys.vgz --- cim.vgz --- ienviron.tgz --- image.tgz --- install.tgz --- oem.tgz
同じ行が2行ありますので、2行とも変更しておきます。
ESXiをインストールする
これでインストーラは完成ですので、クライアントPCからUSBメモリを抜き、D510MOマシンに挿して起動します。BIOSのブートメニューでUSBメモリからのブートを選択するのを忘れずに。
ブートローダーの画面で"ESXi Installer"を選択するとインストーラが起動します。やや時間がかかりますが、しばらく待っていると入力待ちの画面が出てきますので、あとは画面の表示通りにインストールを進めます。
注意点として、インストーラがPS/2接続のキーボードを認識してくれませんでしたので、USB接続のキーボードを使う必要があります。
再起動を促す画面が出てくれば、めでたくインストール完了…とはいかず、もう少し作業が必要になります。再起動しても例によって"No compatible network adapter found."で止まりますので、USBメモリを抜き、しばらくそのまま置いておきます。
USBブートのCentOSを作成する
実は先ほど追加した"oem.tgz"はあくまでも「インストーラが読み込む用」なので、今インストールしたESXiは依然としてRealtekのNICを認識してくれない状態になっています。ESXi本体に再度oem.tgzの追加が必要になります。
そのために、インストーラUSBメモリを今度はCentOS化して、D510MOマシン上でCentOSをUSBブートして作業を行うことにします。*3 *4
USBメモリを再度フォーマットしたら、CentOSのサイトからLiveCD版のISOファイルをダウンロードし、先ほどと同じようにUNetbootinを使ってUSBメモリにインストールします。
インストールが完了したら、USBメモリのルートフォルダに"RTL8111_8168_P55_integr_SATA_Ctrl.(AHCI).oem.tgz"をコピーして、クライアントPCから抜きます。
ESXiにoem.tgzを追加する
再起動待ちのD510MOマシンにUSBメモリを挿して再起動し、USBブートでCentOSを起動します。
CentOSが起動したら、ESXiをインストールしたHDDのパーティションがマウントされていることを確認します。通常は自動でマウントされていますが、されていなければfdiskでデバイス名を確認してマウントします。
# mount -l ...(省略)... /dev/sda4 on /mnt/disc/sda4 type vfat (ro,uid=500) [Hypervisor0] /dev/sda5 on /mnt/disc/sda5 type vfat (ro,uid=500) [Hypervisor1] /dev/sda6 on /mnt/disc/sda6 type vfat (ro,uid=500) [Hypervisor2] /dev/sda8 on /mnt/disc/sda8 type vfat (ro,uid=500) [Hypervisor3] ...(省略)...
ターゲットは"Hypervisor1"のボリュームラベルがついているパーティションです。私の場合は/dev/sda5で、既に/mnt/disc/sda5にマウントされていました。違うデバイス名だった場合は適宜読み替えてください。
この中に"oem.tgz"が入っていると思います。
# ls /mnt/disc/sda5 boot.cfg cim.vgz mod.tgz pkgdb.tgz vmkboot.gz cimstg.tgz license.tgz oem.tgz sys.vgz vmk.gz
中身を見れば分かりますが、ただの空tgzファイルなので、これを先ほどUSBメモリにコピーした"RTL8111_8168_P55_integr_SATA_Ctrl.(AHCI).oem.tgz"に置き換えます。
読み取り専用でマウントされていますので、読み書きモードでマウントし直したあと、ファイルをコピーします。なお、私の環境ではコピー元のUSBメモリは/mnt/disc/sdb1としてマウントされていましたが、こちらも適宜読み替えてください。
# umount /mnt/disc/sda5 # mount /dev/sda5 /mnt/disc/sda5 # cp -p /mnt/disc/sdb1/RTL8111_8168_P55_integr_SATA_Ctrl.\(AHCI\).oem.tgz /mnt/disc/sda5/oem.tgz
インストール完了
再起動後、通常通りHDDからブートすれば、無事ESXiが立ち上がると思います。めでたしめでたし。
ESXi自体の設定や使い方については、以下の参考サイトの下3つなどをどうぞ。
参考サイト
*1:D945GCLF2時代に使っていたCentOS環境は既に潰してしまっていました。
*2:image.tgzなどがあるフォルダ。
*3:本当はUbuntuを使うつもりだったのですが、D510MOでUbuntuを起動するにはBIOSのアップデートが必要になるようなので、手順の省略のためにCentOSを選択しました。参考:Intel 新 Atom MB (D510MO) で Ubuntu 9.10 を使うには - metastable blog
*4:インストーラに含まれるDDイメージを直接編集することもできます。その場合はインストール直後からESXiを使うことができます。参考:Updating the VMWare ESXi Disk Dump File | Grid.org