日記を更新すると、TDiary::Config#data_path/category/ 以下の、カテゴリごとに作られるキャッシュファイルがずいぶんたくさん更新される。全部ではないが半分近い 21のファイルが更新されていた。日記の内容はというと一つのカテゴリしか使っていない。
どこのコードが無駄にファイルを更新しているのかと絞っていくと、category.rbの中の Category::Cache#replace_sectionsだとわかった。ではこの replace_sectionsが悪いのかというとそうではない。replace_sectionsの中の
PStore.new(cache_file(c)).transaction do |db| end
に囲まれた部分をすべてコメントアウトしても 21のキャッシュファイルが一斉に更新されたのだから。一部の PStoreファイルは開いて閉じるだけで常に更新されるのだとしか考えられない。
PStoreは transactionの前後で Marshal::dump の戻り値のサイズと MD5が変化したかどうかを見て、変更があったかどうかを判断し、ファイルに書き込みをするかしないかを決めている。Marshal::load/dump が対称ではないのだろか。(そもそも Hashを Marshal::dumpした結果が一定だと仮定してよいのだろうか*)
原因が何であるにせよサーバーの pstore.rbを書き換えるわけにもいかないので、tDiaryの category.rbに対策を施した。
読み出し専用であることが予めわかっている PStore#transactionはすべて trueを引数にして(readonly=trueの意)呼び出す。実はそういう transactionはすべて PStore#abortで終わっているのでこの対策は必要ないのだが、ファイルを排他ロックすることと MD5を計算する手間が省けるので一応。
肝心の Category::Cache#replace_sectionsは transactionの開始前に変更があるのかどうかがわからないので PStore#transaction(true)は使えない。日付の削除が空振りに終わったときにだけ PStore#abortを呼ぶことにする。これで必要最低限のキャッシュファイルだけが更新されるようになった。
# # cache each section of diary # used in update_proc # def replace_sections(diary) return if diary.nil? or !diary.categorizable? categorized = categorize_diary(diary) categories = restore_categories deleted = [] ymd = diary.date.strftime('%Y%m%d') categories.each do |c| PStore.new(cache_file(c)).transaction do |db| db['category'] = {} unless db.root?('category') if categorized[c] and diary.visible? db['category'].update(categorized[c]) else # diary is invisible or sections of this category is deleted db.abort unless db['category'].delete(ymd) deleted << c if db['category'].empty? end end end if !deleted.empty? deleted.each do |c| File.unlink(cache_file(c)) end replace_categories(categories - deleted) end end
* 追記:最近の Ruby(1.8.7だか 1.9.0)は Hashの keyの順序を保存しているみたいだけど。
以下のコードと同じ出力をより短いコードで得よ。(パーは 27バイト)
-2000.step(-10000,-10) do |v| puts v end
n=1990;801.times{p -n+=10}
-1 (26バイト)だからバーディーかな。アルバトロス(-3)が上限とは限らないけど。数値リテラルが冗長な気がするけど他の表現が思いつかない。文字コード(?X)を使おうと思ったけどできなかった。
irb(main):115:0> require :benchmark.to_s irb(main):119:0> Benchmark.bmbm{|j| j.report } NameError: uninitialized constant Benchmark::Job::ArgmentError from C:/Program Files (x86)/ruby/lib/ruby/1.8/benchmark.rb:333:in `report' from (irb):119 from C:/Program Files (x86)/ruby/lib/ruby/1.8/benchmark.rb:250:in `bmbm' from (irb):119 from :0 irb(main):120:0>
ArgmentError -> ArgumentError
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/44601
irb(main):001:0> RUBY_RELEASE_DATE => "2006-12-25" irb(main):002:0> "aaa"\ irb(main):003:0* "bbb" => "aaabbb" irb(main):004:0> "aaa"\ irb(main):005:0* %[bbb] NameError: undefined local variable or method `bbb' for main:Object from (irb):5
こういうもんなの? "bbb" と %!bbb! は同じものだと思っていたが。
'%04d-%02d-%02d'%[2007,12,12] => "2007-12-12"
String#% が呼ばれてるんだよ。
まずローカルで試してからアップロードしたのだがローカルの Apacheが Ruby-1.9.0を呼ぶので、FrontPageの表示からログインまでを可能にするために加えた変更点のリストをメモしておく。
- instance_variables.each do |v| + instance_variables.each do |v| v = v.to_s;
vにシンボルが渡されて、次の行で v.sub!したときにエラーになっていた。
if page && !page.empty? - redirect(@cgi, @conf.base_url + @plugin.hiki_url( page ), session_cookie( session.session_id )) + redirect(@cgi, @conf.base_url + @plugin.hiki_url( page ), [session_cookie( session.session_id )]) else - redirect(@cgi, @conf.index_url, session_cookie( session.session_id )) + redirect(@cgi, @conf.index_url, [session_cookie( session.session_id )])
Cookieが session_id=SESSIONIDの形でなく SESSIONIDと key名なしの状態でブラウザにセットされるからログインに失敗していた。
Hiki::Command#cmd_logoutでは同じ引数を [session_cookie(session_id, -1)] としていたので同じように配列にした。
- Digest::MD5::new( s || '' ).hexdigest + Digest::MD5::hexdigest( s || '' )
リファレンスマニュアルには Digest::MD5.new([str]) とあるが引数の数が 0でないと叱られる。
when Array - "[\n"+obj.collect{|x| dump_text(x)+",\n"}.to_s+"]" + "[\n"+obj.collect{|x| dump_text(x)}.join(",\n")+"\n]" when Hash - "{\n"+obj.sort_by{|e| e[0].inspect}.collect{|k,v| "#{dump_text(k)} => #{dump_text(v)},\n"}.to_s+"}" + "{\n"+obj.sort_by{|e| e[0].inspect}.collect{|k,v| "#{dump_text(k)} => #{dump_text(v)}"}.join(",\n")+"\n}"
dumpに失敗していた。
原因となった Array#to_sのバージョンによる出力の違い↓。
Ruby-1.8.5p12> [1,2,3].to_s #=> "123" Ruby-1.9.0 20061205> [1,2,3].to_s #=> "[1, 2, 3]"
リファレンスマニュアルには
to_s self.join($,) と同じです。
と書いてあるから to_sで(ある種の) joinを代用していても仕方ない。
と思ったがどちらにしろ Array#to_sの出力は $, に依存するので、後で(loadするときに) evalすることを考えれば今回の to_sの使用は不適切か。
上のエントリで FrontPageの表示とログインまでやったが、差分の表示もおかしかったので Array#to_s らしき部分を join('') に書き換えまくったら直った模様。
'string'.join は存在しなかったので、書き換えてエラーにならないということは考えたとおり Array#to_sだったか、そのコードが実行されてなくて発覚してないが実は間違いだった(実行されたらNoMethodErrorになる)かのどちらか ^_^; こういう実行してみないとわからないところは javascriptと同じでレアなコードパスのデバッグを難しくするね。
以下、リスト。
+if(defined? ' '.ord) # Ruby-1.9 def escape_meta_char( text ) text.gsub( META_CHAR_RE ) do |s| + '&#x%x;' % s[1].ord + end + end +else + def escape_meta_char( text ) + text.gsub( META_CHAR_RE ) do |s| '&#x%x;' % s[1] end end +end
いきなり Array#to_sと関係ないが String#[index] が Integerに代えて一文字の Stringを返すようになった対策。
if digest - return View.new( diff, src.encoding, src.eol ).to_html_digest(overriding_tags, false).to_s.gsub( %r|<br />|, '' ).gsub( %r|\n</ins>|, "</ins>\n" ) + return View.new( diff, src.encoding, src.eol ).to_html_digest(overriding_tags, false).join(\).gsub( %r|<br />|, ).gsub( %r|\n</ins>|, "</ins>\n" ) else - return View.new( diff, src.encoding, src.eol ).to_html(overriding_tags, false).to_s.gsub( %r|<br />|, '' ).gsub( %r|\n</ins>|, "</ins>\n" ) + return View.new( diff, src.encoding, src.eol ).to_html(overriding_tags, false).join(\).gsub( %r|<br />|, ).gsub( %r|\n</ins>|, "</ins>\n" ) end
if digest - return View.new( diff, src.encoding, src.eol ).to_wdiff_digest({}, false).join.gsub( %r|\n\+\}|, "+}\n" ) + return View.new( diff, src.encoding, src.eol ).to_wdiff_digest({}, false).join(nil).gsub( %r|\n\+\}|, "+}\n" ) else - return View.new( diff, src.encoding, src.eol ).to_wdiff({}, false).join.gsub( %r|\n\+\}|, "+}\n" ) + return View.new( diff, src.encoding, src.eol ).to_wdiff({}, false).join(nil).gsub( %r|\n\+\}|, "+}\n" ) end
join('')か join(nil)か統一しろよ、とセルフツッコミ。
- before_change = Document.new(line[1].to_s, + before_change = Document.new(line[1].join(''), doc1.encoding, doc1.eol) - after_change = Document.new(line[2].to_s, + after_change = Document.new(line[2].join(''), doc2.encoding, doc2.eol)
if block_given? - source = yield block[1].to_s - target = yield block[2].to_s + source = yield block[1].to_a.join '' + target = yield block[2].to_a.join '' else - source = block[1].to_s - target = block[2].to_s + source = block[1].to_a.join '' + target = block[2].to_a.join '' end
block[i]は nilの可能性があるので to_a.join
if block_given? - source = yield entry[1].to_s - target = yield entry[2].to_s + source = yield entry[1].to_a.join '' + target = yield entry[2].to_a.join '' else - source = entry[1].to_s - target = entry[2].to_s + source = entry[1].to_a.join '' + target = entry[2].to_a.join '' end if i == 0 context_pre = "" # no pre context for the first entry else - context_pre = @difference[i-1][1].to_s.scan(context_pre_pat).to_s + context_pre = @difference[i-1][1].to_a.join('').scan(context_pre_pat).to_s end if (i + 1) == @difference.size context_post = "" # no post context for the last entry else - context_post = @difference[i+1][1].to_s.scan(context_post_pat).to_s + context_post = @difference[i+1][1].to_a.join('').scan(context_post_pat).to_s end
def source_lines() if @source_lines == nil - @source_lines = @difference.collect{|entry| entry[1]}.join.scan_lines(@eol) + @source_lines = @difference.collect{|entry| entry[1]}.join(nil).scan_lines(@eol)
def target_lines() if @target_lines == nil - @target_lines = @difference.collect{|entry| entry[2]}.join.scan_lines(@eol) + @target_lines = @difference.collect{|entry| entry[2]}.join(nil).scan_lines(@eol)
{{hoge a b 5}} #=> hoge('a', 'b', 5) {{hoge 'a' b(5)}} #=> hoge('a', 'b', 5) {{hoge ,a(, b)(5)}} #=> hoge('a', 'b', 5) {{hoge, a, b, 5}} #=> PluginException('not plugin method: hoge,') {{hoge; hage}} #=> PluginException
文法の緩さとか、一つのメソッドしか呼べないとか、嫌すぎる。
そりゃあ Rubyで
require digest/md5
という風にライブラリ名をクォーテーションで括らずに書けたら楽だなとかは考えるし、Symbolが Stringのサブクラスになったときは
require :sqlite3
が通るのを一番に確認したけど、
defined? printf alias printg printf
を見て、
というのと同種の嫌悪を感じる。
結局
> ruby ipodpl.rb I:\ artist:"小松 未歩" rating:5 rating:4 or and --album-shuffle > temp.m3u
という形になった。
最近は iPodから曲が溢れてきていて、HDDが安いこともあって iPodに入ってる曲や溢れた曲、全て PCに入っている。
iPodが唯一のミュージックライブラリだった 9月とは状況が変わっているので iPod内の曲を PCから検索できてもあまり嬉しくない。iPodより多くの曲が PCに入っているから。
一応、産物。
予め iTunesDBから使えそうな情報を拾って SQLite3形式のデータベースに登録しておいて、検索は SQL任せ、という仕様です。
Songbirdのデータベースは最初から SQLiteだ。良き哉良き哉。
29日に Ruby1.9をダウンロードした。添付ライブラリのRipperが、m4sugar.m4が見つからない、というエラーでコンパイルできない以外は問題なくインストール完了。RUBY_PLATFORMは i386-bccwin32。
Ruby1.8.5で動いている http://vvvvvv.sakura.ne.jp/ds14050/buch/ が Ruby1.9でも動くのか試してみると、sqlite3-rubyの Nativeドライバ、DLドライバが両方とも動かない。Nativeは当然として、DLが動かないのは Ruby1.9では ruby-dl2が dlとして添付されているから。
出てくるエラーを順番に潰す過程でやったことは定数名の置き換えが殆ど。そんなことしかできません。コールバック関数を SQLiteに渡す authorizerや create_function関連は自分で使っていないので何もしていない。DL.callbackが存在しない為にエラーが出るのは間違いない。dl2では bindを使うのだろうか?
以下は変更点のリスト
* sqlite3/driver/dl/api.rb:38 -extend ::DL::Importable +extend ::DL::Importer
単なる名称変更。
* sqlite3/driver/dl/api.rb:92 -extern "ibool sqlite3_complete(const char*)" +extern "int sqlite3_complete(const char*)" * sqlite3/driver/dl/api.rb:93 -extern "ibool sqlite3_complete16(const void*)" +extern "int sqlite3_complete16(const void*)" * sqlite3/driver/dl/driver.rb:96 -API.send( utf16 ? :sqlite3_complete16 : :sqlite3_complete, sql+"\0" ) +API.send( utf16 ? :sqlite3_complete16 : :sqlite3_complete, sql+"\0" ) != 0
iboolという返り値(戻り値?)の型が dl2では定義されていない(定義しようがない?)ので、intを bool値として受け取るのは諦めて、返り値を利用するドライバの方で非0かどうか調べる。
* sqlite3/driver/dl/driver.rb:40 -DL.sizeof("L") +DL::SIZEOF_LONG
sizeofというメソッドは dl2の DL::Importerモジュールにもあるが使い方がわからないし、定数の方が良い。
* sqlite3/driver/dl/driver.rb:* -DL::PtrData +DL::CPtr * sqlite3/driver/dl/driver.rb:242,247,252 -result ? result.to_s : nil +result.null? ? nil : result.to_s
DL::CPtrが DL::PtrDataと完全に互換な置き換えなのかわからないが当面のエラーは消えた。
DLL関数の返値がポインタの場合は常に CPtrが返ってくるらしく、CPtrの指すアドレスが NULLの場合でも Ruby的には nilではないので「result ? result.to_s : nil」が常に result.to_sになり、ぬるぽエラーになることがある。PtrDataとは振る舞いが違う?
* lib/ruby/1.9/dl/value.rb:72 -return [arg].pack("p").unpack("l!")[0] +return [arg.dup].pack("p").unpack("l!")[0] * lib/ruby/1.9/dl/value.rb:74 -return [arg].pack("p").unpack("q")[0] +return [arg.dup].pack("p").unpack("q")[0]
frozenオブジェクトを変更しようとした、ってエラーがでるので間に合わせで Rubyの添付ライブラリの方を修整。どこから frozenオブジェクトが渡されたのやら。
swigが sqlite3_api.iを基に出力する sqlite3_api_wrap.cを以下のように置換したら自分が使用している範囲では動いている。
-RSTRING()->ptr +RSTRING_PTR() -RSTRING()->len +RSTRING_LEN()
irb(main):001:0> RUBY_VERSION => "1.8.4" irb(main):002:0> '//a//b//'.split('/') => [,, "a", "", "b"] irb(main):003:0> '//a//b//'.split('/', 99) => [,, "a",, "b",, ""]
末尾のスラッシュが無かったことに……。< Perl由来のようです
因みに 1.6系のはまりどころは splitの第一引数に 2文字以上の文字列を与えた場合、勝手に正規表現にコンパイルされてしまうところ。
/ItemLookupResponse /ItemLookupResponse/OperationRequest /ItemLookupResponse/OperationRequest/HTTPHeaders /ItemLookupResponse/OperationRequest/RequestId /ItemLookupResponse/OperationRequest/Arguments /ItemLookupResponse/OperationRequest/RequestProcessingTime /ItemLookupResponse/OperationRequest/HTTPHeaders/Header /ItemLookupResponse/OperationRequest/Arguments/Argument[1] /ItemLookupResponse/OperationRequest/Arguments/Argument[1] /ItemLookupResponse/OperationRequest/Arguments/Argument[1] /ItemLookupResponse/OperationRequest/Arguments/Argument[1] /ItemLookupResponse/OperationRequest/Arguments/Argument[1] /ItemLookupResponse/OperationRequest/Arguments/Argument[1] /ItemLookupResponse/OperationRequest/Arguments/Argument
pathが重複している。
def xpath path_elements = [] cur = self path_elements << __to_xpath_helper( self ) while cur.parent cur = cur.parent path_elements << __to_xpath_helper( cur ) end return path_elements.reverse.join( "/" ) end
pathの各要素は __to_xpath_helperで取ってきている。
def __to_xpath_helper node rv = node.expanded_name if node.parent results = node.parent.find_all {|n| n.kind_of?(REXML::Element) and n.expanded_name == node.expanded_name } if results.length > 1 idx = results.index( node ) rv << "[#{idx+1}]" end end rv end
node.expanded_nameを破壊的に変更している。("Argument" -> "Argument[1]")
D:\ruby\lib\ruby\1.8\rexml>diff -u element.rb~ element.rb --- element.rb~ 2005-08-12 21:08:47.000000000 +0900 +++ element.rb 2006-06-27 00:36:58.546875000 +0900 @@ -720,7 +720,8 @@ } if results.length > 1 idx = results.index( node ) - rv << "[#{idx+1}]" + rv += "[#{idx+1}]" end end rv
/ItemLookupResponse /ItemLookupResponse/OperationRequest /ItemLookupResponse/OperationRequest/HTTPHeaders /ItemLookupResponse/OperationRequest/RequestId /ItemLookupResponse/OperationRequest/Arguments /ItemLookupResponse/OperationRequest/RequestProcessingTime /ItemLookupResponse/OperationRequest/HTTPHeaders/Header /ItemLookupResponse/OperationRequest/Arguments/Argument[1] /ItemLookupResponse/OperationRequest/Arguments/Argument[2] /ItemLookupResponse/OperationRequest/Arguments/Argument[3] /ItemLookupResponse/OperationRequest/Arguments/Argument[4] /ItemLookupResponse/OperationRequest/Arguments/Argument[5] /ItemLookupResponse/OperationRequest/Arguments/Argument[6] /ItemLookupResponse/OperationRequest/Arguments/Argument[7]
をを、直っている。というわけで上記は ruby-1.8.4に付随する REXML 3.1.3限定の話でした。
def __to_xpath_helper node rv = node.expanded_name.clone if node.parent results = node.parent.find_all {|n| n.kind_of?(REXML::Element) and n.expanded_name == node.expanded_name } if results.length > 1 idx = results.index( node ) rv << "[#{idx+1}]" end end rv end
3.1.3 からの ChangeLog。(そのうち 3.1.4からの ChangeLogになりそう)
間に合わせにこんなのを紛れ込ませる。
if(::REXML::Version < '3.1.4') module ::REXML class Element def __to_xpath_helper node rv = node.expanded_name.clone if node.parent results = node.parent.find_all {|n| n.kind_of?(REXML::Element) and n.expanded_name == node.expanded_name } if results.length > 1 idx = results.index( node ) rv << "[#{idx+1}]" end end rv end end end end
Segmentation faultが起こったり起こらなかったり、起こったとしても(特定のパターンはあるにせよ)違う場所だったりとはっきりしないエラーに困らされた。
原因が create_aggregateで独自に定義した集約関数を使ってるからだということはわかってる。RubyForgeに関連のありそうな投稿を見つけた。
原因はリファレンスが切れて GCに回収されてしまったオブジェクトを参照しようとしてることにある、ということで良いか? 何ともヘタレな回避策は↓。
GC.disable; db.execute(sql); GC.enable
sqlite3-rubyはもうメンテされないのかね。名前付きプレースホルダの問題も解決されないし。
sql = 'SELECT * FROM Books WHERE Title = :title;' db.execute(sql, {'title'=>'惑星をつぐ者'}) #=> no such bind parameter 'title' とかなんとか db.execute(sql, {':title'=>'星を継ぐもの'}) #=>(゜Д゜ )ウマー
bind_parameterのキーに普通はコロンを付けたりしないよね、多分。
UTF-8な文字列を環境変数に設定して読み出すと尻切れ。
C:\Documents and Settings\ds14050\デスクトップ>irb irb(main):001:0> sjis = '高殿 円\' # 『銃姫』を読んでる。 => "\215\202\223a \211~" irb(main):002:0> ENV['hoge'] = sjis => "\215\202\223a \211~" irb(main):003:0> ENV['hoge'] == sjis => true irb(main):004:0> require 'nkf' => true irb(main):005:0> utf8 = NKF::nkf('-w', sjis) => "\351\253\230\346\256\277 \345\206\206" irb(main):006:0> ENV['hoge'] = utf8 => "\351\253\230\346\256\277 \345\206\206" irb(main):007:0> ENV['hoge'] == utf8 => false irb(main):008:0> ENV['hoge'] => "\351\253\230\346\256\277 \345\206"
日本語の PATH_INFOが文字化けするのに閉口してて、Apacheだとか mod_rewriteが悪さをしてるのかと思ってたけど環境変数を経由してたところに問題があったのかも。
文句を言ってても解決しないので REQUEST_URIから SCRIPT_NAME相当部分を取り除いてから URLデコードして自分で PATH_INFOを手に入れる。
ところで URLエンコードされたスラッシュ(%2F)が含まれてた場合、PATH_INFOを参照するだけではその存在がわからないと思うんだけど。やっぱり PATH_INFOって使えない?(<< いやいや PATHと名の付くものにスラッシュやバックスラッシュを含めるのが間違い)