iOSでLAN内共有フォルダにアクセスする

# やりたいこと

iOSアプリ上でwifi経由で共有フォルダにファイル転送したい。

# ライブラリ
[SMBClient](https://github.com/filmicpro/SMBClient)
Carthageで導入
`gitHub "filmicpro/SMBClient"`

# 実装

SMBClient内にサンプルプロジェクトが組み込まれてるけど、
機能毎に理解するためにサンプル作成

## 接続先検索

```swift:ViewController.swift
import UIKit
import SMBClient

class ViewController: UIViewController, NetBIOSNameServiceDelegate {        
    @IBOutlet weak var label: UILabel!
	
    let biosNameService = NetBIOSNameService()

    // 接続先
    var servers: [NetBIOSNameServiceEntry] = []{
		// デリゲートメソッドがメインスレッド内じゃないので
		// 接続先追加/削除時の処理はプロパティ変更検知で対応
        didSet {
            DispatchQueue.main.async {
                // とりあえずラベルに表示
                var txt = ""
				for server in self.servers {
					txt += "\(server.name) : \(server.ipAddressString)\r\n"
				}
				self.label.text = txt
            }
        }
    }    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        biosNameService.delegate = self
    }

    /// 接続先検索ボタン
    @IBAction func btnTouchUpInside(_ sender: Any) {
		// LAN内検索開始        
        biosNameService.startDiscovery(withTimeout: 3000)
    }

	// MARK: - 以下デリゲートメソッド

	/// 接続先が追加された場合の処理
    ///
    /// - Parameter entry: 接続先
	func added(entry: NetBIOSNameServiceEntry) {
        self.servers.append(entry)
    }
    
    /// 接続先が消えた場合の処理？確認できず
    ///
    /// - Parameter entry: 接続先
    func removed(entry: NetBIOSNameServiceEntry) {
        self.servers = self.servers.filter { $0 != entry }
    }
}
```

## 接続

```swift:ViewController.swift

// 共有ボリューム
var volumes: [SMBVolume] = []

/// 接続処理
func connect(){
	let svr = self.servers.first!	// とりあえずテストとして最初のやつ
	// サーバ情報
	let smbServer = SMBServer(hostname: svr.name, ipAddress: svr.ipAddress)
	// 認証情報
	let creds = SMBSession.Credentials.user(name: "username", password: "password")
	// セッション情報
	let session = SMBSession(server: smbServer, credentials: creds)

	// 接続先の共有フォルダ一覧取得
	session.requestVolumes(completion: { (result) in
		switch result {
		case .success(let volumes):
			// とりあえずラベルに表示
			var txt = ""
			for volume in volumes {
				txt += "\(volume.name)\r\n"
			}
			self.label.text = txt
		case .failure(let error):
			let alert = UIAlertController(title: "error", message: error.debugDescription, preferredStyle: .alert)
			let ok = UIAlertAction(title: "OK", style: .default, handler: { (_) in
				self.navigationController?.popViewController(animated: true)
			})
			alert.addAction(ok)
			self.present(alert, animated: true, completion: nil)
		}
	})
}
```

## ファイル転送

```swift:ViewController.swift

extension ViewController: SessionUploadTaskDelegate {

	/// アップロード
	func upload() {
		// 転送先
		let svr = self.servers.first	// とりあえずテストとして最初の接続先
		let smbServer = SMBServer(hostname: svr.name, ipAddress: svr.ipAddress)
		let creds = SMBSession.Credentials.user(name: "username", password: "password")
		let session = SMBSession(server: smbServer, credentials: creds)

		// 転送するファイル
		let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? ""
		let fileName = "test.jpg"
		let filePath = documentsPath + "/" + fileName
		
		// アップロード
		session.uploadTaskForFile(toPath: self.volumes.first.path,	// とりあえず最初のパス
									withName: fileName,
									fromURL: URL(string: filePath)!,
									delegate: self)
	}

	// MARK: - 以下デリゲートメソッド

	/// アップロード完了時の処理
	func uploadTask(didFinishUploading: SessionUploadTask) {
        print("OK")
    }
    
	/// アップロード中の処理
    func uploadTask(_ task: SessionUploadTask, totalBytesSent: UInt64, totalBytesExpected: UInt64) {
        let percent = String(format: "%.2f", Double(totalBytesSent) / Double(totalBytesExpected) * 100)
        print("\(percent)％完了")
    }
    
	/// エラー時の処理
    func uploadTask(didCompleteWithError: SessionUploadTask.SessionUploadError) {
        print("ERROR:\(didCompleteWithError.localizedDescription)")
    }
}

```

# まとめ

サンプルプロジェクト見るとよくわかるけど
以下のような標準的な画面設計に合わせて
クラスが設計されているので組みやすい。

1.接続先一覧をTableViewに表示
2.認証情報入力画面表示
3.ボリューム一覧をTableViewに表示


ライブラリ公開してくれる神がいたおかげで目的だけは達成できたけど
SMBプロトコルの理解が全く足りてないなぁ。。。