import Foundation import WebKit class File: NSObject, WKScriptMessageHandler { let encoder = JSONEncoder() let fm = FileManager.default; let baseDir = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! override init() { super.init(); try! fm.createDirectory(at: baseDir, withIntermediateDirectories: true, attributes: nil) } func escape(_ str: String) -> String { return "'" + str.replacingOccurrences(of: "\'", with: "\\\'").replacingOccurrences(of: "\n", with: "\\n") + "'" } func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) { let data = message.body as! [String: AnyObject] let key = data["_id"] as! String do { var result: String? switch(data["_type"] as! String) { case "readFile": result = try readFile(data["name"] as! String, (data["start"] as! NSNumber?)?.uint64Value, data["length"] as! Int?) case "writeFile": try writeFile(data["name"] as! String, data["data"] as! String) case "append": try append(data["name"] as! String, data["data"] as! String) case "listDirectories": result = try listDirectories(data["name"] as! String) case "listFiles": result = try listFiles(data["name"] as! String) case "getSize": result = try getSize(data["name"] as! String) case "ensureDirectory": try ensureDirectory(data["name"] as! String) default: message.webView!.evaluateJavaScript("nativeError('\(key)',new Error('Unknown message type'))") return } let output = result == nil ? "undefined" : result!; message.webView!.evaluateJavaScript("nativeMessage('\(key)',\(output))") } catch(let error) { message.webView!.evaluateJavaScript("nativeError('\(key)',new Error('File-\(data["_type"]!): \(error.localizedDescription)'))") } } func readFile(_ name: String, _ start: UInt64?, _ length: Int?) throws -> String? { let url = baseDir.appendingPathComponent(name, isDirectory: false); if(!fm.fileExists(atPath: url.path)) { return nil } let fd = try FileHandle(forReadingFrom: url) fd.seek(toFileOffset: start ?? 0) let data: Data = length != nil ? fd.readData(ofLength: length!) : fd.readDataToEndOfFile(); fd.closeFile() return escape(String(data: data, encoding: .utf8)!) } func writeFile(_ name: String, _ data: String) throws { try data.write(to: baseDir.appendingPathComponent(name, isDirectory: false), atomically: true, encoding: .utf8) } func append(_ name: String, _ data: String) throws { let url = baseDir.appendingPathComponent(name, isDirectory: false); if(!fm.fileExists(atPath: url.path)) { fm.createFile(atPath: url.path, contents: nil) } let fd = try FileHandle(forWritingTo: url) fd.seekToEndOfFile() fd.write(data.data(using: .utf8)!) fd.closeFile() } func listDirectories(_ name: String) throws -> String { let dirs = try fm.contentsOfDirectory(at: baseDir.appendingPathComponent(name, isDirectory: true), includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]).filter { try $0.resourceValues(forKeys: [.isDirectoryKey]).isDirectory == true }.map { $0.lastPathComponent } return escape(String(data: try JSONSerialization.data(withJSONObject: dirs), encoding: .utf8)!); } func listFiles(_ name: String) throws -> String { let files = try fm.contentsOfDirectory(at: baseDir.appendingPathComponent(name, isDirectory: true), includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]).filter { try $0.resourceValues(forKeys: [.isDirectoryKey]).isDirectory == false }.map { $0.lastPathComponent } return escape(String(data: try JSONSerialization.data(withJSONObject: files), encoding: .utf8)!); } func getSize(_ name: String) throws -> String { let path = baseDir.appendingPathComponent(name, isDirectory: false).path; if(!fm.fileExists(atPath: path)) { return "0"; } return String(try fm.attributesOfItem(atPath: path)[.size] as! UInt64) } func ensureDirectory(_ name: String) throws { try fm.createDirectory(at: baseDir.appendingPathComponent(name, isDirectory: true), withIntermediateDirectories: true, attributes: nil) } }