Finderの「サーバへ接続」の「よく使うサーバ」リストに一括追加する

Finderの「サーバへ接続」画面において，`+` ボタンを押すと，「よく使うサーバ」にサーバのURLを追加できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41739/578e04b6-3fb9-491f-c284-70f7ee92fb0e.png)

ここに登録したいサーバがたくさんあるとき，コマンドで一括追加したくなります。

かつては，

```sh
$ /usr/bin/sfltool add-item -n "hoge" com.apple.LSSharedFileList.FavoriteServers "smb://hoge@192.168.0.100"
```

のようなコマンドで「よく使うサーバ」リストに追加ができました。これにより，`~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.FavoriteServers.sfl2` にサーバのエントリが追記されたのです。

これをシェルスクリプトにしておけば，よく使うサーバを大量に一括登録できたのですが，なんと，macOS 10.13 以降，`sfltool` コマンドに `add-item` というサブコマンドは[廃止され，復活の予定はない](https://openradar.appspot.com/radar?id=4985135170584576)そうです。

そこで，`com.apple.LSSharedFileList.FavoriteServers.sfl2` にエントリを追加するには，結構面倒なプログラミングが必要になりました。Swiftコードで書くとこのようになります（エラー処理はテキトーです）。

```swift
import Foundation

let error = NSError(domain: "error", code: -1, userInfo: nil)
let sflURL = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Library").appendingPathComponent("Application Support").appendingPathComponent("com.apple.sharedfilelist").appendingPathComponent("com.apple.LSSharedFileList.FavoriteServers.sfl2")

func readFavoriteServers() throws -> (items: NSArray, properties: NSDictionary, existingURLs: [URL]) {
    guard let dict = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(Data(contentsOf: sflURL)) as? NSDictionary,
          let items = dict["items"] as? NSArray,
          let properties = dict["properties"] as? NSDictionary else { throw error }
    
    // 型情報
    // dict: NSDictionary<NSString*,id>*
    // items: NSArray<NSDictionary<NSString*,id>*>*
    // properties: NSDictionary<NSString*,id>*

    let existingURLs: [URL] = items.compactMap { item in
        var stale: Bool = false
        guard let item = item as? NSDictionary,
              let data = item["Bookmark"] as? Data,
              let url = try? URL(resolvingBookmarkData: data, bookmarkDataIsStale: &stale) else { return nil }
        return url
    }
    
    return (items: items, properties: properties, existingURLs: existingURLs)
}

func addFavoriteServer(_ paths: String...) throws {
    guard let (oldItems, properties, existingURLs) = try? readFavoriteServers() else { throw error }
    let uniqueURLs = Set(existingURLs)
    
    print("Existing items: " + existingURLs.description)

    let newItems = NSMutableArray(array: oldItems)

    paths.forEach { path in
        guard let encodedPath = path.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
              let url = URL(string: encodedPath),
              let bookmark = try? url.bookmarkData(options: [], includingResourceValuesForKeys: nil, relativeTo: nil) else { return }
        
        let entry: [String:Any] = [
            "Name": path,
            "Bookmark": bookmark,
            "uuid": UUID().uuidString,
            "visibility": 0,
            "CustomItemProperties": NSDictionary()
        ]
        
        if uniqueURLs.contains(url) {
            print("Skip: " + path)
        } else {
            newItems.add(entry)
            print("Add: " + path)
        }
    }

    let newDict: [String:Any] = [
        "items": newItems,
        "properties": properties
    ]

    do {
        try NSKeyedArchiver.archivedData(withRootObject: newDict, requiringSecureCoding: true).write(to: sflURL)
    } catch let e {
        throw e
    }
}

// main routine
do {
    try addFavoriteServer("smb://192.168.100.1","smb://192.168.100.2","smb://192.168.100.3","smb://192.168.100.4")
    let (_, _, existingURLs) = try readFavoriteServers()
    print("New items: " + existingURLs.description)
    print("Restart your Mac for the changes to take effect!")
} catch let e {
    print(e.localizedDescription)
}
```

このコードでは，最後のメインルーチンのところで

```swift
addFavoriteServer("smb://192.168.100.1","smb://192.168.100.2","smb://192.168.100.3","smb://192.168.100.4")
```
とすることで，サーバのURLを一括追加しています。`addFavoriteServer` は，引数に与えられたURLたちを，既存のサーバURLと照合して，既存のURLはスキップして，新しいURLのみだけを順に追加していきます。

この変更をFinderのUI上に反映させるためには，macOSの再起動が必要でした。（Finderの再起動だけで済むという話もありましたが，自分の環境ではOS全体を再起動しないと変更が反映されませんでした。）