ラベル Ruby の投稿を表示しています。 すべての投稿を表示
ラベル Ruby の投稿を表示しています。 すべての投稿を表示

2010年12月12日日曜日

短縮URLを展開するRubyスクリプトを作ってみた

なにやら短縮URLを使い、不正なサイトにアクセスさせウィルスに感染させる行為が流行っているらしい(まぁ想定の範囲内だが)。そこで、短縮URLを展開できるrubyスクリプトをちょちょっと作ってみた。


#!/usr/bin/env ruby
#-*- coding: utf-8 -*-

require 'uri'
require 'socket'

uri = URI.parse(ARGV[0])
sock = TCPSocket.open(uri.host, uri.port)
sock.printf("GET %s HTTP/1.1\r\n", uri.path)
sock.printf("Host: %s\r\n\r\n", uri.host)
str = String.new
rsize = 512
while dat = sock.recv(rsize)
str << dat
break if dat.size < rsize
end
sock.close

str.each_line{|line|
puts $1 if line =~ /^Location:\s*(.*)$/
}


HTTPを直打ちしてLocationヘッダの内容を取得してるだけなので、ものすごく単純な上にウィルス感染の心配も無し。
bit.lyやamzn.to、t.co、htn.to、t.hachune.netなど、主要(?)な短縮URLサービスで使えることを確認済み。

以下からダウンロードも可能。
http://file.hachune.net/ex_surl.rb.gz


# これを作った後Google Newsを見ていたら、短縮URL展開サービスみたいのがニュースになっていた件

2010年11月25日木曜日

RubyでPIAPROからファイルを取得するライブラリを作ってみた

余裕がないとは言え、さすがに1ヶ月以上放置するのはいかがなものか、ということで、この間みくった〜♪に取り込まれた(らしい)、RubyでPIAPROからファイルを取得するライブラリを公開してみる。


中身について簡単に説明すると、

・ruby/httpsで認証用クッキー貰ってくる
・HTMLからダウンロードURL抽出
・そのURLとクッキーを利用し、open-uriでファイルをダウンロード

こんな感じ。

実際のコードは、こんな感じになる。
piapro = PIAPRO::Download.new(:user=>"miku", :passwd=>"Mi93kU3KumIKu")
piapro.download_url("http://piapro.jp/download/?view=content_image&id=mikumikumikumiku")



中身を見ると、ハードコードが多くて気持ち悪いとか、いろいろ感じるところはあるが…


ダウンロードはこちらから。
http://file.hachune.net/piapro.rb.gz

2010年9月30日木曜日

SQLite3-RubyでのSQL文のexecuteについて

よく考えずに適当にSQL文をexecuteするだけでそれなりのものは作れてしまうので、私の場合、ロクに考えずに作ってしまいがちになる。しかし、それなりに大きなもの、重要なものを作ろうとするとそういう訳にはいかないので、executeの基本的な使い方を纏めてみる。


今回サンプルとして利用するテーブルは以下の通り。

db = SQLite3::Database.new("test.db")
db.execute(<<EOS
CREATE TABLE IF NOT EXISTS Vocaloid
(
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Name CHAR UNIQUE NOT NULL,
Ver INTEGER NOT NULL
);
EOS
)



普通にexecuteする


毎度SQL文を組み立て、executeする。
SQLインジェクション対策を全て手動で行わなければならない為、非効率。

db.execute(<<EOS
INSERT INTO Vocaloid
(Name, Ver)
VALUES ('miku', 2 )

)



引数を渡し、executeする


引数に?に対応する値を順番に渡す。
quote等も自動的に処理してくれる模様。

sql = <<EOS
INSERT INTO Vocaloid
(Name, Ver )
VALUES (?, ?)
EOS
db.execute(sql, "miku", 2)



ハッシュ引数を渡し、executeする



sql = <<EOS
INSERT INTO Vocaloid
(Name, Ver )
VALUES (:Name, :Ver)
EOS
db.execute(sql, :Name => "miku", :Ver => 2)



prepareを利用する


SQL文をテンプレートとして用意しておいて、execute時に変数として渡す。

pre = db.prepare(<<EOS
INSERT INTO Vocaloid
(Name, Ver)
VALUES (?, ? )

)

pre.execute("miku", 2)
pre.execute("rin", 2)
pre.execute("meiko", 1)



prepareでハッシュ引数を使う



pre = db.prepare(<<EOS
INSERT INTO Vocaloid
(Name, Ver)
VALUES (:Name, :Ver)
EOS
)

pre.execute(:Name=>"miku", :Ver=>2)
pre.execute(:Name=>"rin", :Ver=>2)
pre.execute(:Name=>"meiko", :Ver=>1)



・参考

http://sqlite-ruby.rubyforge.org/sqlite3/faq.html


http://sqlite-ruby.rubyforge.org/classes/SQLite/Database.html

2010年9月8日水曜日

みくった〜♪のWindows版をMinGWでビルドしてみた

みくった〜♪は、全てのミク廃、そしてTwitter中毒者へ贈る、至高のTwitter Clientを目指すTwitter Client…として@toshi_a氏が開発しているソフトウェアである。

みくった〜♪

主にLinux向けとして開発されているようだが、初公開の時点で「Windows版無いの(´・ω・`)?」と言われたように、Windows版の需要がやはりそれなりに存在する。
そこで、MinGW使ってRuby/GTK2環境を整え、Windowsでもみくった〜♪を動作させることはできないものか?と考え、実際にやってみた。

※ただ使ってみたいだけ、という方はみくった〜♪ for Windowsのページかみくった〜♪ for Windows 毎日0,12時に自動ビルドされるバージョンからどうぞ。



ビルドに必要なソフトウェアやライブラリは以下の通り。
MinGW GCC 4.5.0
ftp://ftp.jaist.ac.jp/pub/sourceforge/m/project/mi/mingw/Automated%20MinGW%20Installer/mingw-get-inst/mingw-get-inst-20100831/mingw-get-inst-20100831.exe

ruby 1.8.7-p302
ftp://ftp.ruby-lang.org/pub/ruby/ruby-1.8.7-p302.tar.bz2

ruby-gtk2-0.19.4
http://ftp.jaist.ac.jp/pub/sourceforge/r/project/ru/ruby-gnome2/ruby-gnome2/ruby-gnome2-0.19.4/ruby-gtk2-0.19.4.tar.gz

ruby-hmac-0.3
http://deisui.org/~ueno/ruby/hmac.html
http://deisui.org/~ueno/ruby/ruby-hmac-0.3.tar.gz

iconv
ftp://ftp.gnu.org/gnu/libiconv/libiconv-1.13.1.tar.gz

GTK+
http://ftp.gnome.org/pub/gnome/binaries/win32/gtk+/2.20/gtk+-bundle_2.20.0-20100406_win32.zip

Win32 OpenSSL
http://www.slproweb.com/products/Win32OpenSSL.html
http://www.slproweb.com/download/Win32OpenSSL-0_9_8o.exe

Microsoft Visual C++ 2008 SP1 再頒布可能パッケージ (x86) OpenSSL用
http://www.microsoft.com/downloads/details.aspx?familyid=A5C84275-3B97-4AB7-A40D-3802B2AF5FC2&displaylang=ja

可能な限りソースからビルドするようにしたが、OpenSSLとGTK+は面倒だったので、とりあえずWindows用バイナリを利用することにした。…OpenSSLをMinGWでビルドすれば、MSVC++ランタイム要らなくなるのだが…。



MinGWによるビルド環境の構築


以前はMinGW(コンパイラ)とMSYS(サブシステム)を個別に用意し、セットアップする必要があったが、現在はmingw-get-instパッケージに纏められた模様。これをダウンロードし、普通のソフトウェアのインストーラと同じようにウィザードを進めていくだけでビルド環境が整う。
デフォルトでは、Windowsのパス変数もMSYSのパス変数として登録されてしまい、混乱の原因となるので、PATH変数を変更しておく。

$ PATH="/mingw/bin:/usr/local/bin:/bin"




iconvのビルド


一番最初にiconvをビルドする。といっても、普通に

$ ./configure --prefix=/mingw
$ make
$ make install

のような感じでおk。
本当なら/usr/localあたりにprefixを置いて、そこにインストールすべきな筈なのだが、私のCFLAGSやLDFLAGSの認識がおかしいのか、rubyのiconvライブラリのextconf.rbが/usr/localに置いたiconvを見つけられないため、諦めて/mingwにprefixを置く。



必要なライブラリの展開


GTK+のアーカイブを解凍し、そのまま全て/mingwに展開する。
OpenSSLは、インストーラを実行しインストールした先にあるディレクトリを、GTK+同様に/mingwに展開する。
また、OpenSSLを利用するために必要なMicrosoft Visual C++ 2008 SP1 再頒布可能パッケージ (x86)もインストールしておく。



Rubyのビルド


いつも通りRubyをビルドする。

$ ./configure --prefix=/ruby
$ make
$ make install

prefixを/rubyにしたが、make installする際にMSYSの/を飛び越えるため(コンパイルしたRubyがインストールに使用されるから?)、Windows側の\rubyにインストールされるので注意が必要。
そして、Windows側にインストールされたRubyを、MinGWの/usr/localあたりに移動しておく。



Ruby/GTK2のビルド


Ruby/GTK2のアーカイブを解凍し、extconf.rbを先程作成したRubyで実行する。

$ /usr/local/bin/ruby extconf.rb

そして後はmake、make installするだけの筈なのだが、MinGWでRuby/GTK2をビルドすると何故か毎度gtkのビルドでコケる。
どうやらこれからビルドされる筈のgtk2.soを、ビルド前にリンクしようとするかららしい。
そこで、gtkのMakefileを編集する。
何らかのエディタでgtk/src/Makefileを開き、LIBS = $(LIBRUBYARG_SHARED)...で始まる行の、-lgtk2を削除。

後はいつも通り。

$ make
$ make install


この時点で、Ruby/GTK2自体の開発/実行環境は整っていることになる。
MSYSでの作業はここで終了。



必要なファイルのみを纏め、最小限のRuby/GTK実行環境を作成


MSYSの/usr/localにはRuby/GTKの実行環境が整っている(ライブラリは足りないが)ので、これを適当なディレクトリにコピーし、rubyディレクトリとする。
rubyディレクトリのbinに、iconv、OpenSSL、GTK+の*.dllをコピー。
そして、RubyのLOAD_PATHのどこかにruby-hmacを展開。
後はみくった〜♪をダウンロードして展開、普通に起動できれば、みくった〜♪実行環境完成となる。



mikutter.exeを作成


毎度コマンドラインでruby mikutter.rbとしたり、バッチファイルを実行するのはどことなく落ち着かない。
また、Rubyの環境すべてがrubyディレクトリに入っているので、配布するにしてもかなりのサイズになってしまう。
そこで、更に必要なファイルだけを纏め、Windowsの実行ファイル形式で配布する形をとる。
exeファイル化には、exerbを利用する。
exerbの使用方法は、
・mkexy mikutter.rbでレシピファイルmikutter.exyを自動生成
・exerb mikutter.exyとすると、mikutter.exeが作成される
という感じである。
ここで作成されたバイナリは、Ruby環境無しで動作するものなので、あとはmikutterのディレクトリ階層とdllの配置をするだけ。

exerbの詳しい使用方法、レシピファイルの書式については、
公式ページ http://exerb.sourceforge.jp/
やmikutter for Windows付属のmikutter.exyを参照して欲しい。



みくった〜♪ 本家
http://toshia.dip.jp/
http://mikutter.d.hachune.net/

みくった〜♪ for Windowsのページ
http://append.hachune.net/mikutter.html

みくった〜♪ for Windows 毎日0,12時に自動ビルドされるバージョン
http://file.hachune.net/mikutter/

2010年6月30日水曜日

RubyのYAMLライブラリで2バイト文字をそのまま扱う

RubyのYAMLライブラリでは、標準では日本語などの2バイト文字列をバイナリとして扱うようになっている。しかし、これでは人間が直接修正するのが非常に面倒である。

技術評論社のYAML解説ページには、Rubyのソースにパッチを当てビルドする方法や、ya2yamlを使用する方法が書いてあった。しかし、パッチを当てた環境をすべてに適用するのは非常に面倒であり、ya2yamlについては標準ライブラリではない上に、少々遅いという問題がある。

そこで、どうにかする方法はないものかとYAMLのソースを眺めていたところ、以下の方法であっさり解決してしまった。



#!/usr/bin/env ruby
# -*- encoding: utf-8 -*-

require "yaml"
require "yaml/encoding"

# String.is_binary_data?で必ずfalseを返すように書き換える
class String
def is_binary_data?
return false
end
end

data = {"はつね"=>"みく", "はちゅね"=>"みく", "かがみね"=>"りん・れん", "めぐりね"=>"るか"}

# 通常どおり表示
puts "Escape:"
puts yaml = YAML.dump(data)

# アンエスケープして表示
puts "Unescape:"
puts yaml = YAML.unescape(yaml)


一度エスケープされたものをまた戻すという、面倒なことになっているが、ソース修正するよりマシかな…と。
これで、YAMLで書き出して手動で編集…なんてことがやりやすくなるかもしれない。

2010年6月3日木曜日

RubyでWebサーバを作る(ベーシック認証/ダイジェスト認証)

WEBrickでベーシック認証を行うには、以下のようにする。


#!/usr/ruby1.9/bin/ruby1.9
#-*- encoding: utf-8 -*-

require "webrick"

# HTTPServerを作成
httpd = WEBrick::HTTPServer.new(:Port => 3939)

# Basic認証の設定(引数にはパスワードファイルへのパスを渡す)
pswd = WEBrick::HTTPAuth::Htpasswd.new("dot.passwd")
pswd.set_passwd("mikubox", "miku", "mikumiku") #realm, user, passwd
auth = WEBrick::HTTPAuth::BasicAuth.new(:UserDB => pswd, :Realm => "mikubox")

# HTTPServerの/loginにBasic認証を設定
httpd.mount_proc("/login"){|req, res|
# 認証
auth.authenticate(req, res)
res.body = "<html><head><title>みくかわいいよみく</title></head><body><p>OK.</p></body></html>"
}

# INTシグナルでサーバを停止
trap("INT"){httpd.stop}
# サーバを開始
httpd.start()



ベーシック認証では、パスワードが簡単に復号可能な状態でネットワーク上を流れるため、重要なものに利用するのは非常に危険。
そこで、ダイジェスト認証を利用する。


#!/usr/ruby1.9/bin/ruby1.9
#-*- encoding: utf-8 -*-

require "webrick"

# HTTPServerを作成
httpd = WEBrick::HTTPServer.new(:Port => 3939)

# ダイジェスト認証の設定(引数にはダイジェスト認証用のデータベースのパスを渡す)
pswd = WEBrick::HTTPAuth::Htdigest.new("dot.digest")
if pswd.get_passwd("mikubox", "miku", false) == nil
pswd.set_passwd("mikubox", "miku", "mikumiku") # realm, user, passwd
pswd.flush # ファイルに書き込む
end
auth = WEBrick::HTTPAuth::DigestAuth.new(:UserDB => pswd, :Realm => "mikubox")

# HTTPServerの/loginにダイジェスト認証を設定
httpd.mount_proc("/login"){|req, res|
# 認証
auth.authenticate(req, res)
res.body = "<html><head><title>みくかわいいよみく</title></head><body><p>OK.</p></body></html>"
}

trap("INT"){httpd.stop}
httpd.start()



どちらも比較的簡単に認証を実現することができた。
ベーシック認証はHTTPAuth::HtpasswdとHTTPAuth::BasicAuth、
ダイジェスト認証はHTTPAuth::HtdigestとHTTPAuth::DigestAuthと覚えておけば良いかと思う。



※ 追記
もっと簡単にベーシック認証を行う方法があった。

#!/usr/ruby1.9/bin/ruby1.9
#-*- encoding: utf-8 -*-
require "webrick"

httpd = WEBrick::HTTPServer.new(:Port => 3939)
httpd.mount_proc('/login') {|req, res|
WEBrick::HTTPAuth.basic_auth(req, res, "mikubox") {|user, pass|
user == 'miku' && pass == 'mikumiku'
}
res.body = "<html><head><title>みくかわいいよみく</title></head><body><p>OK.</p></body>
</html>"
}
trap("INT"){httpd.stop}
httpd.start

2010年5月30日日曜日

RubyでWebサーバを作る

最近全然書いてないな~ということで、RubyでWebサーバを簡単に作れるライブラリ、WEBrickについて書いてみる。


Ruby 1.8以上なら、


require "webrick"
WEBrick::HTTPServer.new(:Port => 3939, :DocumentRoot => ENV["HOME"]).start


たったこれだけで、TCPポート3939番に自分のHOMEディレクトリを公開してしまう、とんでもないWebサーバが出来上がる。Ruby on Railsで有名になったライブラリだが、Apacheを使うまでもない、実験用Webサーバが欲しい時などにもとっても便利。



・ CGIを利用する
拡張子を.cgiとしたファイルに実行属性を加えるだけ。

http.mount(Webサーバ上でのパス, WEBrick::HTTPServlet::CGIHandler, CGIとして実行するファイルのパス)

としてマウントすることもできる。


・VirtualHostを利用する
メインのHTTPServerに、別のServerNameを設定したオブジェクトをvirtual_hostとして渡す。
メイン以外のホストは、:Portをnil、:DoNotListenをtrueとする。

http = WEBrick::HTTPServer.new(:ServerName => "miku.hachune.net", :DocumentRoot => "miku/")
http.virtual_host(WEBrick::HTTPServer.new(:ServerName => "append.haachune.net", :DocumentRoot => "append/", :Port => nil, :DoNotListen => true))
http.start



・各ユーザのホームディレクトリの特定のディレクトリを公開する
/home/$USER/public_htmlを公開する場合、DocumentRootOptionsのUserDirを設定する。

:DocumentRootOptions => {
:UserDir => "public_html"
}



・MimeTypeを追加する
デフォルトで設定されているMimeTypesにマージする。

newmime = {"wav"=>"Audio/wav"}
mime = WEBrick::HTTPUtils::DefaultMimeTypes.merge(newmime)
WEBrick::HTTPServer.new(:DocumentRoot => ENV[HOME], :MimeTypes => mime).start



・indexが存在しない場合の、ファイル一覧を表示しないようにする

:DocumentRootOptions => { :FancyIndexing => false }



・最大クライアント数を制限する

:MaxClients => クライアント数



・アクセスできるファイル/ディレクトリを制限する
DocumentRootOptionsのNondisclosureNameに、公開したくないファイル/ディレクトリ名を配列で指定する。
ワイルドカードが使用可能。

:DocumentRootOptions => {
:NondisclosureName => [".ht*", "*~", ".svn"]
}



・他のURLに転送する

http = WEBrick::HTTPServer.new(:Port => 3939)
http.mount_proc("/"){|req, res|
res.set_redirect(WEBrick::HTTPStatus::MovedPermanently, 'http://hachune.net/')
}



・Cookieを渡す

http = WEBrick::HTTPServer.new(:Port => 3939)
http.mount_proc("/"){|req, res|
res["Set-Cookie"] = "hatsune=miku;Max-Age=3600;"
res.body = "<p>Cookieです。</p>"
}



・erb(組み込みRuby)を使う
WEBrickでは、HTMLファイルにRubyスクリプトを埋め込み、実行することも簡単にできる。

まず、MimeTypeにrhtmlをtext/htmlとして追加する。

:DocumentRootOptions =>{
:MimeTypes => WEBrick::HTTPUtils::DefaultMimeTypes.merge({"rhtml"=>"text/html"})
}

後は、拡張子をrhtmlとしたファイルに、
<%= Time.now.to_s %>
のように、必要な場所に埋め込む。


・指定された拡張子(cgi、rhtml)以外のファイル(rb、html)でCGIやerbを有功にする
require "webrick"の下あたりに以下の内容を記入しておく。
拡張子に合わせて、MimeTypeの設定も必要かもしれない。

module WEBrick::HTTPServlet
WEBrick::HTTPServlet::FileHandler.add_handler('rb', WEBrick::HTTPServlet::CGIHandler)
WEBrick::HTTPServlet::FileHandler.add_handler('html', WEBrick::HTTPServlet::ERBHandler)
end



他にもいろいろあるけれど、とりあえず一番必要とされそうな所だけ。
そのうちlogに関して等も追記する…と思う。

2010年3月22日月曜日

RubyでUDPなServer/Clientを作ってみた

なにやらUDPを使ったアプリケーションを作成することになりそうなので、各クライアントから来た要求を全クライアントに返すサーバと、送信/受信を行うクライアントを作成してみた。

UDPなので、相手に届いているかを保証することはできない。
よって、このサーバ/クライアントでは「伝えること」より「リアルタイムで発信すること」を重視する。


まずはサーバ側を作成。

#!/usr/bin/env ruby
#-*- encoding: utf-8 -*-

require "socket"

SERVERIP = "192.168.0.105"
SERVERPORT = "62000"

clientList = Array.new

sock = UDPSocket.new
# サーバとして待ち受けるアドレスとポートを指定 
sock.bind(SERVERIP, SERVERPORT)

while true
# sockに出力が来るのを待つ
# 来なかったらnilを返す
sel = IO::select([sock])
if sel != nil
sel[0].each {|s|
# 最大65535byte分のデータを受け取る。
# データは配列として渡される。
# ["文字列", ["アドレス種別", 送信元ポート番号, "送信元ホスト名", "送信元IPアドレス"]]
# ["192.168.0.105 :43433 miku > miku\n", ["AF_INET", 62000, "192.168.0.105", "192.168.0.105"]]
data = s.recvfrom_nonblock(65535)
string = data[0].chomp!
ip, port = data[1][3].to_s, data[1][1].to_i
clientList << {"ip"=>ip, "port"=> port}
clientList.uniq!
# 各クライアントに向けて返信する
clientList.each{|client|
printf("Server > %-15s:%5d %s\n", client["ip"], client["port"], string)
# 実際に返信する。send("文字列", フラグ, "送信先IPアドレス", "送信先ポート")
sock.send(sprintf("%-15s:%5d %s\n", client["ip"], client["port"], string), 0, client["ip"], client["port"])
}
}
end
end

sock.close


次にクライアント側を作成する。

#!/usr/bin/env ruby
#-*- encoding: utf-8 -*-

require "socket"

if ARGV[0] == nil
puts "ex) ./client.rb miku"
exit 1
end

NAME = ARGV[0]
SERVERIP = "192.168.0.105"
SERVERPORT = "62000"

# UDPのソケットを作成
sock = UDPSocket.new
# バインドするIPアドレスとポートを自動で割り当てる
sock.bind("0.0.0.0", 0)


# サーバからの情報を受け取り表示するスレッドを作成
thread = Thread.new {
while true
# sockに出力が来るのを5秒待つ。
# 来なかったらnilを返す。
sel = IO::select([sock], nil, nil, 5)
if sel != nil
sel[0].each{|s|
data = s.recvfrom_nonblock(65535)
puts data[0].chomp!
}
end
end
}

# サーバに情報を送信するループ
while true
string = STDIN.gets.chomp!
sock.send(sprintf("%-5s> %s\n", NAME, string), 0, SERVERIP, SERVERPORT)
end

sock.close



実際に使用すると、このようになった。
UDPサーバ/クライアント実行結果


これだけで、簡単なチャットサーバ/クライアントが作成できた。
Cで書いたものを見たあとだと、Rubyが簡単過ぎて泣ける。

2010年2月28日日曜日

Ruby1.9.1のcursesでUTF-8文字列を表示

RubyのCursesでプレイリスト操作ツールを作成しようとしたところ、文字化けして全然使い物にならないということが判明。ついでなので1.9.1にパッチを当ててビルドしてみることにする。

・ext/curses/curses.cを編集

追加:
+ #include "locale.h"

Init_curses関数内にsetlocaleを追加:
Init_curses()
{
+ setlocale(LC_ALL, "");


・ext/curses/extconf.rbを編集(ncurseswを探すようにする)

削除:
-if have_header(*curses=%w"ncurses.h") and have_library("ncurses", "initscr")
追加:
+if have_header(*curses=%w"ncurses.h") and
+ ( have_library("ncursesw", "initscr") or have_library("ncurses", "initscr") )
+ make=true
削除:
-elsif have_header(*curses=%w"ncurses/curses.h") and have_library("ncurses", "initscr")
追加:
+elsif have_header(*curses=%w"ncurses/curses.h") and
+ ( have_library("ncursesw", "initscr") or have_library("ncurses", "initscr") )
+ make=true


・ruby-1.9.1-p378.tar.bz2をダウンロード
・ libncursesw-devをaptからインストール
% sudo aptitude install libncursesw-dev

・ビルド。-O2だと何故か途中でコケるらしいので、-O3にする。
% optflags="-O3 -pipe" ./configure --prefix=/usr/ruby19 --program-suffix=19

2009年10月15日木曜日

RubyのFile.extnameについて

Rubyでファイルの種類を拡張子で判別しようと、File.extnameを使用してみたのだが、上手く拡張子を判別することができないことがあった。


irb(main):006:0> File.extname("01 miku.mp3")
=> ".mp3"
irb(main):007:0> File.extname("01 miku .mp3")
=> ""


拡張子の直前にスペースが含まれているとダメらしい。
自分で正規表現使って判別するしかないのか…
/.+(\..+)$/ …で良いのかな…?

半角スペースが入ってるなんてぱっと見分からず、最初物凄く悩んでしまった…(´・ω・`)



ちなみに実行環境はruby 1.8.7 (2008-08-11 patchlevel 72) [i386-mswin32]でした。

2009年9月21日月曜日

Windows Media Player Playlistからリストを抽出

wplとして保存されたプレイリストを、編集したいような気がしたので作成(Ruby)。
かなり手抜きなので、タグ毎に正しく改行されていないとうまく動かない…かもしれない。


class WPLReader
def initialize(wplfile)
@file = wplfile
@head_meta = Hash.new
@head_data = Hash.new
@file_list = Array.new
end

def read
open(@file, "r") do |read|
while line = read.gets
read_head(read) if line =~ /<head>/i
read_body(read) if line =~ /<body>/i
end
end
end

def read_head(file)
while line = file.gets
@head_meta[$1] = $2 if line =~ /<meta name="(.*)" content="(.*)">/i
@head_data[$1] = $2 if line =~ /<(.*?)>(.*?)<\/\1>/i
break if line =~ /<\/head>/i
end
end

def read_body(file)
while line = file.gets
@file_list << $1 if line =~ /<media src="(.+)">/
break if line =~ /<\/body>/i
end
end

attr_reader :head_meta, :head_data, :file_list
end


このクラスを利用し、wplをm3uに変換するツールを作成。
文字コードの修正は行わないので、用途に合わせて修正してやる必要があるかも。

https://sites.google.com/a/g.hachune.net/mtmef/home/download/wpl-m3uConverter.zip?attredirects=0


…べっ、別にBlogのネタが切れたわけじゃないんだからね(´・ω・`)

2009年9月17日木曜日

はつねさんがシステムに常駐して時間をお知らせしてくれるアプリ「みくろっく」を作ってみた

夏休みも今日で終わり…ということで、最後になんか作ってみようと考えたのは良いものの、大して良いアイディアが思い浮かばなかったので、以前ダイナミックDNSの自動更新ツールをWindowsサービス化したときに、実験として作っていたみくろっく(Windowsサービス版)を改良してみることにした。

機能は、
・Windowsサービスとしてシステムに常駐
・以下のタイミングで音声でお知らせ
 -システム起動時(朝、昼、夜)
 -システム終了時(朝、昼、夜)
 -毎時00分(1~12)
 -毎時15、30,45分
 -5分に1回位、ランダム
・音声にはmp3ファイルを使用可能
・複数の音声ファイルから毎回ランダムで再生することも可能



肝心のはつねさんの音声ファイルが地味に手抜きな気が…
…誰かめぐりねさんとかで作って…(´д`*)アハァ


ダウンロードは以下からどうぞ。


こちらからもダウンロード可能。
https://sites.google.com/a/g.hachune.net/mtmef/home/download/Mikulock_WS_v1.01.zip?attredirects=0


たまには大して役に立たないアプリ作ったっていいよね…(´・ω・`)


※09.09.19 追記
「サービスは予期せぬ原因により終了しました」となり落ちることがある模様なので、
修正版を連休中に作りなおします…

※09.09.20 追記
修正版を公開しました。
多分これで大丈夫…なハズ。

2009年8月22日土曜日

Multi thread Media Encoder Frontend v1.2 完成

コマンドプロンプトだと使いにくいって言われるから、そろそろGUI付けようか…(´・ω・`)
と考え続けて半年。
VisualuRubyが無茶苦茶使いやすいことに気付き、とりあえずVisualuRubyで作ることに決めて4日。

ようやくGUI付きのMulti thread Media Encoder Frontendが完成したよ…(´;ω;`)ウッ…
Malti thread Media Encoder Frontend GUI
これで本当に「フロントエンド」を名乗れる…気がしなくもない。
…地味だけど、これでいいよね…('A`)

使用方法、プログラムのダウンロードは、http://mtmef.g.hachune.net/からどうぞ。

2009年8月20日木曜日

MinGWを使ったRuby/GTK2のコンパイルが上手くいかない件

Windowsでも最新のRuby/GTK2を使用できるようにしようと、MinGWを使用したコンパイルに何度も挑戦しているが、どうしても上手くいかない。

gtk2.soを作るところで、ld.exe: cannot find -lruby-gtk2となり、コケてしまう。

環境は、
Windows 7 RC x64
MSYS 1.0.11(32bit)
MinGW 5.1.4(32bit)
Rubyソースruby-1.8.7-p174.tar.gzをmake、D:/RubyGTKにインストール。
GTKバイナリgtk+-bundle_2.16.4-20090708_win32.zipをMSYSの/に展開。
Ruby/GTK2ソースruby-gtk2-0.19.1.tar.gzをmake。


hatsune@HATSUNE-SERVER ~/RubyGTK/ruby-gtk2-0.19.1
$ make
make[1]: Entering directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/glib'
make[2]: Entering directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/glib/src'
make[2]: Nothing to be done for `all'.
make[2]: Leaving directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/glib/src'
make[1]: Leaving directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/glib'
make[1]: Entering directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/gdkpixbuf'
make[1]: Nothing to be done for `all'.
make[1]: Leaving directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/gdkpixbuf'
make[1]: Entering directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/pango'
make[2]: Entering directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/pango/src'
make[2]: Nothing to be done for `all'.
make[2]: Leaving directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/pango/src'
make[1]: Leaving directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/pango'
make[1]: Entering directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/atk'
make[2]: Entering directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/atk/src'
make[2]: Nothing to be done for `all'.
make[2]: Leaving directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/atk/src'
make[1]: Leaving directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/atk'
make[1]: Entering directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/gtk'
make[2]: Entering directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/gtk/src'
gcc -shared -s -o gtk2.so init.o rbgdk.o rbgdkatom.o rbgdkcairo.o rbgdkcolor.o rbgdkcolormap.o rbgdkconst.o rbgdkcursor.o rbgdkdevice.o rbgdkdisplay.o rbgdkdisplaymanager.o rbgdkdragcontext.o rbgdkdraw.o rbgdkevent.o rbgdkgc.o rbgdkgeometry.o rbgdkimage.o rbgdkinput.o rbgdkkeymap.o rbgdkkeyval.o rbgdkpango.o rbgdkpangorenderer.o rbgdkpixbuf.o rbgdkpixmap.o rbgdkproperty.o rbgdkrectangle.o rbgdkregion.o rbgdkrgb.o rbgdkscreen.o rbgdkselection.o rbgdkthreads.o rbgdktimecoord.o rbgdkvisual.o rbgdkwindow.o rbgdkwindowattr.o rbgdkx11.o rbgtk.o rbgtkaboutdialog.o rbgtkaccelerator.o rbgtkaccelgroup.o rbgtkaccelgroupentry.o rbgtkaccelkey.o rbgtkaccellabel.o rbgtkaccelmap.o rbgtkaccessible.o rbgtkaction.o rbgtkactiongroup.o rbgtkadjustment.o rbgtkalignment.o rbgtkallocation.o rbgtkarrow.o rbgtkaspectframe.o rbgtkassistant.o rbgtkbbox.o rbgtkbin.o rbgtkbindingset.o rbgtkborder.o rbgtkbox.o rbgtkbuildable.o rbgtkbuilder.o rbgtkbutton.o rbgtkcalendar.o rbgtkcelleditable.o rbgtkcelllayout.o rbgtkcellrenderer.o rbgtkcellrendereraccel.o rbgtkcellrenderercombo.o rbgtkcellrendererpixbuf.o rbgtkcellrendererprogress.o rbgtkcellrendererspin.o rbgtkcellrenderertext.o rbgtkcellrenderertoggle.o rbgtkcellview.o rbgtkcheckbutton.o rbgtkcheckmenuitem.o rbgtkclipboard.o rbgtkcolorbutton.o rbgtkcolorsel.o rbgtkcolorselectiondialog.o rbgtkcombo.o rbgtkcombobox.o rbgtkcomboboxentry.o rbgtkconst.o rbgtkcontainer.o rbgtkcurve.o rbgtkdialog.o rbgtkdrag.o rbgtkdrawingarea.o rbgtkeditable.o rbgtkentry.o rbgtkentrycompletion.o rbgtkeventbox.o rbgtkexpander.o rbgtkfilechooser.o rbgtkfilechooserbutton.o rbgtkfilechooserdialog.o rbgtkfilechooserwidget.o rbgtkfilefilter.o rbgtkfilesel.o rbgtkfixed.o rbgtkfontbutton.o rbgtkfontselection.o rbgtkfontselectiondialog.o rbgtkframe.o rbgtkgamma.o rbgtkhandlebox.o rbgtkhbbox.o rbgtkhbox.o rbgtkhpaned.o rbgtkhruler.o rbgtkhscale.o rbgtkhscrollbar.o rbgtkhseparator.o rbgtkiconfactory.orbgtkiconinfo.o rbgtkiconset.o rbgtkiconsize.o rbgtkiconsource.o rbgtkicontheme.o rbgtkiconview.o rbgtkimage.o rbgtkimagemenuitem.o rbgtkimcontext.o rbgtkimcontextsimple.o rbgtkimmulticontext.o rbgtkinits.o rbgtkinputdialog.o rbgtkinvisible.o rbgtkitem.o rbgtkitemfactory.o rbgtklabel.o rbgtklayout.o rbgtklinkbutton.o rbgtkliststore.o rbgtkmain.o rbgtkmenu.orbgtkmenubar.o rbgtkmenuitem.o rbgtkmenushell.o rbgtkmenutoolbutton.o rbgtkmessagedialog.o rbgtkmisc.o rbgtknotebook.o rbgtkobject.o rbgtkoptionmenu.o rbgtkpagesetup.o rbgtkpagesetupunixdialog.o rbgtkpaned.o rbgtkpapersize.o rbgtkplug.o rbgtkprintcontext.o rbgtkprinter.o rbgtkprintjob.o rbgtkprintoperation.o rbgtkprintoperationpreview.o rbgtkprintsettings.orbgtkprintunixdialog.o rbgtkprogress.o rbgtkprogressbar.o rbgtkradioaction.o rbgtkradiobutton.o rbgtkradiomenuitem.o rbgtkradiotoolbutton.o rbgtkrange.o rbgtkrc.o rbgtkrcstyle.o rbgtkrecentaction.o rbgtkrecentchooser.o rbgtkrecentchooserdialog.o rbgtkrecentchoosermenu.o rbgtkrecentchooserwidget.o rbgtkrecentdata.o rbgtkrecentfilter.o rbgtkrecentfilterinfo.orbgtkrecentinfo.o rbgtkrecentmanager.o rbgtkruler.o rbgtkscale.o rbgtkscalebutton.o rbgtkscrollbar.o rbgtkscrolledwindow.o rbgtkselection.o rbgtkselectiondata.o rbgtkseparator.o rbgtkseparatormenuitem.o rbgtkseparatortoolitem.o rbgtksettings.o rbgtksizegroup.o rbgtksocket.o rbgtkspinbutton.o rbgtkstatusbar.o rbgtkstatusicon.o rbgtkstock.o rbgtkstyle.o rbgtktable.o rbgtktargetlist.o rbgtktearoffmenuitem.o rbgtktextappearance.o rbgtktextattributes.o rbgtktextbuffer.o rbgtktextchild.o rbgtktextiter.o rbgtktextmark.o rbgtktexttag.o rbgtktexttagtable.o rbgtktextview.o rbgtktoggleaction.o rbgtktogglebutton.o rbgtktoggletoolbutton.o rbgtktoolbar.o rbgtktoolbutton.o rbgtktoolitem.o rbgtktooltip.o rbgtktooltips.o rbgtktreedragdest.o rbgtktreedragsource.o rbgtktreeiter.o rbgtktreemodel.o rbgtktreemodelfilter.o rbgtktreemodelsort.o rbgtktreepath.o rbgtktreerowreference.o rbgtktreeselection.o rbgtktreesortable.o rbgtktreestore.o rbgtktreeview.o rbgtktreeviewcolumn.o rbgtkuimanager.o rbgtkvbbox.o rbgtkvbox.o rbgtkviewport.o rbgtkvolumebutton.o rbgtkvpaned.o rbgtkvruler.o rbgtkvscale.o rbgtkvscrollbar.o rbgtkvseparator.o rbgtkwidget.o rbgtkwindow.o rbgtkwindowgroup.o -L. -Ld:/RubyGTK/lib -L. -LD:/MSYS32/lib -LD:/MSYS32/lib -LD:/MSYS32/lib -LD:/MSYS32/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/glib/src -LD:/MSYS32/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/glib/src -LD:/MSYS32/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/pango/src -LD:/MSYS32/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/pango/src -LD:/MSYS32/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/gtk/src -LD:/MSYS32/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/gtk/src -Wl,--enable-auto-image-base,--enable-auto-import,--export-all,--out-implib=libruby-gtk2.a -lmsvcrt-ruby18 -lgthread-2.0 -lglib-2.0 -lintl -lgtk-win32-2.0 -lgdk-win32-2.0 -latk-1.0 -lgio-2.0 -lgdk_pixbuf-2.0 -lpangowin32-1.0 -lgdi32 -lpangocairo-1.0 -lpango-1.0 -lcairo -lgobject-2.0 -lgmodule-2.0 -lglib-2.0 -lintl -lcairo -lruby-glib2 -lruby-glib2 -lruby-pango -lruby-pango -lruby-gtk2 -lruby-gtk2 -lshell32 -lws2_32
D:\MinGW32\bin\..\lib\gcc\mingw32\3.4.5\..\..\..\..\mingw32\bin\ld.exe: cannot find -lruby-gtk2
collect2: ld returned 1 exit status
make[2]: *** [gtk2.so] Error 1
make[2]: Leaving directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/gtk/src'
make[1]: *** [all] Error 2
make[1]: Leaving directory `/home/hatsune/RubyGTK/ruby-gtk2-0.19.1/gtk'

-----
SUCCEEDED: glib gdkpixbuf pango atk
FAILED: gtk
-----
Done.



…gtk以外はコンパイル出来るのだが…
…諦めようか…(ヽ´ω`)

2009年8月16日日曜日

マルチスレッド対応 マルチフロントエンド(もどき) 改め Multi thread Media Encoder Frontendのまとめサイトがようやく完成

マルチスレッド対応 マルチフロントエンド(もどき)では、どういうソフトなのか分かりづらいという反省点を踏まえ改名。ついでに、物凄く分かりづらかった使い方の説明や、最新版の配布を行うためのまとめサイトを作ってみた。

まとめサイトは以下のリンクから。
http://mtmef.g.hachune.net/


更についでに、ソフト本体の更新も行った。
今回(v1.1)の変更点は、以下の通り。
・lame(mp3)に加え、ogg Vorbis(ogg)、Free Lossless Audio Encoder(flac)、The True Audio(tta)、neroAacEnc(aac、m4a)、Windows Media Audio(wma)によるエンコードに対応。
・ディレクトリ選択時に、ディレクトリパス名の補完をTabキーで行えるようになった。
・-vオプションを付けて起動するか、スレッド数を1にすることで、エンコード/デコード処理の詳細を表示するようにした。
・その他細かな修正


プログラムを直接ダウンロードしたい場合は以下のリンクからどうぞ。
https://sites.google.com/a/g.hachune.net/mtmef/home/download

…(´ぅω・`)ネムタス

2009年8月7日金曜日

ダイナミックDNSの自動更新ツールをWindowsサービス化してみた

Windowsサービスアプリケーションの作り方が分かったので、DynamicDNSのアップデートツールを、作り直してみた。

以前作っていたものは、MyDNS専用かつアカウント一つしか使用できなかったのだが、今回は、
・通常のHTTPか、ベーシック認証を利用できるDDNSであればなんでも利用可能
・複数同時登録可能
・Windowsサービスとして動作
というものを作ってみた。

Windowsサービスとして動作するので、Windowsが起動してさえいれば、ログインしていなくても自動的に更新が行われるので、たぶん便利。


プログラム本体は、以下からダウンロードできます。



Windows 7 RC x64でしか動作確認していないので、それ以外(特にXP)で正しく動作するかちょっと不安…(´・ω・`)
…もしコケたら…ごめんね…(´;ω;`)ブワッ

2009年8月3日月曜日

RubyでWindowsのサービスを作る

以前DDNSUpdaterを作った時に、「Windowsサービス化はむりぽ」と書いたが、
ようやくWindowsサービスをRubyで作れるレベルになったので、纏めてみる。

まず、WindowsサービスをRubyで作成する上で必要なライブラリWin32-Serviceを入手する。
RubyGemsを使用して入手するのが一番楽だと思われる。

Win32アプリケーションを作成するにあたって、Win32Utilsはかなり重要になってくる(と思われる)ので、他にも必要としているものが無いか探しておくと、後で悩むことが無くなって良いと思う。

RubyGemsとWin32-Serviceのインストール
http://projectzero-swb.blogspot.com/2009/08/win32-servicerubygems.html


RubyでWindowsサービスを作成する上で必要なことは、
・サービス登録を行うスクリプトの作成
・サービスデーモンの作成
・サービスデーモンに行わせる処理の作成

の3つ。

この中で、・サービス登録を行うスクリプトの作成は、Windows標準付属のsc.exeで代用することも可能だが、そこも自動化できないといろいろ面倒なので、普通にスクリプトを作成する。
作ると言っても、殆どライブラリ付属のサンプルスクリプトのままでおkなのだが…


以下がサービスを管理するクラス。
定数SERVICE_NAME、SERVICE_DISPLAYNAME、SERVICE_DESCRIPTION、SERVICE_COMMANDを編集することによって、どんなサービスにも応用できる…はず。

サービスを登録…service_install
サービスを開始…service_start
サービスを停止…service_stop
サービスを再開…service_resume
サービスを削除…service_uninstall
サービスの状態を返す…service_stat


#! ruby.exe

require 'rubygems'
require 'win32/service'
include Win32

# サービスの作成に必要な情報を定義(ServiceSetupクラスで使用)
SERVICE_NAME        = "Rubyservice"                  # サービスの正式な名前。これで全て管理される。
SERVICE_DISPLAYNAME = "RubyService"                  # サービスの表示名。サービス一覧などに表示される名前
SERVICE_DESCRIPTION = "Rubyで作成したサービスです。" #
# Rubyスクリプトそのままだと、インタプリタのパス指定間違いによる193 0xc1エラーが発生するなど、
# いろいろ面倒なのでexerbでWindows用exeファイル化しておく
SERVICE_COMMAND     = "C:\\Service\\Rubyservice.exe"


class ServiceSetup
  def initialize
    raise TypeError if SERVICE_NAME        == nil || SERVICE_NAME.class        != String
    raise TypeError if SERVICE_DISPLAYNAME == nil || SERVICE_DISPLAYNAME.class != String
    raise TypeError if SERVICE_DESCRIPTION == nil || SERVICE_DESCRIPTION.class != String
    raise TypeError if SERVICE_COMMAND     == nil || SERVICE_COMMAND.class     != String
  end

  # サービスインストール
  def service_install
    Service.new(
                :service_name     => SERVICE_NAME,
                :display_name     => SERVICE_DISPLAYNAME,
                :description      => SERVICE_DESCRIPTION,
                :binary_path_name => SERVICE_COMMAND
                )
    sleep 1
    puts 'サービス ' + SERVICE_NAME + ' をインストールしました。' 
  end

  # サービス開始
  def service_start
    if Service.status(SERVICE_NAME).current_state != 'running'
      Service.start(SERVICE_NAME)
      while Service.status(SERVICE_NAME).current_state != 'running'
        puts 'One moment...' + Service.status(SERVICE_NAME).current_state
        sleep 1
      end
      puts 'サービス ' + SERVICE_NAME + ' を開始しました。'
    else
      puts SERVICE_NAME + " は既に実行されています。"
    end
  end

  # サービス停止
  def service_stop
    if Service.status(SERVICE_NAME).current_state != 'paused'
      Service.pause(SERVICE_NAME)
      while Service.status(SERVICE_NAME).current_state != 'paused'
        puts 'One moment...' + Service.status(SERVICE_NAME).current_state
        sleep 1
      end
      puts 'サービス ' + SERVICE_NAME + ' を停止しました。'
    else
      puts SERVICE_NAME + ' は既に停止しています。'
    end
  end

  # サービス再開
  def service_resume
    if Service.status(SERVICE_NAME).current_state != 'running'
      Service.resume(SERVICE_NAME)
      while Service.status(SERVICE_NAME).current_state != 'running'
        puts 'One moment...' + Service.status(SERVICE_NAME).current_state
        sleep 1
      end
      puts 'サービス ' + SERVICE_NAME + ' を再開しました。'
    else
      puts SERVICE_NAME + " は既に実行されています。"
    end
  end

  # サービスアンインストール
  def service_uninstall
    if Service.status(SERVICE_NAME).current_state != 'stopped'
      Service.stop(SERVICE_NAME)
    end
    while Service.status(SERVICE_NAME).current_state != 'stopped'
      puts 'One moment...' + Service.status(SERVICE_NAME).current_state
      sleep 1
    end
    Service.delete(SERVICE_NAME)
    puts 'サービス ' + SERVICE_NAME + ' を削除しました。'
  end

  # サービスの状態を返す
  def service_stat
    begin
      stat = Service.status(SERVICE_NAME).current_state
    rescue
      return false
    end
    return stat
  end
end




次に、サービスデーモンを作成する。
ここでは例として、DDNSUpdaterのおまけ(?)として作っていた「みくろっく」をWindowsサービスデーモン化してみた。

Win32::Daemonクラスには、
サービスの開始時に実行…service_init
サービスのメイン処理…service_main
サービス停止時の処理…service_stop
サービス一時停止時の処理…service_pause
サービスらい開示の処理…service_resume
を定義することができる。
それぞれのメソッドは、対応するシグナルが送られてきた時に実行される。

通常は、
service_init→service_main
の順で実行される。


#! ruby.exe

begin
  require 'rubygems'
  require 'win32/daemon'
  # require 'win32/sound' # サービスにすると音が出なかったので
  include Win32
  SYSTEM_BINDIR = ENV["ProgramFiles"] + "\\Mikulock"
  SYSTEM_WAVDIR = SYSTEM_BINDIR + "\\Sound"
  SYSTEM_LOGDIR = ENV["ProgramData"]  + "\\Mikulock"
  SYSTEM_BINARY = SYSTEM_BINDIR + "\\mikulock_service.exe"
  SYSTEM_LOG    = SYSTEM_LOGDIR + "\\mikulock.log"

  #Win32::Daemonクラスの継承
  class RubyDaemon < Daemon
    # サービスの初期化
    def service_init
      File.open(SYSTEM_LOG, "a") {|writelog|
        writelog.printf("%-36s - Service Start.\n", Time.now.to_s)
      }
      sleep 10
    end

    module Mikulock
      def self.mikulock_play(play)
        if File.exist?("Sound\\" + play) == true
          IO.popen("mpg123 -q Sound\\#{play}")
          File.open(SYSTEM_LOG, "a") do |writelog|
            writelog.printf("%-36s - Play: %s\n", Time.now.to_s, File.expand_path("Sound\\" + play))
          end
        end
      end

      def self.mikulock_timecheck
        if Time.now.to_a[1] == 0
          case Time.now.to_a[2]
          when 0
            return "0h.mp3"
          when 1, 13
            return "1h.mp3"
          when 2, 14
            return "2h.mp3"
          when 3, 15
            return "3h.mp3"
          when 4, 16
            return "4h.mp3"
          when 5, 17
            return "5h.mp3"
          when 6, 18
            return "5h.mp3"
          when 7, 19
            return "7h.mp3"
          when 8, 20
            return "8h.mp3"
          when 9, 21
            return "9h.mp3"
          when 10, 22
            return "10h.mp3"
          when 11, 23
            return "11h.mp3"
          when 12
            return "12h.mp3"
          end
        elsif Time.now.to_a[1] == 30
          return "30min.mp3"
        else
          return false
        end
      end
    end

    # サービスのメイン処理
    def service_main
      play     = String.new
      play_end = String.new #お知らせが終わったものを記録

      # 最初の一回を設定
      if Time.now.to_a[2] >= 5  && Time.now.to_a[2] < 11
        play = "morning.mp3"
      elsif Time.now.to_a[2] >= 11 && Time.now.to_a[2] < 18
        play = "noon.mp3"
      else
        play = "night.mp3"
      end

      # 音声を再生する為のスレッド
      thread = Thread.new do
        while running?
          if state == RUNNING
            if play != false && play != play_end
              Mikulock.mikulock_play(play)
              play_end = play
            end
            sleep 5
          end
        end
      end

      # サービスの状態がrunningの時のみ繰り返す
      while running?
        if state == RUNNING
          play = Mikulock.mikulock_timecheck
          File.open(SYSTEM_LOG, "a") {|writelog|
            writelog.printf("%-36s - Running\n", Time.now.to_s)
          }
          sleep 5
        else
          sleep 1
        end
      end
    end

    # Stop(停止)シグナルが送られてきた時の処理
    def service_stop
      File.open(SYSTEM_LOG, "a"){|writelog|
        writelog.printf("%-36s - Stop\n", Time.now.to_s)
      }
    end

    # Pause(一時停止)シグナルが送られてきた時の処理
    def service_pause
      File.open(SYSTEM_LOG, "a"){|writelog|
        writelog.printf("%-36s - Pause\n", Time.now.to_s)
      }
    end

    # Resume(再開)シグナルが送られてきたときの処理
    def service_resume
      File.open(SYSTEM_LOG, "a"){|writelog|
        writelog.printf("%-36s - Resume\n", Time.now.to_s)
      }
    end
  end

  # mpg123のインストール先にcdしておく
  Dir.chdir(SYSTEM_BINDIR + "\\mpg123"){
    # このループの中で全ての処理が行われる
    RubyDaemon.mainloop
  }

  # 例外が発生した時は、例外の内容をログに書き込み、さらに例外を発生させる
rescue => error
  File.open(SYSTEM_LOG, "a"){|writelog|
    writelog.printf("%-36s - Error: %s\n", Time.now.to_s, error)
  }
  raise
end





これをそのまま実行しても、`mainloop': Service_Main thread exited abnormally.....となり、実行できないので、
サービス登録クラスを組み込んだスクリプトを利用し、サービスに登録、ログを読みながらデバッグする必要がある。

サービス作成中に良くあるミスは、
・Rubyインタプリタのパス、プログラムのソースのパスが間違っている
・service_mainのwhile running?の中が正しく書かれていない
・while running?の中にsleep 60などと書いてしまい、60秒待たないとサービスを停止できなくなる
・例外処理が不十分でサービスが落ちる
だった。


サービスの登録が終わったら、
コントロールパネル→管理ツール→サービス
を開き、登録したサービスが正しく実行されているか確認する。
サービスの状態が「開始」になっていれば、問題無く動作している…はず。
サービス一覧

OS起動時に自動実行したいときは、「サービス」でMikulockのプロパティを開き、
「スタートアップの種類」を「自動」もしくは「自動(遅延開始)」に設定すれば良い。
サービスのプロパティ
09.08.04 追記
・スクリプトの中で「スタートアップの種類」を「自動」に設定するにはどうしたら…?
:start_type => Service::AUTO_START
を、サービスインストール時の引数に渡せば良い模様。

ここまでで、とりあえずWindowsサービスを利用したRubyアプリケーションが作れるということが分かった。



以前はWin32/Soundを使用し、音を鳴らしていたが、サービスにすると上手く鳴らないようだったので、
mpg123を使用し、mp3を再生するようにしてみたのだが、
mpg123の-qオプションを使用しても、なにかが標準入力か標準出力を開くようで、
「対話型サービスの検出」ウィンドウが現れてしまうという問題が…
デスクトップとの対話
「デスクトップとの対話をサービスに許可する」を無効にすれば一応出現しなくなるが、Windowsのアプリケーションログに残ってしまうので、根本的な解決にはならない。

更に、mpg123には、どんなmp3ファイルを再生しても最後の2〜3秒がカットされてしまうという問題も…


今回作ったみくろっく(Windowsサービス版)のダウンロードは以下から。
Win32-Serviceのテスト用途にどうぞ。


※09.09.17 追記
みくろっく改良版



※09.08.07 追記
Dynamic DNS UpdaterをWindowsサービス化してみた。
以下からダウンロードできます。

Win32-Serviceが欲しかったのでRubyGemsをインストール

RubyでWindowsサービスを作成しようと、Win32-Serviceをインストールしようとしたところ、RubyGemsが無いと面倒くさそうだったので、まずはRubyGemsをインストールすることにした。


09.08.03時点で最新のrubygems-1.3.5.zipをダウンロードしてきて解凍する。
コマンドプロンプトを開き、setup.rbを実行。
C:\Users\hatsune\Desktop\rubygems-1.3.5> ruby setup.rb


バージョンを確認して、
C:\Users\hatsune\Desktop\rubygems-1.3.5> gem -v
1.3.5

となれば、インストールに成功していると思われる。

あとは、好きなライブラリをgemsから持ってくることが出来る。
C:\> gem install win32-service



…と思っていても、上手くいかないことがあるのが普通。


今回は、zlibとsslのライブラリが足りないと言われた。
Ruby-mswin32をそのまま使用している場合はまず発生すると思われる。

Ruby-mswin32のインストールを読むと、
「http://jarp.does.notwork.org/win32/にライブラリがあるので、これをインストールしろ」とのこと。


仕方が無いので、ライブラリをインストール。
openssl-0.9.8d-1-mswin32.zipと
zlib-1.1.4-1-mswin32.zipをダウンロードしてくる。
これを、フォルダ階層を維持しつつ、Rubyのバイナリがインストールしてあるフォルダにコピーする。
(\binはC:\Ruby\binへ、\libはC:\Ruby\libへ…という感じに)


これで実行できる…と思い、再度実行すると、今度はssleay32.dllが見つからないと言われた。
"ssleay32.dll"でググったところ、自分でOpenSSLをコンパイルするか、
OpenSSLのWindows用バイナリを配布してくれているサイトから持ってきて、
ssleay32.dllをコピーすればいいらしい。
(コンパイルするのは面倒なので、http://curl.haxx.se/download/openssl_w32vc6-0.9.8g.7zをダウンロード、インストールした。)


これでようやくRubyGemsが動き、Win32-Serviceをインストールすることが出来た。

2009年7月25日土曜日

マルチスレッド対応 マルチフロントエンド(もどき) を作ってみた

だいぶ前、マルチスレッド対応 lameフロントエンド(もどき)を更新すると言って早一ヶ月…。
ようやくそれなりのモノが完成したので公開することにしてみることにした。

以前作っていたマルチスレッド対応 lameフロントエンド(もどき)を元に、

・機能と安定性、起動速度の向上
・lame(mp3)以外のエンコーダー以外にも対応(設定ファイルで追加可能、NeroAacEnc(m4a)、ogg Vorbisの動作を確認)
・コマンドラインで動作させられるものであれば、音楽だけでなく、動画等のエンコーダーにも対応させることが可能になった…かもしれない
・一応オブジェクト指向風味に

…などの改善を行った。
(元にしたと言っても、ソースは1から書き直していたりするのだが…)

その他にも、

・手軽にマルチスレッド化でき、どんなエンコーダーでもマルチコアCPUを有効に使える
・ファイルの名前、サイズ、最終アクセス日時、最終更新日時でフィルタリングをかけ、対象ファイルを絞り込んだりできる
・相変わらずディレクトリ単位での一括エンコードに特化
・結構詳しいエンコードログを記録できる(バイナリ統一すればベンチマークとしても使える…かも?)
・実行ファイルのアイコンにディレクトリ(フォルダ)アイコンをドラッグアンドドロップすると、規定の設定で一発エンコードすることが可能

などといった特徴がある…と思う(´・ω・`)


プログラム本体は以下からダウンロードできます。
http://mtmef.g.hachune.net/

※09.07.27 neroAacEncのコマンドラインオプションが、"-b 256000"ではなく"-b 256"となっている問題を修正しました。
ついでに、ogg Vorbisでのエンコードにも対応させました。
これで、neroAacEncとogg Vorbisをマルチスレッド化できる…ハズ。

※09.08.07 lame ABRのオプションの"--a"とすべきところが、"-a"となっている問題、pdcurses.dllが見つからないと言われる問題を修正しました。
設定ファイルの"-a"となっているところを"--a"にするだけでも回避できます。

※09.08.15 追記
ブログだといろいろ見づらい上に分かりづらいので、一つに纏めてみた
ついでに使い方の解説もしています。
最新版のダウンロードはこちらのページから行えます。
http://mtmef.g.hachune.net/



デフォルトの設定ファイルでは、lame(mp3)かneroAacEnc(m4a)、ogg Vorbisのいずれかのバイナリが必要です。
lameのWindows用バイナリは以下からダウンロードすることが出来ます。
http://www.rarewares.org/mp3-lame-bundle.php
neroAacEncのバイナリは以下からダウンロードすることが出来ます。
http://www.nero.com/jpn/technologies-aac-codec.html
ogg Vorbisのバイナリは以下からダウンロードすることが出来ます。
http://www.rarewares.org/ogg-oggenc.php


フォルダごとに適当にドラッグアンドドロップで登録して、実行するとこんな感じになる。

4つを別々に登録したので、コマンドプロンプトのウィンドウが4枚開いているが、
一つのウィンドウの処理が終わるまで待ち、それが終わった瞬間次の処理に移るようにしているので、
ファイルの整合性に問題が出ることは無い。
ドラッグアンドドロップで大量に登録しても、処理待ち状態になって、他の処理が終了すれば自動的に次の処理が開始することになるので、指定されたスレッド数以上の処理をすること無く処理を行う事が出来る。


5月位からすこーしずつ作り続けて、ようやくここまで…(ヽ´ω`)
後は、ディレクトリ選択や設定変更をGUI上で出来るようにすれば…
ドラッグアンドドロップでのエンコードに対応したんで、そろそろ(もどき)外してもいいかな…('A`)

それにしても、今マルチスレッド対応 lameフロントエンド(もどき)のソースを見ると、こりゃ汚いな…と。
非効率な上、やたらコケるという…
正直、あんなもんリリースしててごめんなさいと言わざるを得ない気が…(´・ω・`)

2009年7月17日金曜日

Rubyで端末のサイズを得る

マルチスレッド対応〜プログラムを作っていて、一覧表示時を行う為に、Rubyで端末のサイズを得るにはどうしたら良いのか、という問題に直面した。

LinuxやBSDでは、stty sizeコマンドを実行して、それを解析し取得する方法もあるが、OSに依存するため使えない。
Sorarisなど、同じコマンドがあっても微妙に結果が違ったりするものもあるので…


以前のバージョンでは、cursesを使って獲得していた。

require "curses"
def gettermsize
Curses::init_screen
x = Curses::cols
y = Curses::lines
Curses::close_screen
return {"x"=>x.to_i, "y"=>y.to_i}
end


この方法も、RubyにCursesライブラリが入っていないと駄目という問題が。
しかも、環境によって、標準出力のバッファリングがおかしくなることがあるという謎の現象が…
標準出力がおかしい件

これを回避するには、標準出力を同期モードにしてやればよいようだった。

STDOUT.sync = true

これをcurses読み込み前に書いておけば、標準出力がプログラムの流れと同期して表示されるようになった。


もっと効率が良く、どのプラットフォームでも同じ結果が得られるものは無いのだろうか…