CUI 版Dropbox?
どうやら、この python スクリプト使って、X システムが無いCUIオンリーの環境でも、Dropbox のインストール・実行できるよってことらしい。
使ってみた。
$ wget https://dl.getdropbox.com/u/43645/dbcli.py $ python dbcli.py Usage: dbcli.py <command> [options] ... <command> [options] Available commands: status - Get overall status for the daemon. copypublic <file> - Copies the url to the clipboard (? I guess) install <x86/x86_64> - Tries to download the daemon and install it in your home directory. browse <folder> - Browse the specified folder in your browser. copygallery - Copies the gallery url to the clipboard. share <file> - Open the browser and shows share-information. file <file> - Get information about a file/folder. folder <folder> - Get information about a file/folder. help - Gives this. revisions <file> - Opens the revisions page in your browser for the specified file.
なるほどー取りあえず install コマンド使えば良い感じ?
$ python dbcli.py install x86 error: couldn't connect to the daemond. Trying to start the daemon. The daemon does not exist (try dbcli.py install) rm: cannot remove `/home/kui/.dropbox-dist/': そのようなファイルやディレクトリはありません Starting to download dropbox-lnx.x86-0.6.382.tar.gz... ... 1 kB of 17149 kB ... 1716 kB of 17149 kB ... 3431 kB of 17149 kB ... 5146 kB of 17149 kB ... 6861 kB of 17149 kB ... 8576 kB of 17149 kB ... 10291 kB of 17149 kB ... 12006 kB of 17149 kB ... 13721 kB of 17149 kB ... 15436 kB of 17149 kB ... 17151 kB of 17149 kB # この辺で「おおおこれはひょっとして、dropboxdとかダウンロードしてる!?wktk」ってなってる。 Done. Unpacking... Trying to start the daemon. Traceback (most recent call last): File "<string>", line 6, in <module> File "__main__.py", line 105, in <module> File "__main__dropbox__.py", line 174, in <module> File "__main__dropbox__.py", line 169, in main_startup File "arch/linux/startup.py", line 131, in main_thread_loop AttributeError: 'NoneType' object has no attribute 'MainLoop' Exception in thread CONTROL (most likely raised during interpreter shutdown): Traceback (most recent call last): File "threading.py", line 486, in __bootstrap_inner File "common_util/trace.py", line 31, in intwatched <type 'exceptions.TypeError'>: 'NoneType' object is not callable Unhandled exception in thread started by <bound method WatchedThread.__bootstrap of <WatchedThread(CONTROL, started)>> Error in sys.excepthook: TypeError: 'NoneType' object is not callable Original exception was: Traceback (most recent call last): File "threading.py", line 462, in __bootstrap File "threading.py", line 527, in __bootstrap_inner AttributeError: 'NoneType' object has no attribute 'acquire' Exception in thread AUTHENTICATE (most likely raised during interpreter shutdown): Traceback (most recent call last): File "threading.py", line 486, in __bootstrap_inner File "client_api/authenticate.py", line 207, in run <type 'exceptions.TypeError'>: 'NoneType' object is not callable Unhandled exception in thread started by <bound method AuthenticationThread.__bootstrap of <AuthenticationThread(AUTHENTICATE, started)>> Error in sys.excepthook: TypeError: 'NoneType' object is not callable Original exception was: Traceback (most recent call last): File "threading.py", line 462, in __bootstrap File "threading.py", line 527, in __bootstrap_inner AttributeError: 'NoneType' object has no attribute 'acquire' Exception in thread RTRACE (most likely raised during interpreter shutdown): Traceback (most recent call last): File "threading.py", line 486, in __bootstrap_inner File "common_util/trace.py", line 486, in run File "common_util/trace.py", line 412, in rtrace_thread_func <type 'exceptions.TypeError'>: 'NoneType' object is not callable Unhandled exception in thread started by <bound method RtraceThread.__bootstrap of <RtraceThread(RTRACE, started)>> Error in sys.excepthook: TypeError: 'NoneType' object is not callable Original exception was: Traceback (most recent call last): File "threading.py", line 462, in __bootstrap File "threading.py", line 527, in __bootstrap_inner AttributeError: 'NoneType' object has no attribute 'acquire'
何やらダメだった。pythonはちょっとしかかじった事無いので、何が書いてあるか今イチピンとこない。どうやら lenny な debian ではダメだったようだ。あとで原因追及したいな。
追記
どうやら
: Trying to start the daemon. :
から下のメッセージは、dropboxd からのメッセージみたい。直接 dropboxd を動かしてみて分かった。Ubuntu は良くても debian はダメってことかしら。残念。
Dropbox使ってみた & ssh ログイン時にDropbox使う方法
話題になってただけあって便利な感じ.気になるのは,Gnome (正確には nautilus?)を起動するようなログインをしないと自動で同期してくれない点.
実際,Linux を ssh でリモートログインして使うことの多い僕には,Dropbox縁無いかなー残念かなー
と思いつつ,そもそもこれ,誰が同期してるんだろうなーと思い探し出す.
$ ps x | grep drop 19109 ? Ssl 0:07 /home/kui/.dropbox-dist/dropboxd
それっぽい名前...
ssh ログインしてこいつを動かしてみる.
$ ~/.drop-dist/dropboxd [プロンプト帰ってこない]
う,,ごいた?
別端末から確認してみたところうまく機能しているようです.ノーティファー無くても,同期さえしてくれていればいいのさ!って人は,これだけ動かせばいいのかもしれないですね.
これで,このデーモンのスタート&ストップスクリプトを誰かが書いてくれたら幸せになれる!
ちなみに「.drop-dist/dropboxd」でぐぐるとなにやらたくさん出てきているのでもっと便利な方法あるかもしれないです.
もごもごに Autopagerize を
最近になってAutopagerize使いはじめた。今まで手をつけてなかったのは、AutoPagerizeを絶賛する記事が多かったため天邪鬼根性発動していたから。
いいかんじですねー。ホイールまわすのが楽しくなってしまう。
難点は、
- フッターを見る機会がほとんど無くなってしまうため、サイトデザイナーさん泣かせな点
- AutopagerizeのON/OFFやりにくい
- 調子に乗ってホイール回す → トップに戻るの大変(Homeキー押すなりすれば解決
って感じでしょうか。
でもやっぱりホイール回すの楽しい。
んでタイトルの通り、いつも遊びに行ってるもごもごがautopagerizeできないので自分でsiteinfo書いた。
var SITEINFO = [ // みんなのひろば、マイホーム { url: 'http://(www.)?mogo2.jp/(top|home(/archive_(all|res)|/res)?|(comment|keyword/search)/[1-9][0-9]*)', nextLink: '//div[@id="t_all_read"]/p/a[last()]', pageElement: 'id("newest_comment")', exampleUrl: 'http://mogo2.jp/top', }, // もごリンク、XXさんの部屋 (こっちの表示納得いかない) { url: 'http://(www.)?mogo2.jp/(member/(keyword/)?)?[1-9][0-9]*', nextLink: '//div[@id="t_all_read"]/p/a[last()]', pageElement: 'id("hot_kuchikomi")', exampleUrl: 'http://mogo2.jp/99', }, ]
こんな感じで書いた。
しかしなんだ、urlをあんなに気合い入れて書く必要なかった様子。もったいないからそのまま。
wedata に登録しておいた。もごもご(みんなのひろば、マイホーム)、もごもご(もごリンク、XX さんの部屋)
登録してから気がついたけど、これ、もごもごで使う人いるのかなぁ。。。
なんか久しぶりにXPath書いたー
んー登録したけど上のデータを一つに纏められてしまったので,みんなのひろばでの挙動がおかしくなってしまった.何かにコミットする経験って初めてだからどうしたらいいかわからないなあ.
Last.fmラジオの自作
http://code.google.com/p/thelastripper/wiki/LastFM12UnofficialDocumentation
Last.fm のラジオを自分で作る際に必要な WebAPI のドキュメント.ただ...これをみて堂々とプレイヤーを公開するのは気が引けますね.再生までの簡単な手順はこんな感じ*1.最後の方に "まとめ" として簡単なRubyスクリプトも載せてみた.これくらいなら大丈夫だよね...きっと..
手順
ラジオとして機能させるにはざっくり分けると四つのステップを踏むことになります.具体的には,
- Handshake
- セッションIDの取得,リクエスト先のホスト名の取得
- Adjusting Radio Station
- ラジオの選択
- Requesting an XSPF
- プレイリストの取得
- Play
- 音楽再生
となります.しかし,Handshake で取得する情報は,2回目以降でも使い回しができるみたいです.
Handshake
セッションを張る
要求
以下の URL に GET リクエストを送る.
http://ws.audioscrobbler.com/radio/handshake.php?version=1.3.1.1&username=[USERNAME]&passwordmd5=[PASSWORDMD5_HASH]
応答
すると,ws.audioscrobbler.com からはこんな感じのが返ってくる
session=ae1eb54a11615e605d61d6e83dde71bc stream_url=http://87.117.229.85:80/last.mp3?Session=ae1eb54a11615e605d61d6e83dde71bc subscriber=0 framehack=0 base_url=ws.audioscrobbler.com base_path=/radio info_message= fingerprint_upload_url=http://ws.audioscrobbler.com/fingerprint/upload.php
実際に必要なのは session, base_url, base_path の値だけ.
これら三つを保存しておく.
Adjusting Radio Station
ラジオ局の指定
要求
以下の URL に GET リクエストを送る.
http://[base_url][base_path]/adjust.php?session=[sessionID]&url=[LASTFMURI]
Last.fm独自URI
僕が良く使うやつだけ挙げておく.他のは元のを参照してください.
- lastfm://artist/$artistname
- $artistname で指定されるアーティストに似た楽曲が集まった局を指定
- lastfm://globaltags/$tag
- $tag で指定されるタグがついた楽曲が集まった局を指定
応答
例えば, lastfm://globaltags/8bit とラジオ局を選択した時に,選択が成功するとこんな感じの応答が返ってくる.
response=OK url=lastfm://globaltags/8bit stationname= 8bit Tag Radio discovery=true
ここで特に保存すべき内容は無い.
Requesting an XSPF
プレイリストを取得する.XSPF とはプレイリストのファイル形式の一つで,XML の形式をとっています.
要求
以下のように GET 要求を送る
http://[base_url][base_path]/xspf.php?sk=[SESSIONID]&desktop=1.3.1.1
- [base_url],[base_path],[sessionID]
- 先ほどの Handshake で取得した値
応答
すると以下のXSPF形式で応答がある.(先ほどと同じ様に 8bit というタグで指定した場合)
<playlist version="1" xmlns:lastfm="http://www.audioscrobbler.net/dtd/xspf-lastfm"> <title>+8bit+Tag+Radio</title> <creator>Last.fm</creator> <link rel="http://www.last.fm/skipsLeft">6</link> <trackList> <track> <location>http://play.last.fm/user/da4d7abd06d00e21b9b387b491cbf1fe.mp3</locatio n> <title>Marbles</title> <id>36732</id> <album>Swarm & Dither</album> <creator>Hrvatski</creator> <duration>227000</duration> <image>http://cdn.last.fm/coverart/130x130/7099.jpg</image> <lastfm:trackauth>92c11</lastfm:trackauth> <lastfm:albumId>7099</lastfm:albumId> <lastfm:artistId>2308</lastfm:artistId> <link rel="http://www.last.fm/artistpage">http://www.last.fm/music/Hrvat ski</link> <link rel="http://www.last.fm/albumpage">http://www.last.fm/music/Hrvatski/Swarm %2B%2526%2BDither</link> <link rel="http://www.last.fm/trackpage">http://www.last.fm/music/Hrvatski/_/Mar bles</link> <link rel="http://www.last.fm/buyTrackURL"></link> <link rel="http://www.last.fm/buyAlbumURL">http://www.last.fm/affiliate_sendto.p hp?link=catch&prod=7099&pos=65633c2c6d40fbe9c8bf27ce82d2ca5a</link> <link rel="http://www.last.fm/freeTrackURL"></link> </track> <track> <location>http://play.last.fm/user/f1702fa3845bec7817a62749a1fbc49a.mp3</location> <title>Pioneer</title> <id>69590509</id> <album>Pioneer</album> <creator>she</creator> <duration>204000</duration> <image>http://cdn.last.fm/coverart/130x130/3287460.jpg</image> <lastfm:trackauth>35a7f</lastfm:trackauth> <lastfm:albumId>3287460</lastfm:albumId> <lastfm:artistId>1083333</lastfm:artistId> <link rel="http://www.last.fm/artistpage">http://www.last.fm/music/she</link> <link rel="http://www.last.fm/albumpage">http://www.last.fm/music/she/Pioneer</link> <link rel="http://www.last.fm/trackpage">http://www.last.fm/music/she/_/Pioneer</link> <link rel="http://www.last.fm/buyTrackURL"></link> <link rel="http://www.last.fm/buyAlbumURL"></link> <link rel="http://www.last.fm/freeTrackURL">http://freedownloads.last.fm/download/69590509/Pioneer.mp3</link> </track> </trackList> </playlist>
とこんな感じ.
Play
先のレスポンスの中の,"/playlist/trackList/track/location" で指定される文字列を,mpg123 などに渡す.
まとめ
再生までの最小限のコードはこんな感じ(?)になるのかな.
net/http で Keep-Alive なやりとり
Ruby でよく使うライブラリ net/http なんですが,コネクション張り続けて通信するにはどうしたらいいんだろう.という話.
いろいろ弄った結果
ポイントは二つくらい
念のため Net::HTTP:Get のインスタンスに以下のようなヘッダエンティティtをくっつける.Net::HTTP::Get#['Connection'] = 'Keep-Alive'
Net::HTTP.start や,Net::HTTP#start を使ってコネクションを張る
2回目以降の要求をする時において,前回の要求から時間が空いていると,サーバがコネクションを断ち切ってしまうので,コネクションの張り直し手続きが必要になる.
また3回目の要求の際は,意図的に間隔を空けて,サーバからコネクションを断ち切られてしまった場合を再現している.
#!/usr/bin/ruby -Ku require 'net/http' require 'uri' host = 'd.hatena.ne.jp' port = 80 path = '/' sleep_time = 7 # 要求・応答ヘッダ出力 def print_key_and_val(hash) puts "-- #{hash.class} --" hash.each do|k,v| puts "#{k} => \t#{v}" end puts '' end http = Net::HTTP.new(host,port) req = Net::HTTP::Get.new(path) # ポイント1 req['Connection'] = 'Keep-Alive' # コネクションを張る(ポイント2 http.start # 1回目の要求 puts '## First' print_key_and_val req print_key_and_val http.request(req) # 2回目の要求 puts '## Second' print_key_and_val req print_key_and_val http.request(req) # 間隔を空ける puts "\n## sleep #{sleep_time}s\n\n" sleep sleep_time # この間に,d.hatena サーバからコネクションを切られてしまう # 3回目の要求(ポイント3 # sleep 中に,サーバからコネクションを切られているので,うまくいかない. # EOFError が発生してしまう.この例外を捕捉し,コネクションを張りなおす # 事で対処をした. puts '## Third' retry_flag = true # 張りなおしてもダメだった時を判別するためのフラグ begin print_key_and_val req print_key_and_val http.request(req) rescue EOFError => e puts '********************' puts '** Catch EOFError **' puts '********************' if retry_flag http.finish # 一度こちらからも切って, http.start # コネクション張り張り直し retry_flag = false retry else raise EOFError, e.message end end # コネクションを切る http.finish
実行結果
$ ruby http_timeout.rb ## First -- Net::HTTP::Get -- connection => Keep-Alive accept => */* -- Net::HTTPOK -- vary => Accept-Encoding connection => Keep-Alive content-type => text/html; charset=euc-jp date => Fri, 21 Mar 2008 09:06:09 GMT server => Apache keep-alive => timeout=7, max=5 transfer-encoding => chunked ## Second -- Net::HTTP::Get -- connection => Keep-Alive accept => */* host => d.hatena.ne.jp -- Net::HTTPOK -- vary => Accept-Encoding connection => Keep-Alive content-type => text/html; charset=euc-jp date => Fri, 21 Mar 2008 09:06:09 GMT server => Apache keep-alive => timeout=7, max=4 transfer-encoding => chunked ## sleep 7s ## Third -- Net::HTTP::Get -- connection => Keep-Alive accept => */* host => d.hatena.ne.jp ******************** ** Catch EOFError ** ******************** -- Net::HTTP::Get -- connection => Keep-Alive accept => */* host => d.hatena.ne.jp -- Net::HTTPOK -- vary => Accept-Encoding connection => Keep-Alive content-type => text/html; charset=euc-jp date => Fri, 21 Mar 2008 09:06:17 GMT server => Apache keep-alive => timeout=7, max=5 transfer-encoding => chunked $
その他
実際,書く時は,イチイチ begin ... rescue ... end で EOFError を補足するよりも,def で EOFError 捕捉機能つきの要求メソッドを用意するか,下記のようにしてしまった方が,賢く見えるかも.
class Net::HTTP alias_method :old_request, :request def request(req, body = nil, &block) retry_flag = true begin old_request(req, body, &block) rescue EOFError => e if retry_flag retry_flag = false finish start retry else raise EOFError, e.message end end end end
こうすれば,再接続を意識せずにコードが書ける.本当は
レスポンスヘッダ Keep-Alive の max値も考慮に入れなければならないんでしょうね...
Ruby で音楽再生
Ruby で mp3 ファイルを再生する方法について.ただし mpg123 など音楽再生部分は外部コマンドに任せてる.
RubyForge とか,RAAみてもピンとくるのがなかったので書く.
音楽再生に関するノウハウはさっぱりなので,誰かいい方法知ってたらへるぷみー
目的
Ruby で音楽再生をしたい.
しかしながら RubyForge とか,RAAみてもピンとくるのがなかった.
ピンとくるのがなかっただけで,Ruby/SDL とか,ruby-audiere があります.
しかし,これらはムダに大掛かりだったり,Audiere というソフト(ライブラリ?)をインストールする必要があったりするので使いたくありませんでした.
そこで,手元の自宅サーバに既にインストールされてる mpg123 を使えないかと考えました.
それに mpg123 は CUI の音楽再生ソフトの中では有名な方だと思うので.
やり方
この方法では mpg123 が必要になる.
Siena.さんからのアドバイスから,popen3, open 以下の内容をブロックに入れました.ありがとうございます.
#!/usr/bin/env ruby -Ku require 'open3' mp3filename = ARGV.shift mpg123 = '/usr/bin/mpg123' command = "#{mpg123} -b 1000 -q -" buffer_size = 1024 Open3.popen3(command) do | pipes | sound = pipes[0] pipes[1].close # これと pipes[2].close # これって,要らないかな. open(mp3filename) do | mp3file | while !(buf = mp3file.read(buffer_size)).nil? sound.write buf end end end
問題点
厳密な再生終了がわからない.
実際に上のコードを動かすとわかるけれど,上の while ループ部分が終了しても音楽の再生が終わったとは限らない.
mpg123 のオプションの -b を小さく(あるいは取っ払う)ことで幾分かマシにはなるものの,それでも両終了の時差は 1 秒程度発生している.
マイッタネ.