diff --git a/Sources/OneDriveFileProvider.swift b/Sources/OneDriveFileProvider.swift index 7faf313..2a3ae2b 100644 --- a/Sources/OneDriveFileProvider.swift +++ b/Sources/OneDriveFileProvider.swift @@ -19,8 +19,71 @@ import CoreGraphics */ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing { override open class var type: String { return "OneDrive" } - /// Drive name for user, default is `root`. Changing its value will effect on new operations. - open var drive: String + + public enum SubAddress: RawRepresentable { + case me + case drive(uuid: UUID) + case group(uuid: UUID) + case site(uuid: UUID) + case user(uuid: UUID) + + public init?(rawValue: String) { + let components = rawValue.components(separatedBy: ";") + guard let type = components.first else { + return nil + } + if type == "me" { + self = .me + } + guard let uuid = components.last.flatMap({ UUID(uuidString: $0) }) else { + return nil + } + switch type { + case "drive": + self = .drive(uuid: uuid) + case "group": + self = .group(uuid: uuid) + case "site": + self = .site(uuid: uuid) + case "user": + self = .user(uuid: uuid) + default: + return nil + } + } + + public var rawValue: String { + switch self { + case .me: + return "me;" + case .drive(uuid: let uuid): + return "drive;" + uuid.uuidString + case .group(uuid: let uuid): + return "group;" + uuid.uuidString + case .site(uuid: let uuid): + return "site;" + uuid.uuidString + case .user(uuid: let uuid): + return "user;" + uuid.uuidString + } + } + + var drivePath: String { + switch self { + case .me: + return "me/drive" + case .drive(uuid: let uuid): + return "drives/" + uuid.uuidString + case .group(uuid: let uuid): + return "groups/" + uuid.uuidString + "/drive" + case .site(uuid: let uuid): + return "sites/" + uuid.uuidString + "/drive" + case .user(uuid: let uuid): + return "users/" + uuid.uuidString + "/drive" + } + } + } + /// Sub Address for container, default is `.me`. Changing its value will effect on new operations. + open var subAddress: SubAddress /** Initializer for Onedrive provider with given client ID and Token. @@ -36,17 +99,45 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing { - drive: drive name for user on server, default value is `root`. - cache: A URLCache to cache downloaded files and contents. */ - public init(credential: URLCredential?, serverURL: URL? = nil, drive: String = "root", cache: URLCache? = nil) { + @available(*, deprecated, message: "use init(credential:, serverURL, subAddress:, cache:) instead.") + public init(credential: URLCredential?, serverURL: URL? = nil, drive: String?, cache: URLCache? = nil) { let baseURL = serverURL?.absoluteURL ?? URL(string: "https://api.onedrive.com/")! let refinedBaseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("") - self.drive = drive + self.subAddress = drive.flatMap({ UUID(uuidString: $0) }).flatMap({ SubAddress.drive(uuid: $0) }) ?? .me + super.init(baseURL: refinedBaseURL, credential: credential, cache: cache) + } + + /** + Initializer for Onedrive provider with given client ID and Token. + These parameters must be retrieved via [Authentication for the OneDrive API](https://dev.onedrive.com/auth/readme.htm). + + There are libraries like [p2/OAuth2](https://github.com/p2/OAuth2) or [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) which can facilate the procedure to retrieve token. + The latter is easier to use and prefered. Also you can use [auth0/Lock](https://github.com/auth0/Lock.iOS-OSX) which provides graphical user interface. + + - Parameters: + - credential: a `URLCredential` object with Client ID set as `user` and Token set as `password`. + - serverURL: server url, Set it if you are trying to connect OneDrive Business server, otherwise leave it + `nil` to connect to OneDrive Personal uses. + - subAddress: drive name for user on server, default value is `.me`. + - cache: A URLCache to cache downloaded files and contents. + */ + public init(credential: URLCredential?, serverURL: URL? = nil, subAddress: SubAddress = .me, cache: URLCache? = nil) { + let baseURL = serverURL?.absoluteURL ?? URL(string: "https://api.onedrive.com/")! + let refinedBaseURL = baseURL.absoluteString.hasSuffix("/") ? baseURL : baseURL.appendingPathComponent("") + self.subAddress = subAddress super.init(baseURL: refinedBaseURL, credential: credential, cache: cache) } public required convenience init?(coder aDecoder: NSCoder) { + let subAddress: SubAddress + if let driveId = aDecoder.decodeObject(forKey: "drive") as? String, let uuid = UUID(uuidString: driveId) { + subAddress = .drive(uuid: uuid) + } else { + subAddress = (aDecoder.decodeObject(forKey: "subAddress") as? String).flatMap({ SubAddress(rawValue: $0) }) ?? .me + } self.init(credential: aDecoder.decodeObject(forKey: "credential") as? URLCredential, serverURL: aDecoder.decodeObject(forKey: "baseURL") as? URL, - drive: aDecoder.decodeObject(forKey: "drive") as? String ?? "root") + subAddress: subAddress) self.currentPath = aDecoder.decodeObject(forKey: "currentPath") as? String ?? "" self.useCache = aDecoder.decodeBool(forKey: "useCache") self.validatingCache = aDecoder.decodeBool(forKey: "validatingCache") @@ -54,11 +145,11 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing { open override func encode(with aCoder: NSCoder) { super.encode(with: aCoder) - aCoder.encode(self.drive, forKey: "drive") + aCoder.encode(self.subAddress.rawValue, forKey: "subAddress") } open override func copy(with zone: NSZone? = nil) -> Any { - let copy = OneDriveFileProvider(credential: self.credential, serverURL: self.baseURL, drive: self.drive, cache: self.cache) + let copy = OneDriveFileProvider(credential: self.credential, serverURL: self.baseURL, subAddress: self.subAddress, cache: self.cache) copy.currentPath = self.currentPath copy.delegate = self.delegate copy.fileOperationDelegate = self.fileOperationDelegate @@ -83,7 +174,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing { if let response = response as? HTTPURLResponse { let code = FileProviderHTTPErrorCode(rawValue: response.statusCode) serverError = code != nil ? FileProviderOneDriveError(code: code!, path: path, errorDescription: String(data: data ?? Data(), encoding: .utf8)) : nil - if let json = data?.deserializeJSON(), let file = OneDriveFileObject(baseURL: self.baseURL, drive: self.drive, json: json) { + if let json = data?.deserializeJSON(), let file = OneDriveFileObject(baseURL: self.baseURL, subAddress: self.subAddress, json: json) { fileObject = file } } @@ -132,26 +223,40 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing { } open func url(of path: String, modifier: String? = nil) -> URL { + var url: URL = baseURL! var rpath: String = path + let isId = path.hasPrefix("id:") - let driveURL = baseURL!.appendingPathComponent("drive/\(drive):/") + url.appendPathComponent(subAddress.drivePath) + + if isId { + url.appendPathComponent("root:") + } else { + url.appendPathComponent("items") + } if rpath.hasPrefix("/") { _=rpath.characters.removeFirst() } - if rpath.isEmpty { - if let modifier = modifier { - return driveURL.appendingPathComponent(modifier) - } - return driveURL - } rpath = rpath.trimmingCharacters(in: pathTrimSet) - if let modifier = modifier { - rpath = rpath + ":/" + modifier + + switch (modifier == nil, rpath.isEmpty, isId) { + case (true, false, _): + url.appendPathComponent(rpath) + case (true, true, _): + break + case (false, true, _): + url.appendPathComponent(modifier!) + case (false, false, true): + url.appendPathComponent(rpath) + url.appendPathComponent(modifier!) + case (false, false, false): + url.appendPathComponent(rpath + ":") + url.appendPathComponent(modifier!) } - return driveURL.appendingPathComponent(rpath) + return url } open override func isReachable(completionHandler: @escaping (Bool) -> Void) { @@ -167,8 +272,7 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing { override func request(for operation: FileOperationType, overwrite: Bool = false, attributes: [URLResourceKey : Any] = [:]) -> URLRequest { - func correctPath(_ path: String?) -> String? { - guard let path = path else { return nil } + func correctPath(_ path: String) -> String { if path.hasPrefix("id:") { return path } @@ -219,10 +323,22 @@ open class OneDriveFileProvider: HTTPFileProvider, FileProviderSharing { case .copy(let source, let dest) where !source.hasPrefix("file://") && !dest.hasPrefix("file://"), .move(source: let source, destination: let dest): request.set(httpContentType: .json) - let cdest = (correctPath(dest) as NSString?)! + let cdest = correctPath(dest) as NSString + var parentRefrence: [String: AnyObject] = [:] + if cdest.hasPrefix("id:") { + parentRefrence["id"] = cdest.components(separatedBy: "/").first as NSString? + switch self.subAddress { + case .drive(uuid: let uuid): + parentRefrence["driveId"] = uuid.uuidString as NSString + default: + break + } + } else { + parentRefrence["path"] = cdest.deletingLastPathComponent as NSString + } var requestDictionary = [String: AnyObject]() - requestDictionary["parentReference"] = ("/drive/\(drive):" + cdest.deletingLastPathComponent) as NSString - requestDictionary["name"] = cdest.lastPathComponent as NSString + requestDictionary["parentReference"] = parentRefrence as NSDictionary + requestDictionary["name"] = (cdest as NSString).lastPathComponent as NSString request.httpBody = Data(jsonDictionary: requestDictionary) default: break diff --git a/Sources/OneDriveHelper.swift b/Sources/OneDriveHelper.swift index 7763808..d6b4a9a 100644 --- a/Sources/OneDriveHelper.swift +++ b/Sources/OneDriveHelper.swift @@ -27,15 +27,15 @@ public final class OneDriveFileObject: FileObject { super.init(url: url, name: name, path: rpath.removingPercentEncoding ?? path) } - internal convenience init? (baseURL: URL?, drive: String, jsonStr: String) { + internal convenience init? (baseURL: URL?, subAddress: OneDriveFileProvider.SubAddress, jsonStr: String) { guard let json = jsonStr.deserializeJSON() else { return nil } - self.init(baseURL: baseURL, drive: drive, json: json) + self.init(baseURL: baseURL, subAddress: subAddress, json: json) } - internal convenience init? (baseURL: URL?, drive: String, json: [String: AnyObject]) { + internal convenience init? (baseURL: URL?, subAddress: OneDriveFileProvider.SubAddress, json: [String: AnyObject]) { guard let name = json["name"] as? String else { return nil } guard let path = json["parentReference"]?["path"] as? String else { return nil } - var lPath = path.replacingOccurrences(of: "/drive/\(drive):", with: "/", options: .anchored, range: nil) + var lPath = path.replacingOccurrences(of: subAddress.drivePath, with: "", options: .anchored, range: nil) lPath = lPath.replacingOccurrences(of: "/:", with: "", options: .anchored) lPath = lPath.replacingOccurrences(of: "//", with: "", options: .anchored) self.init(baseURL: baseURL, name: name, path: lPath) @@ -109,7 +109,7 @@ internal extension OneDriveFileProvider { if let json = data?.deserializeJSON() { if let entries = json["value"] as? [AnyObject] , entries.count > 0 { for entry in entries { - if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, drive: self.drive, json: entry) { + if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, subAddress: self.subAddress, json: entry) { files.append(file) } } @@ -133,9 +133,19 @@ internal extension OneDriveFileProvider { } let url: URL - let q = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! - let expanded = recursive ? "&expand=children" : "" - url = next ?? self.url(of: startPath, modifier: "view.search?q=\(q)\(expanded)") + if let next = next { + url = next + } else { + let bURL = self.baseURL!.appendingPathComponent(subAddress.drivePath).appendingPathComponent("root/search") + var components = URLComponents(url: bURL, resolvingAgainstBaseURL: false)! + let qItem = URLQueryItem(name: "q", value: query) + components.queryItems = [qItem] + if recursive { + components.queryItems?.append(URLQueryItem(name: "expand", value: "children")) + } + url = components.url! + } + var request = URLRequest(url: url) request.httpMethod = "GET" request.set(httpAuthentication: credential, with: .oAuth2) @@ -148,7 +158,7 @@ internal extension OneDriveFileProvider { if let json = data?.deserializeJSON() { if let entries = json["value"] as? [AnyObject] , entries.count > 0 { for entry in entries { - if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, drive: self.drive, json: entry) { + if let entry = entry as? [String: AnyObject], let file = OneDriveFileObject(baseURL: self.baseURL, subAddress: self.subAddress, json: entry) { foundItem(file) } }