コメント付きのTime Machineにしてみる

Time Machineは、絶大な安心感を与えてくれる。過去1年半の間に、幾度かの復元作業を経験して、確実に過去を復元できることを実感した。ちなみに、Time Machineを利用した復元には3つの方法があって、目的に応じて使い分ける必要がある。

  • ファイルの復元
    • メニューバーのTime MachineアイコンからTime Machineに入って作業する。
    • Finderを開いてTime Machineに入り、復元したいファイルやフォルダを指定する。
    • ITunesiPhotoを開いてTime Machineに入れば、音楽や写真を確認しながら、復元するアイテムを指定することができる。
  • OS環境全体の復元
    • インストールDVD >> ユーティリティ >> システムの復元から実行する。
    • 日時とOSXのバージョン番号がリストされ、過去の任意の時点に、OS環境をまるごと復元する。
  • OS以外の環境の移行
    • /Applications/Utilities/移行アシスタント.app を起動して、現在のユーザ、アプリケーション、各種設定 等をコピーする。

ところで、ファイルの復元をするのでTime Machineに入ると、あの神秘的な宇宙空間に、延々と過去に連なるウィンドウの羅列が表示される。どこでも好きな過去に戻ってくれと言わんばかりに!しかーし、一体、自分はいつの過去に戻りたいのか?

日付と時間は表示される。クイックルックでファイルの内容も確認できる。でも、何らかの設定ファイルだったりすると、すぐに内容を確認できず、日付と時間に頼るしかない。あのセキュリティアップデートはいつだったか、あのアプリケーションのバージョンアップしたのはいつだったか、調べる必要があるかもしれない。そのためにTime Machineを出たり、入ったり、別のファイルを選択して、過去と未来に行ったり来たりしながら、戻りたい日時を特定する。

(まあ、あまり上記のような状況に陥ることはないのだけど)もし、バックアップした時のOSやアプリケーションの状況や、自分のコメントが簡単に確認できれば、より安心・確実にTime Machineを使いこなすことができるはずだ。きっと、それらの情報をテキストファイルに書いて、クイックルックできるようにしておけば、Time Machineの中で的確に戻りたい過去を判断できるだろう。

何を表示するべきか?

インストールされたソフトウェアの一覧

ソフトウェア・アップデートでは、インストールされたソフトウェアの一覧を表示できる。

それが記録されているファイルは、/Library/Receipts/InstallHistory.plist だ。内容を確認して初めて知ったが、ここにはインストーラー.app(/System/Library/CoreServices/Installer.app)がインストールした履歴が保存されているようだ。つまり、インストールパッケージをダブルクリックしてインストールしたソフトウェアは、随時このファイルにインストール履歴が記録されているのだ。

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0">
<array> 
	<dict>
		<key>date</key>
		<date>2009-09-29T23:49:17Z</date>
		<key>displayName</key>
		<string>Sun VirtualBox</string>
		<key>displayVersion</key>
		<string></string>
		<key>packageIdentifiers</key>
		<array>
			<string>org.virtualbox.pkg.vboxkexts</string>
			<string>org.virtualbox.pkg.vboxstartupitems</string>
			<string>org.virtualbox.pkg.virtualbox</string>
			<string>org.virtualbox.pkg.virtualboxcli</string>
		</array>
		<key>processName</key>
		<string>Installer</string>
	</dict>
	<dict>
		<key>date</key>
		<date>2009-10-02T03:57:18Z</date>
		<key>displayName</key>
		<string>キヤノン・プリンタドライバ・アップデート</string>
		<key>displayVersion</key>
		<string>2.1</string>
		<key>packageIdentifiers</key>
		<array>
			<string>com.apple.pkg.Canon_Frameworks</string>
			<string>com.apple.pkg.Canon_PDEs</string>
			<string>com.apple.pkg.MakeQueuesScript</string>
		</array>
		<key>processName</key>
		<string>Software Update</string>
	</dict>
</array>
</plist>
AppFresh.appがインストールした履歴

AppFreshという、ソフトウェアのアップデートを管理するアプリケーションを利用している。仕組みは、CDの曲のタイトルを表示するCDDBと似た感じだ。

  • i use thisというサイトがある。
  • そこにユーザーは、自分がどんなソフトウェアを利用しているか投稿する。
  • 投稿する情報には、最新バージョンや、ダウンロードのURL情報も含まれている。
  • AppFreshは、i use thisに蓄積されたそれらの情報を利用して、最新のアップデート情報を通知して、インストールもしてくれる。
  • そして、AppFresh経由でインストールした履歴は、/Library/Logs/Software Update.log に記録されるのだ。
2009-09-20 21:32:21 +0900: Installed "RightZoom" (1.6)
2009-09-20 21:35:39 +0900: Installed "The Unarchiver" (2.0.2)
2009-09-20 21:37:14 +0900: Installed "AllBookmarks" (3.1.4)
2009-09-20 21:37:26 +0900: Installed "CotEditor" (1.0.1)
2009-09-20 21:39:40 +0900: Installed "VLC" (1.0.1)
2009-09-20 21:42:24 +0900: Installed "Xmarks for Safari" (1.2.1)
2009-09-20 21:43:03 +0900: Installed "Xmarks" (1.0)
自分のコメント

ソフトウェアの中には、ダウンロードしたzipを解凍して、アプリケーションフォルダにドラッグ コピーするだけでインストールが完了してしまうものもある。例えば、Firefox等がその類い。インストールパッケージでもなく、AppFreshも経由しない場合、何も記録が残らなくなってしまう。また、MacBookの環境を判断するヒントになることをメモしておきたいかもしれない。そこで、自分のメモもインストール履歴に含めて自由に書き込めるようにしたい。

AppleScriptとシェルコマンド

以上の情報を一つのファイルにまとめておくと便利だ。テキスト情報として表示するには、AppFreshが生成する Software Update.log のような書式が見易いと思う。その書式に統一したいのだが、悩みどころは InstallHistory.plist を同じ書式に変換する方法。PropertyListというxmlなのだが、各dictのkeyであるdate・displayName・displayVersionに対する値を日付情報と並べて1行で表示したい。
調べてみると、AppleScriptxmlのパース(構文解析)にも軽く対応しているようなので、利用することにしてみた。AppleScriptでSoftware Update.log書式に変換して、その後、do shell scriptからシェルのsortコマンドで、3つのファイルをまとめて、日時をキーに並べ替える。以下のようにしてみた。


----comment_on_TimeMachine----

add_comment() merge_comment_and_install_history()
on add_comment() set datetime to do shell script "date"
set localtime to do_ruby_script({"require 'time'", "Time.parse('" & datetime & "').strftime('%Y-%m-%d %H:%M:%S +0900:')"}) set msg to "コメントを入力してください。"
set res_text to text returned of (display dialog msg default answer "" with icon note) if res_text"" then
set comment to localtime & space & res_text
do shell script "echo " & quoted form of (comment) & " >> ~/_time_machine_comment.txt"
my message("", quoted form of comment) end if
end add_comment

on merge_comment_and_install_history() try
set merge_file_list to "/tmp/install_history.txt", ¬ "/Library/Logs/Software\\ Update.log", ¬ "~/_time_machine_comment.txt"} do shell script "echo " & quoted form of my plist_to_text("/Library/Receipts/InstallHistory.plist") & " > /tmp/install_history.txt"
do shell script "sort -r -k1,2 " & join(merge_file_list, space) & " > /tmp/_time_machine_comment_and_install_history.txt"
--UTF-8のままではクイックルックで表示できなかったので、仕方なくUTF-16に変換した
do shell script "iconv -f UTF-8 -t UTF-16 < /tmp/_time_machine_comment_and_install_history.txt > ~/_time_machine_comment_and_install_history.txt"
on error
do shell script "touch " & join(merge_file_list, space) my message("", "エラーが発生しました。もう一度、実行してみてください。") error -128 --処理を中止するためのエラー
end try
quick_look((path to home folder as text) & "_time_machine_comment_and_install_history.txt") end merge_comment_and_install_history

on plist_to_text(plist_path) tell application "System Events"
tell contents of XML file plist_path
set dictArray to (XML element "plist"'s XML element "array"'s XML elements) end tell
set install_history to {} repeat with dict in dictArray
set dictValue to (dict's XML elements whose name is not "key")'s value
set datetime to dictValue's item 1
set localtime to my do_ruby_script({"require 'time'", "(Time.parse('" & datetime & "') + 9*3600).strftime('%Y-%m-%d %H:%M:%S +0900:')"}) set dictValue's item 1 to localtime
set dictValue to my join(dictValue, space) set dictValue to my replace(dictValue, "missing value", "-") set install_history's end to dictValue
end repeat
--改行コードによるsortコマンドの動作状況: \n...sort_OK, return...sort_NG, \r...sort_NG
my join(install_history, "\n") end tell
end plist_to_text

on quick_look(file_path) tell application "Finder" to activate
tell application "Finder" to select file file_path
tell application "System Events" to keystroke space
end quick_look






--rubyコードを実行して結果を返す
--do_ruby_script({"require 'uri'", "URI.escape(%q|" & "tell application \"System Events\" --ショートカット操作をする限り" & "|)"})
--バックスラッシュのみ,エスケープ\\が必要、それ以外はRubyコードの書き方と同じ
on do_ruby_script(ruby_code) set ruby_code to ruby_code as list
set last_code to ruby_code's last item
set puts_last_code to "puts(" & last_code & ")"
if (count of ruby_code) ≥ 2 then
set pre_code to join(ruby_code's items 1 thru -2, ";") & ";"
else
set pre_code to ""
end if
set shell_code to "ruby -e " & quoted form of (pre_code & puts_last_code) log shell_code
do shell script shell_code
end do_ruby_script

--rubyコードを require 'jcode'; $KCODE='u'; な日本語環境で実行して結果を返す
on do_ruby_jcode_u(ruby_code) do_ruby_script({"require 'jcode'", "$KCODE='u'"} & ruby_code) end do_ruby_jcode_u

--sourceTextdelimiterでリストに変換する
--split("1,2,3,4", ",")
-- 結果:{"1", "2", "3", "4"}
--AppleScript2.0では、«constant conszkhk»などの拡張属性はサポートしない。
--http://www.seuzo.jp/st/Other/AS2.0.html
on split(sourceText, delimiter) --considering «constant conszkhk»
if sourceText = "" then return {} set oldDelimiters to AppleScript's text item delimiters
set AppleScript's text item delimiters to {delimiter} set theList to text items of sourceText
set AppleScript's text item delimiters to oldDelimiters
return theList
--end considering
end split

--sourceListdelimiterで区切ったテキストに変換する
--join({"1", "2", "3", "4"}, ",")
-- 結果:"1,2,3,4"
--join({{1, 2}, {3, 4}}, ",")
-- 結果:"1,2,3,4"
--AppleScript2.0では、«constant conszkhk»などの拡張属性はサポートしない。
--http://www.seuzo.jp/st/Other/AS2.0.html
on join(sourceList, delimiter) --considering «constant conszkhk»
set oldDelimiters to AppleScript's text item delimiters
set AppleScript's text item delimiters to {delimiter} set theText to sourceList as text
set AppleScript's text item delimiters to oldDelimiters
return theText
--end considering
end join

--sourceText中の全てのtext1text2に置き換える
--replace("abcdefg", "bc", "_bc_")
-- 結果:"a_bc_defg"
on replace(sourceText, text1, text2) join(split(sourceText, text1), text2) end replace

--growlまたはdisplay dialogでメッセージを表示する。
on message(title, msg) try
do shell script "/usr/local/bin/growlnotify " & title & " -m " & quoted form of msg
on error
activate
display alert msg giving up after 1
end try
end message

運用

  • コメントは入力しなくてもOK。入力すれば、~/_time_machine_comment.txt に追記される。
  • 処理が完了すれば、_time_machine_comment_and_install_history.txt の内容がクイックルックで表示される。


以上で、ホームフォルダ直下には、二つのファイルが生成されている。

  • _time_machine_comment_and_install_history.txt
    • 以下3つのファイルをまとめたインストール履歴。Time Machineでクイックルックして活用する。
    • /Library/Receipts/InstallHistory.plist
    • /Library/Logs/Software Update.log
    • ~/_time_machine_comment.txt


これで、Time Machineのコメントもどきファイル、一応完成!ソフトウェアをインストールした時、あるいはTime Machineのバックアップの前に、コメント入力を心掛ける。そうすれば、ソフトウェアのインストール状態を把握するコメント履歴となるのだ。