iOS7のユーザ辞書をリセットするスクリプト
前回までに自分のiPhoneとMacBookのユーザ辞書は同期するようになったのだけど、その手順はちょっと複雑だった。再び同期できなくなった時のことを考えて、素早く、安全に、iPhoneのユーザ辞書をリセットするスクリプトにしておこうと、思い立った。
mbdbを編集するRubyコード(reset_keyboard.rb)
- いちいちテキストに書き出さずとも、mbdbの内容を取捨選択できるように改良した。
- 「mbdb.reject!(/HomeDomain::Library\/Keyboard/)」によって、キーボードに関連する設定ファイルを一括削除している。
# encoding: ASCII-8BIT class Mbdb def initialize(mbdb_filename, verbose = false) @verbose = verbose process_mbdb_file(mbdb_filename) end def to_text_file(filename) File.open(filename, 'w') do |f| @mbdb.each do |r| f.puts r.values.inspect end end end def to_mbdb_file(filename) File.open(filename, 'wb') do |f| f.write(binary_data) end end def reject!(regexp) @mbdb.reject! {|r| "#{r[:domain]}::#{r[:filename]}" =~ regexp} end def fill!(regexp) @mbdb.reject! {|r| !("#{r[:domain]}::#{r[:filename]}" =~ regexp)} end private # Return an integer (big-endian) and new offset from the current offset def get_int(data, offset, intsize) value = 0 while intsize > 0 value = (value<<8) + data[offset].ord offset += 1 intsize -= 1 end return value, offset end # Return a string and new offset from the current offset into the data def get_string(data, offset) return 'ffff', offset + 2 if data[offset] == 0xFF.chr and data[offset + 1] == 0xFF.chr # Blank string length, offset = get_int(data, offset, 2) # 2-byte length value = data[offset...(offset + length)] value = '0000' if value == "" return value, (offset + length) end def put_int(data_10, intsize) data_16 = "%0#{intsize*2}x" % data_10 [data_16].pack('H*') end def put_string(str) return "\xff\xff" if str == 'ffff' return "\x00\x00" if str == '0000' return [str.length].pack('n') + str end def process_mbdb_file(filename) @mbdb = Array.new data = File.open(filename, 'rb') { |f| f.read } puts "MBDB file read. Size: #{data.size}" raise 'This does not look like an MBDB file' if data[0...4] != 'mbdb' offset = 4 offset += 2 # value x05 x00, not sure what this is while offset < data.size fileinfo = {} fileinfo[:domain], offset = get_string(data, offset) fileinfo[:filename], offset = get_string(data, offset) fileinfo[:linktarget], offset = get_string(data, offset) fileinfo[:datahash], offset = get_string(data, offset) fileinfo[:unknown1], offset = get_string(data, offset) fileinfo[:mode], offset = get_int(data, offset, 2) fileinfo[:unknown2], offset = get_int(data, offset, 4) fileinfo[:unknown3], offset = get_int(data, offset, 4) fileinfo[:userid], offset = get_int(data, offset, 4) fileinfo[:groupid], offset = get_int(data, offset, 4) fileinfo[:mtime], offset = get_int(data, offset, 4) fileinfo[:atime], offset = get_int(data, offset, 4) fileinfo[:ctime], offset = get_int(data, offset, 4) fileinfo[:filelen], offset = get_int(data, offset, 8) fileinfo[:flag], offset = get_int(data, offset, 1) fileinfo[:propertynum], offset = get_int(data, offset, 1) fileinfo[:properties] = {} (0...(fileinfo[:propertynum])).each do |i| propname, offset = get_string(data, offset) propval, offset = get_string(data, offset) fileinfo[:properties][propname] = propval end @mbdb << fileinfo end @mbdb end def binary_data bin = "mbdb\x05\x00" @mbdb.each do |h| bin << put_string(h[:domain]) bin << put_string(h[:filename]) bin << put_string(h[:linktarget]) bin << put_string(h[:datahash]) bin << put_string(h[:unknown1]) bin << put_int(h[:mode], 2) bin << put_int(h[:unknown2], 4) bin << put_int(h[:unknown3], 4) bin << put_int(h[:userid], 4) bin << put_int(h[:groupid], 4) bin << put_int(h[:mtime], 4) bin << put_int(h[:atime], 4) bin << put_int(h[:ctime], 4) bin << put_int(h[:filelen], 8) bin << put_int(h[:flag], 1) bin << put_int(h[:propertynum], 1) h[:properties].each do |k, v| bin << put_string(k) bin << put_string(v) end end bin end end if RUBY_VERSION >= "1.9" then `mv '#{ARGV[0]}' '#{ARGV[0]}.back'` mbdb = Mbdb.new("#{ARGV[0]}.back", true) mbdb.reject!(/HomeDomain::Library\/Keyboard/) # Reset UserDictionary mbdb.to_text_file("#{ARGV[0]}.txt") mbdb.to_mbdb_file(ARGV[0]) else puts 'Needs Ruby version 1.9 or later' end
バックアップを保護するシェルスクリプト(reset_keyboard.sh)
- 上記Rubyコードがmbdbを修正する前に、バックアップをコピーして、安全に作業する環境を整える。
#!/bin/sh current_dir=`dirname $0` target_dir="$1" reset_dir="${target_dir}-reset-user-dictionary" rm -fr "$reset_dir"; cp -r "$target_dir" "$reset_dir" display_name=`defaults read "${reset_dir}/Info.plist" "Display Name"` defaults write "${reset_dir}/Info.plist" "Display Name" -string "${display_name}-reset-user-dictionary" ruby "${current_dir}/reset_keyboard.rb" "${reset_dir}/Manifest.mbdb"
バックアップリストを返すシェルスクリプト(current_backup_list.sh)
- iPhoneのバックアップは、端末固有のUniqueDeviceIDという英数字が羅列したフォルダに存在する。
- リセットする端末を指定する時、英数字の羅列では扱いにくいので、端末名を関連づけたリストを返す。
#!/bin/sh backup_folders=`find "$HOME/Library/Application Support/MobileSync/Backup" -name Info.plist -exec dirname {} \;` IFS=$'\n' for f in $backup_folders do deviceID=`defaults read "$f/Manifest.plist" 'Lockdown'|grep UniqueDeviceID|awk '{print $3}'|tr -d ';'` folder_name=`basename "$f"` if [ "$deviceID" = "$folder_name" ]; then echo "`defaults read "$f/Info.plist" 'Display Name'` :: $folder_name" fi done
AppleScriptアプリケーションによるGUI(reset_iOS7_UserDictionary.app)
- 以上のスクリプトを、AppleScriptアプリケーションとしてまとめてみた。
- iOS端末を指定すると、上記スクリプトが連携して、ユーザ辞書をリセットしたバックアップを生成するのだ。
activate
set current_backup_list_sh to (path to resource "current_backup_list.sh")'s POSIX path
set reset_keyboard_sh to (path to resource "reset_keyboard.sh")'s POSIX path
set device_list to (do shell script current_backup_list_sh)'s paragraphs
set selected_item to choose from list device_list with prompt "ユーザ辞書をリセットする端末を選択してください。" with title my name
if selected_item is false then error number -128 --キャンセル
set selected_folder to split(selected_item as text, " :: ")'s item 2
display notification "ユーザ辞書をリセット中です..."
delay 1
set mobilesync_backup to (((path to application support folder from user domain) as text) & "MobileSync:Backup:")'s POSIX path
do shell script (reset_keyboard_sh & space & quoted form of (mobilesync_backup & selected_folder)) -- & " >& /dev/null &"
display notification "リセットが完了しました。"
delay 1
on split(src_text, delimiter) set last_delimiter to AppleScript's text item delimiters
set AppleScript's text item delimiters to delimiter
set res to src_text's text items
set AppleScript's text item delimiters to last_delimiter
res
end split