« 最新版GSPlayer v2.25(GSPlayer2)のビルド方法 | Main | デジカメRAWファイル現像コマンドdcraw 8.37とliblcms 1.15のZaurus向けパッケージ »

2006.09.11

ZaurusでCronInetとMewによるメールの定期巡回

CronInetという、電源を落としていても定期的にネットにつないでコマンドを実行してくれる便利なZaurus用スクリプトがある。モバイルの可能性を広げてくれる、素晴らしいものだ。

fetchmailと連携して使う例しか紹介されてないけど、fetchmailはモバイル向きじゃない。理由はメールのサイズの閾値を設定できても、はじくことしかできない(ようだ)からだ。
はじかれたメールの存在をMUAから簡単に知ることはできない。
(いや、手はいくらでもあるだろうけど、めんどくさい)
だからといって、10MBとか普通に添付してくるメールを受け取るのも現実的でない。
添付を落とすような転送をかけて、ワンクッション置けばいいんだけどね。

モバイル的には、メールの先頭だけ受信して、後で用があらば選択受信したい。
検索という鬼門はあるものの、モバイルにおけるMUAは、それができるMewが最有力。

Mewの弱点は、Emacsの中で動くという性質上、外部からのキックでメールを受信させることが難しいことだ。

ゆえに、CronInetなどというヨダレもののスクリプトがあっても眺めているしか無かった。しかし、ElispやEmacsへの理解が進んで壁を越えることができたので書いておこう。

実現のキーはemacsclient

実現のキーになるのは、emacsclient。emacsclientは、起動中のEmacsにTCP/IP経由で引数を渡すことができるコマンドだ。

emacsclientを使うには、.emacs.elに

(server-start)

と書いておく。emacsclientは-eオプションでEmacsにElispを評価させることができる。これでMewのメール受信関数をキックしてやる、というのがアイデアの骨格になる。つまり、Emacsの外からメール受信を開始させられるのだ。

以下に紹介するスクリプト「mew-cron-retrieve.sh」をfetchmailの代わりにCronInetで定期実行してやると、Mewでメールの送受信ができる。

Emacs側の準備

以下を.emacs.el等に追加する。

;;emacsclientを使えるようにする
(server-start)

;;mew-cron-retrieve
(defun mew-cron-retrieve()
""
(interactive)
(defvar mew-cron-retrieve nil)
(defvar mew-cron-busy nil)
(if mew-cron-retrieve
(progn
(setq mew-cron-busy t)
(mew-summary-retrieve)
)
nil)
)

(add-hook 'mew-init-hook
(function
(lambda ()
(setq mew-cron-retrieve t)
)))

(add-hook 'mew-quit-hook
(function
(lambda ()
(setq mew-cron-retrieve nil)
)))

(add-hook 'mew-pop-sentinel-hook
(function
(lambda ()
(setq mew-cron-busy nil)
)))

(add-hook 'mew-send-hook
(function
(lambda ()
(setq mew-cron-busy t)
)))

(add-hook 'mew-smtp-flush-hook
(function
(lambda ()
(setq mew-cron-busy t)
)))

(add-hook 'mew-smtp-sentinel-hook
(function
(lambda ()
(setq mew-cron-busy nil)
)))

Mewのメール送受信は非同期処理なので、メール送受信を指示しても、送受信終了を待たずに制御が戻ってしまう。そのまま事後処理に入ってメールの送受信中にPPPを切断してしまうことが無いよう、add-hookして、処理中を示すフラグを上げ下げしている。
pop開始時のhookが無かったのでhookを追加しようとしたが、これはemacsclientからのキック時にやってやればいいことなので、run-hook追加はやめた。

CronInetからキックするシェルスクリプト

自分はmew-cron-retrieve.shという名前にしている。
これをCronInetでキックする。

#!/bin/sh

export LD_LIBRARY_PATH=/home/QtPalmtop/lib
export QPEGROUP=qpe
export QTDIR=/home/QtPalmtop
export QPEDIR=/home/QtPalmtop
export QPEUSER=zaurus

logfile="/home/zaurus/mew-kicked-log.txt"
ps x | grep -e ' emac[s]' >/dev/null
if [ $? ] ; then
echo "emacs found."
emacsclient -e '()' >/dev/null 2>&1
if [ "X$?" = "X0" ] ; then
echo "emacs server found."
emacsclient -e '(mew-cron-retrieve)'
echo "checking..."
while [ "X`emacsclient -e 'mew-cron-busy'`" = "Xt" ]; do
sleep 1
# debug print
# emacsclient -e 'mew-cron-busy'
done
echo "done."
echo "kicked at `date '+%Y-%m-%d %H:%M:%S'`" >> $logfile
echo "" | sudo ntpdate eric.nc.u-tokyo.ac.jp >> $logfile
fi
fi

sleep 5
while ifconfig ppp0 >/dev/null 2>&1; do
netctl -d >/dev/null 2>&1
sleep 5
done

Emacs内の変数mew-cron-busyを見て、それがtの間は「待ち」としている。
CronInet側での切断がうまく動かないので、このスクリプト内で切断までやってしまっている。
ログを取ったり、時刻合わせをしたりは余計かもしれないので、要らない人は削除してね。

少々不安定なことがあるものの、勝手にスリープから起きてPPP接続、メール巡回、ntpdateによる時刻合わせ、PPP切断、スリープ移行、という一連の動作ができ、常に最新のメールがMewのInboxに入っているので、Zaurusの利用価値が倍増した。

Mewの受信件数読み上げ

ついでに受信件数読み上げというのもやってみた。
自分は音声合成が大好きだ。ボコーダーとかも好き。
だから、やらねばなるまい。

zaurusで動作実績のある音声合成ソフトが二つある。
一つはflite。もう一つはMBROLA。

↓flite
http://www.speech.cs.cmu.edu/flite/download.html
↓MBROLA本体(音素ファイルとかは別途必要)
http://tcts.fpms.ac.be/synthesis/mbrola/bin/pocketlinux/mbr301h.zip

後者は日本語まで喋れる上に品質が高いものの、専用の発音表記のマクロを書かなければならず、お手軽じゃない。前段にhuman readableなテキストからマクロを生成する別のソフトが必要。また、再生には生成したデータをsoxで変換し、/dev/dspに流し込んでやる必要があるなど、何かと面倒。それにちょっと重い。

一方のfliteは扱いがとても簡単。

flite -t "Speech test."

これだけ。
というわけで、fliteを使ってMewで受信したメールの件数を読み上げる、というのをやってみた。整数を引数に取り、読み上げる関数を書いた。これを.emacs.elとかに書いておく。
1通以上だと"mails"になるという小技入り。

(defun read-off-number-of-retrieved-mail (n)
""
(interactive)
(cond ((= n 1)
(setq read-off-number-of-retrieved-mail-postfix " mail.\""))
((> n 1)
(setq read-off-number-of-retrieved-mail-postfix " mails.\""))
(t
(setq read-off-number-of-retrieved-mail-postfix " mail.\""))
)
(call-process-shell-command
(concat
"flite -t \"You got "
(int-to-string n)
read-off-number-of-retrieved-mail-postfix
))
)

でもって、mew-pop.elのmew-pop-sentinelの中に適宜この関数を挿入する。
書き換えたのが以下(Mew 4.2.52)。
僕は書き換えたので上書きしてしまった。
rttlという変数に、受信件数が整数型で入っている。

(defun mew-pop-sentinel (process event)
(let* ((pnm (process-name process))
(directive (mew-pop-get-directive pnm))
(mdb (mew-pop-get-mdb pnm))
(sshpro (mew-pop-get-ssh-process pnm))
(sslpro (mew-pop-get-ssl-process pnm))
(rttl (mew-pop-get-rttl pnm))
(dttl (mew-pop-get-dttl pnm))
(left (mew-pop-get-left pnm))
(bnm (or (mew-pop-get-bnm pnm) (current-buffer)))
(flush (mew-pop-get-flush pnm))
(kils (mew-pop-get-kils pnm))
(hlds (mew-pop-get-hlds pnm))
(uidl (mew-pop-get-uidl pnm))
(done (mew-pop-get-done pnm))
(error (mew-pop-get-error pnm))
(file (mew-expand-folder bnm mew-pop-msgid-file))
(buf (process-buffer process))
(thread-info (mew-pop-get-thread-info pnm)))
(mew-pop-debug "POP SENTINEL" event)
(set-process-buffer process nil)
(set-buffer bnm)
(mew-summary-mark-recover mdb)
(mew-remove-buffer buf)
(if (not done)
(let* ((rtrs (mew-pop-get-rtrs pnm))
(lefts (length rtrs))
(uid (nth 1 (car rtrs)))
recovered)
(mew-pop-message pnm "POP connection is lost")
(when (mew-pop-get-dispatched pnm)
(cond
((eq directive 'scan)
;; uidl is reversed.
(setq uidl (cdr (member uid uidl)))
(mew-lisp-save file uidl nil 'unlimit)
(setq recovered t))
((eq directive 'inc)
;; uidl is reversed.
(setq uid (assoc uid uidl))
(setq uidl (cdr (member uid uidl)))
(mew-net-uidl-db-set (mew-pop-passtag pnm) uidl)
(setq recovered t)))
(when recovered
(mew-pop-message
pnm
"%d message retrieved. %d messages are left due to an error"
(- rttl lefts) lefts)
(mew-summary-folder-cache-save))))
(if thread-info (mew-summary-retrieve-message-for-thread thread-info))
(cond
(error
;; retain the error message
)
((eq directive 'biff)
(funcall mew-biff-function rttl))
((eq directive 'sync)
(mew-pop-message pnm "Synchronizing messages...")
(mew-net-folder-sync bnm hlds)
(mew-pop-message pnm "Synchronizing messages...done"))
((eq directive 'inc)
(mew-biff-clear)
(mew-net-uidl-db-set (mew-pop-passtag pnm) uidl)
(cond
((= rttl 0)
(mew-pop-message2 pnm "No new messages" left))
((= rttl 1)
(mew-pop-message2 pnm "1 message retrieved" left)
(read-off-number-of-retrieved-mail rttl)
(mew-summary-folder-cache-save))
(t
(mew-pop-message2 pnm (format "%d messages retrieved" rttl) left)
(read-off-number-of-retrieved-mail rttl)
(mew-summary-folder-cache-save))))
((eq directive 'get)
(cond
((= rttl 0)
(mew-pop-message2 pnm "No new messages" left))
((= rttl 1)
(mew-pop-message2 pnm "1 message retrieved" left)
(read-off-number-of-retrieved-mail rttl)
(mew-summary-folder-cache-save))
(t
(mew-pop-message2 pnm (format "%d messages retrieved" rttl) left)
(read-off-number-of-retrieved-mail rttl)
(mew-summary-folder-cache-save))))
((eq directive 'scan)
(mew-biff-clear)
(cond
((= rttl 0)
(mew-pop-message pnm "No messages scanned"))
((= rttl 1)
(mew-pop-message pnm "1 message scanned")
(mew-lisp-save file uidl nil 'unlimit)
(mew-summary-folder-cache-save))
(t
(mew-pop-message pnm "%d messages scanned" rttl)
(mew-lisp-save file uidl nil 'unlimit)
(mew-summary-folder-cache-save))))
((eq directive 'exec)
(when kils
(mew-mark-exec-unlink bnm kils)
(mew-mark-kill-invisible)
(mew-summary-folder-cache-save))
(cond
((= dttl 0)
(mew-pop-message pnm "No messages deleted"))
((= dttl 1)
(mew-pop-message pnm "1 message deleted"))
(t
(mew-pop-message pnm "%d messages deleted" dttl))))))
;;
(mew-net-status-clear (mew-pop-get-status-buf pnm))
(mew-info-clean-up pnm)
(set-buffer-modified-p nil)
(mew-summary-unlock)
(if (and (processp sshpro) (not mew-ssh-keep-connection))
(process-send-string sshpro "exit\n"))
(if (and (processp sslpro) (not mew-ssl-keep-connection))
(delete-process sslpro))
(unless (eq directive 'biff)
(run-hooks 'mew-pop-sentinel-non-biff-hook))
(run-hooks 'mew-pop-sentinel-hook)
(when (and mew-auto-flush-queue flush)
(mew-smtp-flush-queue mew-case))))

これでメールを自動巡回して、メールがあったら受信件数読み上げ、というアレゲな環境が完成。
本当は、音楽を聞いているところにメールが届いたら、一時的に音楽再生を中止して、音声合成でお知らせ、読み上げが終わったら音楽再生再開、というのをやろうと思ったけど、標準搭載のプレイヤーはリモコンできない。madplayやmpg123なんかにシグナル送って一時停止ってのはできないだろうか。
MPDというのがあって、これをZaurusで動かしている人も居るみたいだけど、Mac OS Xで動かしてみたら負荷がかかると音が途切れ途切れ。Zaurusで大丈夫なのかな。

|

« 最新版GSPlayer v2.25(GSPlayer2)のビルド方法 | Main | デジカメRAWファイル現像コマンドdcraw 8.37とliblcms 1.15のZaurus向けパッケージ »