帝力何有於我哉

無職。汎用人型雑用兵器。型式番号: 高木 宏 ( Hiroshi Takagi )

Becky!2 → ThunderBird3 : mbox 構造調整スクリプト

Farewell to Becky!

長年使っているBecky!2がついに破綻して来た。

だいたい、機器の障害監視用のalert/notify メールはがんがん飛んでくるわ、お客さんは平気で数十Mbyteクラスのファイルを添付してMLにpost してくるわ、な環境では、Becky!のメッセージ管理のしくみでは、もう持たないのはBecky!のせいではない。
わたしのBecky!のフォルダは総計40GB以上、ひとつのフォルダに一万通以上のメッセージがあることもざらなのだから。

どうせ引っ越すなら、Windows とおさらばできるようにThunderBird 3 へ、ということでこの一週間、少しずつ引越しの準備を進めてきた。
なんとか簡単にできるようになったので、同じようなことを考えている人のために、移行方法のメモとスクリプトを残しておく。

How to export from Becky! and How to import to Thundirbird : research

ぐぐってみると意外にこの手の話題が少ない。

  1. http://smileart.sakura.ne.jp/wordpress/archives/45.html
  2. input.html

どちらも、CircleBeckyというBecky!プラグインで、(たいていの人はそうであろう)階層構造化しているBecky!のメールフォルダとメッセージを再帰的にエクスポートしている。違いは、

  1. は、eml 形式でエクスポート(1メッセージ1ファイル)
  2. は、mbox 形式でエクスポート

しているところにある。
さらに 1. は、Thunderbird 側にImportExportToolsという拡張機能を入れて、フォルダ階層にエクスポートされた eml ファイルをフォルダ階層を再現しつつ取り込んでいる。

ひとことでいうと、

  1. は、操作はとても簡単で、上のリンクにあるとおりの手順で終わる。ただし、メッセージがたくさんある人は、エクスポートもインポートもとんでもなく時間がかかる。
  2. は、エクスポートが早く。インポートの操作はない(フォルダにほうり込むだけ)が、Becky!がエクスポートするmbox のフォルダ階層とThunderbird のそれが違うことから、フォルダ構造とパス名の調整が手作業で必要になる。

フォルダあたりに万単位のメッセージをかかえていた私には、1.の方法は中規模のフォルダてちょっと時間を計測してみて使い物にならない、とあきらめた。小規模ののテストフォルダなら、とても楽なんだが。

また、仕事がら一日数千のメールと戦うために数百に達しているフォルダの階層構造は自分でも覚ええていないくらいの深い深い密林と化しているので 2. の「手作業によるmboxの調整」も現実性がない。

というわけで、エクスポートがとても早い 2. の移行戦略を取り、「手作業」の部分を ruby にやらせることにした。

How to convert mboxes exported from Becky! into Thunderbirds' style

2. のmboxの変換の部分はとてもよくまとまっているので、ちょっと無断転載。

Thunderbirdではファイル自体がひとつのメールフォルダとして認識されてしまうので、CircleBeckyからの出力をそのまま置くと、[元のフォルダ名].mbox という名前のフォルダとして見えてしまう。さらに悪いことには、サブフォルダは[サブフォルダ名].sbd という名前のディレクトリを作っておかないと認識されないので、CircleBeckyの出力をそのまま置くと、サブフォルダはまったく認識されないということになってしまう。

という訳で、CircleBeckyから出力されたファイルの位置とファイル名、ディレクトリ名を変更しなければならない。手順は以下のとおり。

1. [メールフォルダ名].mbox という名前のファイルをすべて一つ上のディレクトリに移動する。
2. 空になったディレクトリはすべて削除する。
3. メールフォルダの名前が付いたディレクトリ、サブディレクトリの名前をすべて [元の名前].sbd に変更する。
4. [メールフォルダ名].mbox というファイルの名前から .mbox という拡張子を削除する。

実は、rayman さんが、まとめている手順はひとつ抜けている点がある。
MUAをつかっていると、複数のフォルダをかかえるためだけのメッセージを持たないフォルダができる。この場合がうまくいかない。

例えば、

customers

    • customer_name1
    • customer_name2
    • customer_name3

という構成のBecky!フォルダがあり、customers には何もメッセージはなく、customer_name[1-3]には、実際の顧客のメッセージがある場合、まず Circle Becky は、以下のファイルをエクスポートする。

customers <-- directory

    • customer_name1 <-- directory
        • customer_name1.mbox <-- mbox file
    • customer_name2 <-- directory
        • customer_name2.mbox <-- mbox file
    • customer_name3 <-- directory
        • customer_name3.mbox <-- mbox file

これを上の手順で変換すると、

customers.sbd <-- directory
customer_name1 <-- mbox file
customer_name2 <-- mbox file
csutomer_name3 <-- mbox file

ができるが、これでは Thunderbird は、customer フォルダの存在を認識してくれない。同じディレクトリに、

customers

という mbox ファイル(空でよい)が必要になる。

上の手順にこの空のmbox を作る(touchする)ロジックを加えて、変換用のスクリプトを作成した。

ruby script for convert

以下は、変換にやっつけで作った、cvt.rb というスクリプト
例によってテストもエラー処理もぜんぜんやってない(すみません。)

$KCODE = "SJIS"

require 'jcode'
require 'fileutils'

def cvtB2mboxdir2thunderbird(t_dir)
  FileUtils.cd(t_dir)
  puts FileUtils.pwd

  subdir_found = false
  folder_contains_only_folders = true
  mbox_list = Array.new

  Dir.glob("*").each do |path|
    if /^(.+)\.mbox$/ =~ path
      FileUtils.mv(path, "../")
      mbox_list << path
      puts path

      if $1 == File.basename(t_dir)
        folder_contains_only_folders = false
      end
    end

    if FileTest.directory?(path)
      subdir_found = true
      cvtB2mboxdir2thunderbird(path)
    end
  end

  FileUtils.cd("..")
  puts FileUtils.pwd

  unless  subdir_found
    FileUtils.rmdir(t_dir)
  else
    FileUtils.mv(t_dir, "#{t_dir}.sbd")
  end

  mbox_list.each do |mbox_file|
    basename = File.basename(mbox_file, ".mbox")
    FileUtils.mv(mbox_file, basename)
  end

  if folder_contains_only_folders
    FileUtils.touch(File.basename(t_dir))
  end

  puts
end

cvtB2mboxdir2thunderbird(ARGV[0])

あっちこっちに、FileUtils.pwd を出力しているのは、どこまで変換が進んだかを見てないと退屈なのと、再帰的なロジックは昔から苦手なので、ようすをみたいから。

CircleBecky で mbox エクスポートしたフォルダをコマンドラインで指定して、

ruby -Ks cvt.rb C:\becky_exported\folder_top

で、ok。
あとは、変換された mbox ファイルとフォルダ群をThundirBird のデータフォルダに入れるだけ。

eml エクスポートの数十分の一の時間で移行できるので、仕事の合間に少しずつ、フォルダを移行できた。

このスクリプトを書いてみて、はじめて、FileUtils に touch なんてメソッドがあるのを知った。でも、FileUtils ってクラス名はいまいちいけてないなぁ、と思いません?

あとは、振り分けルールの変換かなぁ〜。変換するより、msgFilterRules.dat を簡単に生成するクラスでも書いておいて、コマンドラインから同形式のルールをばんばん作るほうが楽なような気がしてきたな。

Re: 【だいたいあってる時計】乱数の代わりに配列をつかってみたよ

kondoyuko さんのRuby修行のフォローです

id:kondoyuko:20110111:1294754578 で書かれているプログラムへのコメントです。
(はてなDiary のコメント内にはてな記法を使う方法がようわからんかった orz

作ろうとされている辻褄時計の目指しているものがいまいちつかめていない(というか経緯を知らない)ので、プログラムのお作法的なところでいくつかアドバイスを。

オブジェクト名と collect メソッド

ary = []
for i in 0..20
  ary << 0.1 * i
end

まず、ary っていうオブジェクト名(変数名)は、"配列"ということを意味しているんだろうけど、この場合は、"時計がどれだけ狂う(drift)するかの秒数のテーブル"ということのようだから、clock_drifts とでもしてみましょうか。
プログラムでは、オブジェクト名、メソッド名にわかりやすいものをつけて行く習慣がとてもだいじです。ima の方はとてもわかりやすいw

次に 0..20 という範囲オブジェクト(Rangeクラスのオブジェクト)は、Enumerableモジュールをインクルードしているので、配列オブジェクトと同じように collect という便利なイテレータメソッドが使えます。
以下、irb で示します。

irb(main):021:0> (0..5)
=> 0..5
irb(main):022:0> (0..5).class
=> Range
irb(main):023:0> (0..5).to_a
=> [0, 1, 2, 3, 4, 5]
irb(main):024:0> (0..5).collect{|i| i* 0.1 }
=> [0.0, 0.1, 0.2, 0.3, 0.4, 0.5]
irb(main):025:0> a = (0..5).collect{|i| i* 0.1 }
=> [0.0, 0.1, 0.2, 0.3, 0.4, 0.5]
irb(main):026:0> a
=> [0.0, 0.1, 0.2, 0.3, 0.4, 0.5]
irb(main):027:0> a.class
=> Array

つまり、clock_drifts は、以下のように一行で定義できます。

clock_drifts = (0..20).collect{|i| i* 0.1 }

ちょっとクラスプログラミング風に

わたしが同じものを書くとたぶんこうなるだろうというものを。
loop 周りはちょっと動作を変えました start_ticking すると、
すぐに現在時刻を表示して無限ループに入ります。

class TsujitsumaClock
  CLOCK_DRIFTS = (0..20).collect{|i| i* 0.1 }

  def self.sleep_random
    sleep(CLOCK_DRIFTS.choice)
  end

  def self.start_ticking
    ima = Time.now
    loop do
      puts ima
      ima += 1
      self.sleep_random
    end
  end
end

TsujitsumaClock.start_ticking

ちょっとしたロジックでも名前をつけてメソッドにする習慣をつけておくと、見通しがよくなり、テストや評価も楽です。
> 結果、よく解らん!!!あんまり変わんないような気がする。
と言うてはった、点も sleep_random というクラスメソッドの中だけを書き換えて、いろいろ統計を取ってみる・テストしてみればいいわけで。

で、統計を取って評価するプログラムも書いてみようかと思ったけど、もう、夜も遅いので今日はここまで。

2011年が連続した素数の合計であることはすごい!か?

2011年と素数

ツィッターで、2011年とは、次の11個の連続した素数の合計である。
2011 = 157 + 163 + 167 + 173 + 179 + 181 + 191 + 193 + 197 + 199 + 211
という話しを聞いた。

2011年は「セクシー素数」の年
http://wiredvision.jp/news/201101/2011010517.html

「すごい」は「美しい」か「めったにない」か

素数フリークな方には、「連続した素数の合計」というだけで、美しくてエクスタシーを感じてしまうものかもしれないが、一般人?な自分としては、どれだけ「めったにないか」、つまり「連続した素数の合計」である数(年号)は、AD1年から2011年までどれだけあったかをRubyで洗い出してみるか、ということに興味が出た。

要件定義

  • 1 〜 2011 について調べる
  • 二つ以上の連続した素数の合計である場合を選び出す

必要なもの

実装

めちゃくちゃべたべたやなあ〜。まあ、動けばいいや、という感じで。is_sum_of_contiguous_primes? は、テストを書いていないので、ちょとあやし〜。

class Fixnum
  def is_prime?
    return false if self == 1
    2.upto(self -1) do |i|
      if self % i == 0
        return false
      end
    end
    return true
  end

  def has_primes_lt_self
    primes = Array.new
    2.upto(self) do |i|
      if i.is_prime?
        primes << i
      end
    end
    primes
  end

  def is_sum_of_contiguous_primes?
    primes = self.has_primes_lt_self

    while primes.size > 0
      primes_for_sum = Array.new
      primes.each do |p|
        primes_for_sum << p
        sum = primes_for_sum.inject(:+)
        break if sum > self
        if sum == self && primes_for_sum.size > 1
          return primes_for_sum
        end
      end
      primes.shift
    end
    return false
  end
end

で、すごかったのか?

このメソッドを使って、以下のプログラムを書いた。タブ区切りで出力しているのは、リダイレクトした出力をExcel で眺めたから。

(2..2011).each do |year|
  if primes = year.is_sum_of_contiguous_primes?
    printf("%d\t", year)
    primes.each do |i|
      printf("%d\t", i)
    end
    puts("\n")
  end
end

結果、2年から2011年の間に、連続した素数の合計であった年号は、790個あった。40%近い出現確率だから、めずらしいとは言えないかも。最近の20年では他に、1993, 1994, 1995, 1998, 1999, 2002, 2006 がある。
特に1998年は、

1998 = 31 + 37 + 41 + 43 + 47 + 53 + 59 + 61 + 67 + 71 + 73 + 79 + 83 + 89 + 97 + 101 + 103 + 107 + 109 + 113 + 127 + 131 + 137 + 139

と24個の連続した素数の和になっている。これはすごいなぁ〜(笑

Ruby で指定範囲の実数をランダムに返すメソッドを作る

kondoyuko さんとtwitter で表題の件を話していて、tweetでは説明しづらいので、こちらに。

def x_rand(from, to)
  return from.to_f + rand * (to.to_f - from.to_f)
end

続いて、こんなプログラムを追加して、ばらつきぐあいを見てみた。

randomized_values = Array.new

1000.times do
  r = x_rand(-1, 1)
  puts r
  randomized_values << r
end

puts "-" * 20
puts randomized_values.inject(:+)
puts randomized_values.inject(:+) / randomized_values.size
puts randomized_values.max
puts randomized_values.min

いかん、合計の計算に inject(:+) なんて使ってる(汗
初心者向きではないな〜

ま、いっか。

月龍/老祥記

1月3日、奥さんが観たいという「相棒-劇場版II-」に行く前に、元町の本館牡丹園で昼ごはんを食べた。ごはんの後、奥さんは、南京町で雑貨の店をぶらぶら。
私は豚まんの店の老祥記の隣にある直営雑貨店の月龍で、携帯電話用ストラップを二つ買う。

IS03の赤いジャケットにはなかなか似合っているんではないかと、自己満足。
もうひとつのこちらのタイプは奥さんにあげた。

http://item.rakuten.co.jp/mslife/21-010/