macrubyでのSandbox有効アプリの開発について

## macrubyでSandboxのsecurity-scoped bookmarkを取り扱う方法

Mac App Storeに申請するアプリはすべてSandboxが有効でなければならない。Sandboxを有効化する上で必要な初期設定は開発言語に関係なく、TARGETS > Summary > Entitlements > Enable App Sandboxingにチェックを入れて、必要なAccessにチェックを入れておく必要がある。Sandboxの設定ではインターネットアクセスや印刷などいくつかの処理に対して可否を設定することにになるが、ここではファイルへのアクセスについてまとめる。

Sandboxが有効なアプリでは、アプリが勝手にファイルを新規保存したり読み込んだりすることができない。ただし、Entitlementsの
`com.apple.security.files.user-selected.read-write`
をYESにしておくとユーザーがNSOpenPanelやNSSavePanelを介して選択したファイルやフォルダへのアクセスが可能になる。しかしこれだけでは不便なので、ユーザーが選択したファイルに後ほどアクセスするための設定
`com.apple.security.files.bookmarks.app-scope`
がある。これを有効にしておくと、NSOpenPanelやNSSavePanelで指定したファイルを「bookmarkデータ(security-scoped bookmarks)」として格納することができる（NSData）。さらにBookmarkデータはplistに保存しておくことができるので、次回起動時に自動で読み込んだりすることができる(user-selectedを有効にするだけではできない)。なお、`com.apple.security.files.bookmarks.app-scope`についてはxcodeのGUI上にチェックボックスがないようなので、Entitlementsを直接編集して、上記Keyを追加、TypeをBoolean、ValueをYESにしておく必要がある。具体的な手順について以下まとめる。

NSOpenPanelで`openPanel = NSOpenPanel.openPanel`を開き、ファイルを読み込んだ後、

```ruby:bookmarkData.rb
def bkURLtoData(url)
    error = Pointer.new('@')
    bkFileData = url.bookmarkDataWithOptions(NSURLBookmarkCreationWithSecurityScope,
                                                 includingResourceValuesForKeys:nil,
                                                 relativeToURL:nil, error:error)
    if error[0]
        puts error[0].description
        raise
    end
    return bkFileData
end
    
def bkDataToURL(data)
    error = Pointer.new('@')
    bkFileURL = NSURL.URLByResolvingBookmarkData(data,
                                                     options:NSURLBookmarkResolutionWithSecurityScope,
                                                     relativeToURL:nil,
                                                     bookmarkDataIsStale:nil,
                                                     error:error)
   return bkFileURL
    if error[0]
        puts error[0].description
        raise
    end
end

# Create a bookmark file NSData Object
bkData = bkURLtoData(NSURL.fileURLWithPath(opePanel.filename))
# Get NSURL path of the bookmark file NSData Object
bkURL = bkDataToURL(bkData)
```

上の`bkURLtoData`で引数にURLを与えるとブックマークデータ(NSDataオブジェクト)が生成される。またまたブックマークデータのNSURLは`bkDataToURL`で取得できる。上述したようにブックマークデータはplistに保存することができて、後々読み込んでファイル操作することができる。たとえば、以下の様にする。

```ruby:plist.rb
user_plist = NSUserDefaults.standardUserDefaults
# Save: @user_plist.setObject value, forKey:key
user_plist.setObject bkData, forKey:"bkData"
# Load: @user_plist.dataForKey(key)
user_plist.dataForKey("bkData")
```

またplistに保存されているかどうかは、ターミナルで`defaults read com.appID.AppName`と打てば確認できる。

ブックマークされたデータに対して処理をする場合は

```ruby:access.rb
bkURL.startAccessingSecurityScopedResource
# Do any processes you want (File.open, for example)
bkURL.stopAccessingSecurityScopedResource
```
というように処理の開始と終了を宣言する必要がある。

基本的な処理は問題なくできるのだけど、複雑な処理、たとえばmacgemのsqlite3でデータベースを`db = SQLite3::Database.open bkURL.path`で開いて更に`db.execute "CREATE VIRTUAL TABLE"`したりするとエラーになる（どうしたらよいかわかりません）。

なお、テンポラリディレクトリはsandboxの有無に関係なくアクセス自由なので、どうしてもうまくいかないときは`NSTemporaryDirectory()`のなかで作業して、最後に`moveItemAtURL`でファイルを移動してもいいのかも。

```ruby:temporary.rb
error = Pointer.new('@')
fileManager = NSFileManager.defaultManager
fileManager.moveItemAtURL(NSURL.fileURLWithPath(tmp_file),
    toURL:NSURL.fileURLWithPath(distination_file),
    error: error)
if error[0]
    puts error[0].description
    raise
end
```

もちろん、ここで`distination_file`はNSOpenPanelやNSSavePanelで指定されたURLまたはBookmarkDataのURLである必要がある。

## 参考
[Link text](http://developer.apple.com/library/mac/#documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW8 "Title")

[Link text](http://good-morning-call.com/2012/06/23/サンドボックス対応アプリでファイルを選択する/ "サンドボックス対応アプリでファイルを選択する")

PS. App Storeで問題になる被仕向送金手数料ですが、最近ソニー銀行で無料化されたようですね。