diff --git a/Package.swift b/Package.swift index a70f176..7926455 100644 --- a/Package.swift +++ b/Package.swift @@ -21,7 +21,7 @@ let package = Package( url: "https://github.com/claucambra/NextcloudCapabilitiesKit.git", .upToNextMajor(from: "2.0.0") ), - .package(url: "https://github.com/nextcloud/NextcloudKit", .upToNextMajor(from: "2.9.9")), + .package(url: "https://github.com/nextcloud/NextcloudKit", branch: "develop"), .package(url: "https://github.com/realm/realm-swift.git", exact: "10.49.0"), .package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0") ], diff --git a/Sources/NextcloudFileProviderKit/Enumeration/Enumerator+SyncEngine.swift b/Sources/NextcloudFileProviderKit/Enumeration/Enumerator+SyncEngine.swift index 60898fc..9b5b019 100644 --- a/Sources/NextcloudFileProviderKit/Enumeration/Enumerator+SyncEngine.swift +++ b/Sources/NextcloudFileProviderKit/Enumeration/Enumerator+SyncEngine.swift @@ -18,6 +18,7 @@ import OSLog extension Enumerator { func fullRecursiveScan( + account: Account, remoteInterface: RemoteInterface, dbManager: FilesDatabaseManager, scanChangesOnly: Bool @@ -34,6 +35,7 @@ extension Enumerator { let results = await self.scanRecursively( rootContainerDirectoryMetadata, + account: account, remoteInterface: remoteInterface, dbManager: dbManager, scanChangesOnly: scanChangesOnly @@ -65,6 +67,7 @@ extension Enumerator { private func scanRecursively( _ directoryMetadata: ItemMetadata, + account: Account, remoteInterface: RemoteInterface, dbManager: FilesDatabaseManager, scanChangesOnly: Bool @@ -89,7 +92,7 @@ extension Enumerator { let itemServerUrl = directoryMetadata.ocId == NSFileProviderItemIdentifier.rootContainer.rawValue - ? remoteInterface.account.davFilesUrl + ? account.davFilesUrl : directoryMetadata.serverUrl + "/" + directoryMetadata.fileName Self.logger.debug("About to read: \(itemServerUrl, privacy: .public)") @@ -98,6 +101,7 @@ extension Enumerator { metadatas, newMetadatas, updatedMetadatas, deletedMetadatas, readError ) = await Self.readServerUrl( itemServerUrl, + account: account, remoteInterface: remoteInterface, dbManager: dbManager, domain: domain, @@ -156,7 +160,7 @@ extension Enumerator { Self.logger.info( """ Finished reading serverUrl: \(itemServerUrl, privacy: .public) - for user: \(remoteInterface.account.ncKitAccount, privacy: .public) + for user: \(account.ncKitAccount, privacy: .public) """ ) @@ -166,7 +170,7 @@ extension Enumerator { Self.logger.warning( """ Nil metadatas received in change read at \(itemServerUrl, privacy: .public) - for user: \(remoteInterface.account.ncKitAccount, privacy: .public) + for user: \(account.ncKitAccount, privacy: .public) """ ) } @@ -177,7 +181,7 @@ extension Enumerator { Self.logger.warning( """ Nil new metadatas received in change read at \(itemServerUrl, privacy: .public) - for user: \(remoteInterface.account.ncKitAccount, privacy: .public) + for user: \(account.ncKitAccount, privacy: .public) """ ) } @@ -188,7 +192,7 @@ extension Enumerator { Self.logger.warning( """ Nil updated metadatas received in change read at \(itemServerUrl, privacy: .public) - for user: \(remoteInterface.account.ncKitAccount, privacy: .public) + for user: \(account.ncKitAccount, privacy: .public) """ ) } @@ -199,7 +203,7 @@ extension Enumerator { Self.logger.warning( """ Nil deleted metadatas received in change read at \(itemServerUrl, privacy: .public) - for user: \(remoteInterface.account.ncKitAccount, privacy: .public) + for user: \(account.ncKitAccount, privacy: .public) """ ) } @@ -242,6 +246,7 @@ extension Enumerator { ) let childScanResult = await scanRecursively( childDirectory, + account: account, remoteInterface: remoteInterface, dbManager: dbManager, scanChangesOnly: scanChangesOnly @@ -262,7 +267,7 @@ extension Enumerator { static func handleDepth1ReadFileOrFolder( serverUrl: String, - ncAccount: Account, + account: Account, dbManager: FilesDatabaseManager, files: [NKFile] ) async -> ( @@ -275,14 +280,14 @@ extension Enumerator { Self.logger.debug( """ Starting async conversion of NKFiles for serverUrl: \(serverUrl, privacy: .public) - for user: \(ncAccount.ncKitAccount, privacy: .public) + for user: \(account.ncKitAccount, privacy: .public) """ ) let (directoryMetadata, metadatas) = await withCheckedContinuation { continuation in ItemMetadata.metadatasFromDirectoryReadNKFiles( - files, account: ncAccount.ncKitAccount + files, account: account ) { directoryMetadata, _, metadatas in continuation.resume(returning: (directoryMetadata, metadatas)) } @@ -291,7 +296,7 @@ extension Enumerator { // STORE DATA FOR CURRENTLY SCANNED DIRECTORY // We have now scanned this directory's contents, so update with etag in order to not check // again if not needed unless it's the root container - if serverUrl != ncAccount.davFilesUrl { + if serverUrl != account.davFilesUrl { dbManager.addItemMetadata(directoryMetadata) } @@ -301,7 +306,7 @@ extension Enumerator { // They will get updated when they are the subject of a readServerUrl call. // (See above) let changedMetadatas = dbManager.updateItemMetadatas( - account: ncAccount.ncKitAccount, + account: account.ncKitAccount, serverUrl: serverUrl, updatedMetadatas: metadatas, updateDirectoryEtags: false @@ -318,6 +323,7 @@ extension Enumerator { static func readServerUrl( _ serverUrl: String, + account: Account, remoteInterface: RemoteInterface, dbManager: FilesDatabaseManager, domain: NSFileProviderDomain? = nil, @@ -331,17 +337,16 @@ extension Enumerator { deletedMetadatas: [ItemMetadata]?, readError: NKError? ) { - let ncAccount = remoteInterface.account - let ncKitAccount = ncAccount.ncKitAccount + let ncKitAccount = account.ncKitAccount Self.logger.debug( """ Starting to read serverUrl: \(serverUrl, privacy: .public) for user: \(ncKitAccount, privacy: .public) at depth \(depth.rawValue, privacy: .public). - username: \(ncAccount.username, privacy: .public), - password is empty: \(ncAccount.password == "" ? "EMPTY" : "NOT EMPTY"), - serverUrl: \(ncAccount.serverUrl, privacy: .public) + username: \(account.username, privacy: .public), + password is empty: \(account.password == "" ? "EMPTY" : "NOT EMPTY"), + serverUrl: \(account.serverUrl, privacy: .public) """ ) @@ -351,6 +356,7 @@ extension Enumerator { showHiddenFiles: true, includeHiddenFiles: [], requestBody: nil, + account: account, options: .init(), taskHandler: { task in if let domain, let enumeratedItemIdentifier { @@ -387,7 +393,7 @@ extension Enumerator { Self.logger.debug( """ Read item is a file. Converting NKfile for serverUrl: \(serverUrl, privacy: .public) - for user: \(ncAccount.ncKitAccount, privacy: .public) + for user: \(account.ncKitAccount, privacy: .public) """ ) let itemMetadata = receivedFile.toItemMetadata() @@ -418,7 +424,7 @@ extension Enumerator { } if depth == .target { - if serverUrl == ncAccount.davFilesUrl { + if serverUrl == account.davFilesUrl { return (nil, nil, nil, nil, nil) } else { let metadata = receivedFile.toItemMetadata() @@ -435,7 +441,7 @@ extension Enumerator { allMetadatas, newMetadatas, updatedMetadatas, deletedMetadatas, readError ) = await handleDepth1ReadFileOrFolder( serverUrl: serverUrl, - ncAccount: ncAccount, + account: account, dbManager: dbManager, files: files ) diff --git a/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift b/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift index fb74516..a337898 100644 --- a/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift +++ b/Sources/NextcloudFileProviderKit/Enumeration/Enumerator.swift @@ -29,8 +29,8 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { private let anchor = NSFileProviderSyncAnchor(Date().description.data(using: .utf8)!) private static let maxItemsPerFileProviderPage = 100 static let logger = Logger(subsystem: Logger.subsystem, category: "enumerator") + let account: Account let remoteInterface: RemoteInterface - let ncKitAccount: String let fastEnumeration: Bool var serverUrl: String = "" var isInvalidated = false @@ -42,6 +42,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { public init( enumeratedItemIdentifier: NSFileProviderItemIdentifier, + account: Account, remoteInterface: RemoteInterface, dbManager: FilesDatabaseManager = .shared, domain: NSFileProviderDomain? = nil, @@ -50,7 +51,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { ) { self.enumeratedItemIdentifier = enumeratedItemIdentifier self.remoteInterface = remoteInterface - self.ncKitAccount = remoteInterface.account.ncKitAccount + self.account = account self.dbManager = dbManager self.domain = domain self.fastEnumeration = fastEnumeration @@ -63,7 +64,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { \(enumeratedItemIdentifier.rawValue, privacy: .public) """ ) - serverUrl = remoteInterface.account.davFilesUrl + serverUrl = account.davFilesUrl } else { Self.logger.debug( """ @@ -89,7 +90,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.logger.info( """ - Set up enumerator for user: \(self.ncKitAccount, privacy: .public) + Set up enumerator for user: \(self.account.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) """ ) @@ -117,7 +118,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.logger.debug( """ Received enumerate items request for enumerator with user: - \(self.ncKitAccount, privacy: .public) + \(self.account.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) """ ) @@ -137,7 +138,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { if enumeratedItemIdentifier == .trashContainer { Self.logger.debug( """ - Enumerating trash set for user: \(self.ncKitAccount, privacy: .public) + Enumerating trash set for user: \(self.account.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) """ ) @@ -175,7 +176,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { { Self.logger.debug( """ - Enumerating initial page for user: \(self.ncKitAccount, privacy: .public) + Enumerating initial page for user: \(self.account.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) """ ) @@ -183,6 +184,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Task { let (metadatas, _, _, _, readError) = await Self.readServerUrl( serverUrl, + account: account, remoteInterface: remoteInterface, dbManager: dbManager ) @@ -190,7 +192,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { guard readError == nil else { Self.logger.error( """ - "Finishing enumeration for user: \(self.ncKitAccount, privacy: .public) + "Finishing enumeration for: \(self.account.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error \(readError!.errorDescription, privacy: .public) """ @@ -207,7 +209,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { guard let metadatas else { Self.logger.error( """ - Finishing enumeration for user: \(self.ncKitAccount, privacy: .public) + Finishing enumeration for: \(self.account.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with invalid metadatas. """ @@ -222,13 +224,14 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.logger.info( """ Finished reading serverUrl: \(self.serverUrl, privacy: .public) - for user: \(self.ncKitAccount, privacy: .public). + for user: \(self.account.ncKitAccount, privacy: .public). Processed \(metadatas.count) metadatas """ ) Self.completeEnumerationObserver( observer, + account: account, remoteInterface: remoteInterface, dbManager: dbManager, numPage: 1, @@ -244,7 +247,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.logger.debug( """ Enumerating page \(numPage, privacy: .public) - for user: \(self.ncKitAccount, privacy: .public) + for user: \(self.account.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) """ ) @@ -263,7 +266,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.logger.debug( """ Received enumerate changes request for enumerator for user: - \(self.ncKitAccount, privacy: .public) + \(self.account.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) """ ) @@ -279,7 +282,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { if enumeratedItemIdentifier == .workingSet { Self.logger.debug( - "Enumerating changes in working set for: \(self.ncKitAccount, privacy: .public)" + "Enumerating changes in working set for: \(self.account.ncKitAccount, privacy: .public)" ) // Unlike when enumerating items we can't progressively enumerate items as we need to @@ -288,6 +291,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { let ( _, newMetadatas, updatedMetadatas, deletedMetadatas, error ) = await fullRecursiveScan( + account: account, remoteInterface: remoteInterface, dbManager: dbManager, scanChangesOnly: true @@ -297,7 +301,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.logger.info( """ Enumerator invalidated during working set change scan. - For user: \(self.ncKitAccount, privacy: .public) + For user: \(self.account.ncKitAccount, privacy: .public) """ ) listener?.enumerationActionFailed( @@ -311,7 +315,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.logger.info( """ Finished recursive change enumeration of working set for user: - \(self.ncKitAccount, privacy: .public) + \(self.account.ncKitAccount, privacy: .public) with error: \(error!.errorDescription, privacy: .public) """ ) @@ -326,13 +330,14 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.logger.info( """ Finished recursive change enumeration of working set for user: - \(self.ncKitAccount, privacy: .public). Enumerating items. + \(self.account.ncKitAccount, privacy: .public). Enumerating items. """ ) Self.completeChangesObserver( observer, anchor: anchor, + account: account, remoteInterface: remoteInterface, dbManager: dbManager, newMetadatas: newMetadatas, @@ -344,7 +349,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { return } else if enumeratedItemIdentifier == .trashContainer { Self.logger.debug( - "Enumerating changes in trash set for user: \(self.ncKitAccount, privacy: .public)" + "Enumerating changes in trash set for: \(self.account.ncKitAccount, privacy: .public)" ) // TODO! @@ -355,7 +360,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.logger.info( """ - Enumerating changes for user: \(self.ncKitAccount, privacy: .public) + Enumerating changes for user: \(self.account.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) """ ) @@ -368,6 +373,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { _, newMetadatas, updatedMetadatas, deletedMetadatas, readError ) = await Self.readServerUrl( serverUrl, + account: account, remoteInterface: remoteInterface, dbManager: dbManager, stopAtMatchingEtags: true @@ -382,7 +388,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { guard readError == nil else { Self.logger.error( """ - Finishing enumeration of changes for: \(self.ncKitAccount, privacy: .public) + Finishing enumeration of changes for: \(self.account.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error: \(readError!.errorDescription, privacy: .public) """ @@ -432,6 +438,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.completeChangesObserver( observer, anchor: anchor, + account: account, remoteInterface: remoteInterface, dbManager: dbManager, newMetadatas: nil, @@ -460,13 +467,14 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { Self.logger.info( """ Finished reading serverUrl: \(self.serverUrl, privacy: .public) - for user: \(self.ncKitAccount, privacy: .public) + for user: \(self.account.ncKitAccount, privacy: .public) """ ) Self.completeChangesObserver( observer, anchor: anchor, + account: account, remoteInterface: remoteInterface, dbManager: dbManager, newMetadatas: newMetadatas, @@ -486,6 +494,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { // TODO: Use async group private static func metadatasToFileProviderItems( _ itemMetadatas: [ItemMetadata], + account: Account, remoteInterface: RemoteInterface, dbManager: FilesDatabaseManager, completionHandler: @escaping (_ items: [NSFileProviderItem]) -> Void @@ -518,6 +527,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { let item = Item( metadata: itemMetadata, parentItemIdentifier: parentItemIdentifier, + account: account, remoteInterface: remoteInterface ) Self.logger.debug( @@ -553,13 +563,14 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { private static func completeEnumerationObserver( _ observer: NSFileProviderEnumerationObserver, + account: Account, remoteInterface: RemoteInterface, dbManager: FilesDatabaseManager, numPage: Int, itemMetadatas: [ItemMetadata] ) { metadatasToFileProviderItems( - itemMetadatas, remoteInterface: remoteInterface, dbManager: dbManager + itemMetadatas, account: account, remoteInterface: remoteInterface, dbManager: dbManager ) { items in observer.didEnumerate(items) Self.logger.info("Did enumerate \(items.count) items") @@ -581,6 +592,7 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { private static func completeChangesObserver( _ observer: NSFileProviderChangeObserver, anchor: NSFileProviderSyncAnchor, + account: Account, remoteInterface: RemoteInterface, dbManager: FilesDatabaseManager, newMetadatas: [ItemMetadata]?, @@ -621,7 +633,10 @@ public class Enumerator: NSObject, NSFileProviderEnumerator { } metadatasToFileProviderItems( - allUpdatedMetadatas, remoteInterface: remoteInterface, dbManager: dbManager + allUpdatedMetadatas, + account: account, + remoteInterface: remoteInterface, + dbManager: dbManager ) { updatedItems in if !updatedItems.isEmpty { diff --git a/Sources/NextcloudFileProviderKit/Enumeration/RemoteChangeObserver.swift b/Sources/NextcloudFileProviderKit/Enumeration/RemoteChangeObserver.swift index a64b425..5030ca7 100644 --- a/Sources/NextcloudFileProviderKit/Enumeration/RemoteChangeObserver.swift +++ b/Sources/NextcloudFileProviderKit/Enumeration/RemoteChangeObserver.swift @@ -5,6 +5,7 @@ // Created by Claudio Cambra on 17/4/24. // +import Alamofire import FileProvider import Foundation import NextcloudCapabilitiesKit @@ -13,11 +14,12 @@ import OSLog public let NotifyPushAuthenticatedNotificationName = Notification.Name("NotifyPushAuthenticated") -public class RemoteChangeObserver: NSObject, NKCommonDelegate, URLSessionWebSocketDelegate { +public class RemoteChangeObserver: NSObject, NextcloudKitDelegate, URLSessionWebSocketDelegate { public let remoteInterface: RemoteInterface public let changeNotificationInterface: ChangeNotificationInterface public let domain: NSFileProviderDomain? - public var accountId: String { remoteInterface.account.ncKitAccount } + public var account: Account + public var accountId: String { account.ncKitAccount } public var webSocketPingIntervalNanoseconds: UInt64 = 3 * 1_000_000_000 public var webSocketReconfigureIntervalNanoseconds: UInt64 = 1 * 1_000_000_000 @@ -60,10 +62,12 @@ public class RemoteChangeObserver: NSObject, NKCommonDelegate, URLSessionWebSock } public init( + account: Account, remoteInterface: RemoteInterface, changeNotificationInterface: ChangeNotificationInterface, domain: NSFileProviderDomain? ) { + self.account = account self.remoteInterface = remoteInterface self.changeNotificationInterface = changeNotificationInterface self.domain = domain @@ -130,7 +134,8 @@ public class RemoteChangeObserver: NSObject, NKCommonDelegate, URLSessionWebSock private func configureNotifyPush() async { let (_, capabilitiesData, error) = await remoteInterface.fetchCapabilities( - options: .init(), + account: account, + options: .init(), taskHandler: { task in if let domain = self.domain { NSFileProviderManager(for: domain)?.register( @@ -199,8 +204,8 @@ public class RemoteChangeObserver: NSObject, NKCommonDelegate, URLSessionWebSock logger.debug("Received auth challenge with method: \(authMethod, privacy: .public)") if authMethod == NSURLAuthenticationMethodHTTPBasic { let credential = URLCredential( - user: remoteInterface.account.username, - password: remoteInterface.account.password, + user: account.username, + password: account.password, persistence: .forSession ) completionHandler(.useCredential, credential) @@ -245,8 +250,8 @@ public class RemoteChangeObserver: NSObject, NKCommonDelegate, URLSessionWebSock private func authenticateWebSocket() async { do { - try await webSocketTask?.send(.string(remoteInterface.account.username)) - try await webSocketTask?.send(.string(remoteInterface.account.password)) + try await webSocketTask?.send(.string(account.username)) + try await webSocketTask?.send(.string(account.password)) } catch let error { logger.error( """ @@ -370,7 +375,63 @@ public class RemoteChangeObserver: NSObject, NKCommonDelegate, URLSessionWebSock } } + // MARK: - NextcloudKitDelegate methods + public func networkReachabilityObserver(_ typeReachability: NKCommon.TypeReachability) { networkReachability = typeReachability } + + public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { } + + public func downloadProgress( + _ progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String, + session: URLSession, + task: URLSessionTask + ) { } + + public func uploadProgress( + _ progress: Float, + totalBytes: Int64, + totalBytesExpected: Int64, + fileName: String, + serverUrl: String, + session: URLSession, + task: URLSessionTask + ) { } + + public func downloadingFinish( + _ session: URLSession, + downloadTask: URLSessionDownloadTask, + didFinishDownloadingTo location: URL + ) { } + + public func downloadComplete( + fileName: String, + serverUrl: String, + etag: String?, + date: Date?, + dateLastModified: Date?, + length: Int64, + task: URLSessionTask, + error: NKError + ) { } + + public func uploadComplete( + fileName: String, + serverUrl: String, + ocId: String?, + etag: String?, + date: Date?, + size: Int64, + task: URLSessionTask, + error: NKError + ) { } + + public func request( + _ request: Alamofire.DataRequest, didParseResponse response: Alamofire.AFDataResponse + ) { } } diff --git a/Sources/NextcloudFileProviderKit/Interface/NextcloudKit+RemoteInterface.swift b/Sources/NextcloudFileProviderKit/Interface/NextcloudKit+RemoteInterface.swift index 9426fc0..a270d66 100644 --- a/Sources/NextcloudFileProviderKit/Interface/NextcloudKit+RemoteInterface.swift +++ b/Sources/NextcloudFileProviderKit/Interface/NextcloudKit+RemoteInterface.swift @@ -12,31 +12,24 @@ import NextcloudKit extension NextcloudKit: RemoteInterface { - public var account: Account { - Account( - user: nkCommonInstance.user, - id: nkCommonInstance.userId, - serverUrl: nkCommonInstance.urlBase, - password: nkCommonInstance.password - ) - } - - public func setDelegate(_ delegate: any NKCommonDelegate) { + public func setDelegate(_ delegate: any NextcloudKitDelegate) { setup(delegate: delegate) } public func createFolder( remotePath: String, + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (URLSessionTask) -> Void = { _ in } ) async -> (account: String, ocId: String?, date: NSDate?, error: NKError) { return await withCheckedContinuation { continuation in createFolder( serverUrlFileName: remotePath, + account: account.ncKitAccount, options: options, taskHandler: taskHandler - ) { account, ocId, date, error in - continuation.resume(returning: (account, ocId, date, error)) + ) { account, ocId, date, _, error in + continuation.resume(returning: (account, ocId, date as NSDate?, error)) } } } @@ -46,6 +39,7 @@ extension NextcloudKit: RemoteInterface { localPath: String, creationDate: Date? = nil, modificationDate: Date? = nil, + account: Account, options: NKRequestOptions = .init(), requestHandler: @escaping (UploadRequest) -> Void = { _ in }, taskHandler: @escaping (URLSessionTask) -> Void = { _ in }, @@ -56,7 +50,7 @@ extension NextcloudKit: RemoteInterface { etag: String?, date: NSDate?, size: Int64, - allHeaderFields: [AnyHashable : Any]?, + response: HTTPURLResponse?, afError: AFError?, remoteError: NKError ) { @@ -66,18 +60,19 @@ extension NextcloudKit: RemoteInterface { fileNameLocalPath: localPath, dateCreationFile: creationDate, dateModificationFile: modificationDate, + account: account.ncKitAccount, options: options, requestHandler: requestHandler, taskHandler: taskHandler, progressHandler: progressHandler - ) { account, ocId, etag, date, size, allHeaderFields, afError, nkError in + ) { account, ocId, etag, date, size, response, afError, nkError in continuation.resume(returning: ( account, ocId, etag, - date, + date as NSDate?, size, - allHeaderFields, + response?.response, afError, nkError )) @@ -89,18 +84,20 @@ extension NextcloudKit: RemoteInterface { remotePathSource: String, remotePathDestination: String, overwrite: Bool, + account: Account, options: NKRequestOptions, taskHandler: @escaping (URLSessionTask) -> Void - ) async -> (account: String, error: NKError) { + ) async -> (account: String, data: Data?, error: NKError) { return await withCheckedContinuation { continuation in moveFileOrFolder( serverUrlFileNameSource: remotePathSource, serverUrlFileNameDestination: remotePathDestination, overwrite: overwrite, + account: account.ncKitAccount, options: options, taskHandler: taskHandler - ) { account, error in - continuation.resume(returning: (account, error)) + ) { account, data, error in + continuation.resume(returning: (account, data?.data, error)) } } } @@ -108,6 +105,7 @@ extension NextcloudKit: RemoteInterface { public func download( remotePath: String, localPath: String, + account: Account, options: NKRequestOptions = .init(), requestHandler: @escaping (DownloadRequest) -> Void = { _ in }, taskHandler: @escaping (URLSessionTask) -> Void = { _ in }, @@ -117,7 +115,7 @@ extension NextcloudKit: RemoteInterface { etag: String?, date: NSDate?, length: Int64, - allHeaderFields: [AnyHashable : Any]?, + response: HTTPURLResponse?, afError: AFError?, remoteError: NKError ) { @@ -125,17 +123,18 @@ extension NextcloudKit: RemoteInterface { download( serverUrlFileName: remotePath, fileNameLocalPath: localPath, + account: account.ncKitAccount, options: options, requestHandler: requestHandler, taskHandler: taskHandler, progressHandler: progressHandler - ) { account, etag, date, length, allHeaderFields, afError, remoteError in + ) { account, etag, date, length, data, afError, remoteError in continuation.resume(returning: ( account, etag, - date, + date as NSDate?, length, - allHeaderFields, + data?.response, afError, remoteError )) @@ -149,6 +148,7 @@ extension NextcloudKit: RemoteInterface { showHiddenFiles: Bool = false, includeHiddenFiles: [String] = [], requestBody: Data? = nil, + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (URLSessionTask) -> Void = { _ in } ) async -> ( @@ -161,69 +161,79 @@ extension NextcloudKit: RemoteInterface { showHiddenFiles: showHiddenFiles, includeHiddenFiles: includeHiddenFiles, requestBody: requestBody, + account: account.ncKitAccount, options: options, taskHandler: taskHandler ) { account, files, data, error in - continuation.resume(returning: (account, files, data, error)) + continuation.resume(returning: (account, files ?? [], data?.data, error)) } } } public func delete( remotePath: String, + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (URLSessionTask) -> Void = { _ in } - ) async -> (account: String, error: NKError) { + ) async -> (account: String, response: HTTPURLResponse?, error: NKError) { return await withCheckedContinuation { continuation in - deleteFileOrFolder(serverUrlFileName: remotePath) { account, error in - continuation.resume(returning: (account, error)) + deleteFileOrFolder( + serverUrlFileName: remotePath, account: account.ncKitAccount + ) { account, response, error in + continuation.resume(returning: (account, response?.response, error)) } } } public func downloadThumbnail( - url: URL, options: NKRequestOptions, taskHandler: @escaping (URLSessionTask) -> Void + url: URL, + account: Account, + options: NKRequestOptions, + taskHandler: @escaping (URLSessionTask) -> Void ) async -> (account: String, data: Data?, error: NKError) { await withCheckedContinuation { continuation in - getPreview( - url: url, options: options, taskHandler: taskHandler + downloadPreview( + url: url, account: account.ncKitAccount, options: options, taskHandler: taskHandler ) { account, data, error in - continuation.resume(returning: (account, data, error)) + continuation.resume(returning: (account, data?.data, error)) } } } public func fetchCapabilities( + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> (account: String, data: Data?, error: NKError) { return await withCheckedContinuation { continuation in - getCapabilities(options: options, taskHandler: taskHandler) { account, data, error in - continuation.resume(returning: (account, data, error)) + getCapabilities(account: account.ncKitAccount, options: options, taskHandler: taskHandler) { account, data, error in + continuation.resume(returning: (account, data?.data, error)) } } } public func fetchUserProfile( + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> (account: String, userProfile: NKUserProfile?, data: Data?, error: NKError) { return await withCheckedContinuation { continuation in getUserProfile( - options: options, taskHandler: taskHandler + account: account.ncKitAccount, options: options, taskHandler: taskHandler ) { account, userProfile, data, error in - continuation.resume(returning: (account, userProfile, data, error)) + continuation.resume(returning: (account, userProfile, data?.data, error)) } } } public func tryAuthenticationAttempt( + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in } ) async -> AuthenticationAttemptResultState { // Test by trying to fetch user profile let (_, _, _, error) = - await fetchUserProfile(options: options, taskHandler: taskHandler) + await fetchUserProfile(account: account, options: options, taskHandler: taskHandler) if error == .success { return .success diff --git a/Sources/NextcloudFileProviderKit/Interface/RemoteInterface.swift b/Sources/NextcloudFileProviderKit/Interface/RemoteInterface.swift index 461042e..50d2a95 100644 --- a/Sources/NextcloudFileProviderKit/Interface/RemoteInterface.swift +++ b/Sources/NextcloudFileProviderKit/Interface/RemoteInterface.swift @@ -22,12 +22,11 @@ public enum AuthenticationAttemptResultState: Int { public protocol RemoteInterface { - var account: Account { get } - - func setDelegate(_ delegate: NKCommonDelegate) + func setDelegate(_ delegate: NextcloudKitDelegate) func createFolder( remotePath: String, + account: Account, options: NKRequestOptions, taskHandler: @escaping (_ task: URLSessionTask) -> Void ) async -> (account: String, ocId: String?, date: NSDate?, error: NKError) @@ -37,6 +36,7 @@ public protocol RemoteInterface { localPath: String, creationDate: Date?, modificationDate: Date?, + account: Account, options: NKRequestOptions, requestHandler: @escaping (_ request: UploadRequest) -> Void, taskHandler: @escaping (_ task: URLSessionTask) -> Void, @@ -47,8 +47,8 @@ public protocol RemoteInterface { etag: String?, date: NSDate?, size: Int64, - allHeaderFields: [AnyHashable: Any]?, - afError: AFError?, + response: HTTPURLResponse?, + afError: AFError?, remoteError: NKError ) @@ -56,13 +56,15 @@ public protocol RemoteInterface { remotePathSource: String, remotePathDestination: String, overwrite: Bool, + account: Account, options: NKRequestOptions, taskHandler: @escaping (_ task: URLSessionTask) -> Void - ) async -> (account: String, error: NKError) + ) async -> (account: String, data: Data?, error: NKError) func download( remotePath: String, localPath: String, + account: Account, options: NKRequestOptions, requestHandler: @escaping (_ request: DownloadRequest) -> Void, taskHandler: @escaping (_ task: URLSessionTask) -> Void, @@ -72,7 +74,7 @@ public protocol RemoteInterface { etag: String?, date: NSDate?, length: Int64, - allHeaderFields: [AnyHashable: Any]?, + response: HTTPURLResponse?, afError: AFError?, remoteError: NKError ) @@ -83,31 +85,40 @@ public protocol RemoteInterface { showHiddenFiles: Bool, includeHiddenFiles: [String], requestBody: Data?, + account: Account, options: NKRequestOptions, taskHandler: @escaping (_ task: URLSessionTask) -> Void ) async -> (account: String, files: [NKFile], data: Data?, error: NKError) func delete( remotePath: String, + account: Account, options: NKRequestOptions, taskHandler: @escaping (_ task: URLSessionTask) -> Void - ) async -> (account: String, error: NKError) + ) async -> (account: String, response: HTTPURLResponse?, error: NKError) func downloadThumbnail( url: URL, + account: Account, options: NKRequestOptions, taskHandler: @escaping (_ task: URLSessionTask) -> Void ) async -> (account: String, data: Data?, error: NKError) func fetchCapabilities( - options: NKRequestOptions, taskHandler: @escaping (_ task: URLSessionTask) -> Void + account: Account, + options: NKRequestOptions, + taskHandler: @escaping (_ task: URLSessionTask) -> Void ) async -> (account: String, data: Data?, error: NKError) func fetchUserProfile( - options: NKRequestOptions, taskHandler: @escaping (_ task: URLSessionTask) -> Void + account: Account, + options: NKRequestOptions, + taskHandler: @escaping (_ task: URLSessionTask) -> Void ) async -> (account: String, userProfile: NKUserProfile?, data: Data?, error: NKError) func tryAuthenticationAttempt( - options: NKRequestOptions, taskHandler: @escaping (_ task: URLSessionTask) -> Void + account: Account, + options: NKRequestOptions, + taskHandler: @escaping (_ task: URLSessionTask) -> Void ) async -> AuthenticationAttemptResultState } diff --git a/Sources/NextcloudFileProviderKit/Item/Item+Create.swift b/Sources/NextcloudFileProviderKit/Item/Item+Create.swift index d0f9828..5591db4 100644 --- a/Sources/NextcloudFileProviderKit/Item/Item+Create.swift +++ b/Sources/NextcloudFileProviderKit/Item/Item+Create.swift @@ -17,13 +17,14 @@ extension Item { remotePath: String, parentItemIdentifier: NSFileProviderItemIdentifier, domain: NSFileProviderDomain? = nil, + account: Account, remoteInterface: RemoteInterface, progress: Progress, dbManager: FilesDatabaseManager ) async -> (Item?, Error?) { - let (account, _, _, createError) = await remoteInterface.createFolder( - remotePath: remotePath, options: .init(), taskHandler: { task in + let (_, _, _, createError) = await remoteInterface.createFolder( + remotePath: remotePath, account: account, options: .init(), taskHandler: { task in if let domain, let itemTemplate { NSFileProviderManager(for: domain)?.register( task, @@ -56,6 +57,7 @@ extension Item { showHiddenFiles: true, includeHiddenFiles: [], requestBody: nil, + account: account, options: .init(), taskHandler: { task in if let domain, let itemTemplate { @@ -92,6 +94,7 @@ extension Item { let fpItem = Item( metadata: directoryMetadata, parentItemIdentifier: parentItemIdentifier, + account: account, remoteInterface: remoteInterface ) @@ -104,15 +107,17 @@ extension Item { itemTemplate: NSFileProviderItem, parentItemRemotePath: String, domain: NSFileProviderDomain? = nil, + account: Account, remoteInterface: RemoteInterface, progress: Progress, dbManager: FilesDatabaseManager ) async -> (Item?, Error?) { - let (account, ocId, etag, date, size, _, _, error) = await remoteInterface.upload( + let (_, ocId, etag, date, size, _, _, error) = await remoteInterface.upload( remotePath: remotePath, localPath: localPath, creationDate: itemTemplate.creationDate as? Date, modificationDate: itemTemplate.contentModificationDate as? Date, + account: account, options: .init(), requestHandler: { progress.setHandlersFromAfRequest($0) }, taskHandler: { task in @@ -151,7 +156,7 @@ extension Item { etag: \(etag ?? "", privacy: .public) date: \(date ?? NSDate(), privacy: .public) size: \(size, privacy: .public), - account: \(account, privacy: .public) + account: \(account.ncKitAccount, privacy: .public) """ ) @@ -168,7 +173,7 @@ extension Item { let newMetadata = ItemMetadata() newMetadata.date = (date ?? NSDate()) as Date newMetadata.etag = etag ?? "" - newMetadata.account = account + newMetadata.account = account.ncKitAccount newMetadata.fileName = itemTemplate.filename newMetadata.fileNameView = itemTemplate.filename newMetadata.ocId = ocId @@ -188,6 +193,7 @@ extension Item { let fpItem = Item( metadata: newMetadata, parentItemIdentifier: itemTemplate.parentItemIdentifier, + account: account, remoteInterface: remoteInterface ) @@ -199,6 +205,7 @@ extension Item { contents: URL, remotePath: String, domain: NSFileProviderDomain? = nil, + account: Account, remoteInterface: RemoteInterface, progress: Progress, dbManager: FilesDatabaseManager @@ -268,7 +275,9 @@ extension Item { """ ) let (_, _, _, createError) = await remoteInterface.createFolder( - remotePath: childRemoteUrl, options: .init(), taskHandler: { task in + remotePath: childRemoteUrl, + account: account, + options: .init(), taskHandler: { task in if let domain { NSFileProviderManager(for: domain)?.register( task, @@ -305,6 +314,7 @@ extension Item { localPath: childUrlPath, creationDate: childUrlAttributes.creationDate, modificationDate: childUrlAttributes.contentModificationDate, + account: account, options: .init(), requestHandler: { progress.setHandlersFromAfRequest($0) }, taskHandler: { task in @@ -337,7 +347,10 @@ extension Item { // After everything, check into what the final state is of each folder now Self.logger.debug("Reading bpi folder at: \(remoteDirectoryPath, privacy: .public)") let (_, _, _, _, readError) = await Enumerator.readServerUrl( - remoteDirectoryPath, remoteInterface: remoteInterface, dbManager: dbManager + remoteDirectoryPath, + account: account, + remoteInterface: remoteInterface, + dbManager: dbManager ) if let readError, readError != .success { @@ -352,14 +365,14 @@ extension Item { } guard let bundleRootMetadata = dbManager.itemMetadata( - account: remoteInterface.account.ncKitAccount, locatedAtRemoteUrl: remotePath + account: account.ncKitAccount, locatedAtRemoteUrl: remotePath ) else { Self.logger.error( """ Could not find directory metadata for bundle or package at: \(remotePath, privacy: .public) of account: - \(remoteInterface.account.ncKitAccount, privacy: .public) + \(account.ncKitAccount, privacy: .public) with contents located at: \(contentsPath, privacy: .public) """ @@ -372,6 +385,7 @@ extension Item { return Item( metadata: bundleRootMetadata, parentItemIdentifier: rootItem.parentItemIdentifier, + account: account, remoteInterface: remoteInterface ) } @@ -383,6 +397,7 @@ extension Item { options: NSFileProviderCreateItemOptions = [], request: NSFileProviderRequest = NSFileProviderRequest(), domain: NSFileProviderDomain? = nil, + account: Account, remoteInterface: RemoteInterface, progress: Progress, dbManager: FilesDatabaseManager = .shared @@ -412,7 +427,7 @@ extension Item { // TODO: Deduplicate if parentItemIdentifier == .rootContainer { - parentItemRemotePath = remoteInterface.account.davFilesUrl + parentItemRemotePath = account.davFilesUrl } else { guard let parentItemMetadata = dbManager.directoryMetadata( ocId: parentItemIdentifier.rawValue @@ -454,6 +469,7 @@ extension Item { remotePath: newServerUrlFileName, parentItemIdentifier: parentItemIdentifier, domain: domain, + account: account, remoteInterface: remoteInterface, progress: isBundleOrPackage ? Progress() : progress, dbManager: dbManager @@ -489,6 +505,7 @@ extension Item { ) let (metadatas, _, _, _, readError) = await Enumerator.readServerUrl( newServerUrlFileName, + account: account, remoteInterface: remoteInterface, dbManager: dbManager, domain: domain, @@ -519,6 +536,7 @@ extension Item { item = Item( metadata: itemMetadata, parentItemIdentifier: parentItemIdentifier, + account: account, remoteInterface: remoteInterface ) } @@ -557,6 +575,7 @@ extension Item { contents: url, remotePath: newServerUrlFileName, domain: domain, + account: account, remoteInterface: remoteInterface, progress: progress, dbManager: dbManager @@ -573,6 +592,7 @@ extension Item { itemTemplate: itemTemplate, parentItemRemotePath: parentItemRemotePath, domain: domain, + account: account, remoteInterface: remoteInterface, progress: progress, dbManager: dbManager diff --git a/Sources/NextcloudFileProviderKit/Item/Item+Delete.swift b/Sources/NextcloudFileProviderKit/Item/Item+Delete.swift index 6ce6a66..879fccb 100644 --- a/Sources/NextcloudFileProviderKit/Item/Item+Delete.swift +++ b/Sources/NextcloudFileProviderKit/Item/Item+Delete.swift @@ -22,8 +22,11 @@ public extension Item { } let ocId = itemIdentifier.rawValue - let (_, error) = await remoteInterface.delete( - remotePath: serverFileNameUrl, options: .init(), taskHandler: { task in + let (_, _, error) = await remoteInterface.delete( + remotePath: serverFileNameUrl, + account: account, + options: .init(), + taskHandler: { task in if let domain { NSFileProviderManager(for: domain)?.register( task, diff --git a/Sources/NextcloudFileProviderKit/Item/Item+Fetch.swift b/Sources/NextcloudFileProviderKit/Item/Item+Fetch.swift index 030d765..9b0fb0b 100644 --- a/Sources/NextcloudFileProviderKit/Item/Item+Fetch.swift +++ b/Sources/NextcloudFileProviderKit/Item/Item+Fetch.swift @@ -28,7 +28,10 @@ public extension Item { while !remoteDirectoryPaths.isEmpty { let remoteDirectoryPath = remoteDirectoryPaths.removeFirst() let (metadatas, _, _, _, readError) = await Enumerator.readServerUrl( - remoteDirectoryPath, remoteInterface: remoteInterface, dbManager: dbManager + remoteDirectoryPath, + account: account, + remoteInterface: remoteInterface, + dbManager: dbManager ) if let readError, readError != .success { @@ -74,6 +77,7 @@ public extension Item { let (_, _, _, _, _, _, error) = await remoteInterface.download( remotePath: remotePath, localPath: childLocalPath, + account: account, options: .init(), requestHandler: { progress.setHandlersFromAfRequest($0) }, taskHandler: { task in @@ -208,6 +212,7 @@ public extension Item { let (_, _, _, _, _, _, error) = await remoteInterface.download( remotePath: serverUrlFileName, localPath: localPath.path, + account: account, options: .init(), requestHandler: { _ in }, taskHandler: { _ in }, @@ -259,6 +264,7 @@ public extension Item { let fpItem = Item( metadata: updatedMetadata, parentItemIdentifier: parentItemIdentifier, + account: account, remoteInterface: remoteInterface ) @@ -286,7 +292,7 @@ public extension Item { ) let (_, data, error) = await remoteInterface.downloadThumbnail( - url: thumbnailUrl, options: .init(), taskHandler: { task in + url: thumbnailUrl, account: account, options: .init(), taskHandler: { task in if let domain { NSFileProviderManager(for: domain)?.register( task, diff --git a/Sources/NextcloudFileProviderKit/Item/Item+Modify.swift b/Sources/NextcloudFileProviderKit/Item/Item+Modify.swift index b2bed93..b2a9b98 100644 --- a/Sources/NextcloudFileProviderKit/Item/Item+Modify.swift +++ b/Sources/NextcloudFileProviderKit/Item/Item+Modify.swift @@ -23,10 +23,11 @@ public extension Item { let ocId = itemIdentifier.rawValue let isFolder = contentType.conforms(to: .directory) let oldRemotePath = metadata.serverUrl + "/" + metadata.fileName - let (_, moveError) = await remoteInterface.move( + let (_, _, moveError) = await remoteInterface.move( remotePathSource: oldRemotePath, remotePathDestination: newRemotePath, overwrite: false, + account: account, options: .init(), taskHandler: { task in if let domain { @@ -82,6 +83,7 @@ public extension Item { let modifiedItem = Item( metadata: newMetadata, parentItemIdentifier: newParentItemIdentifier, + account: account, remoteInterface: remoteInterface ) return (modifiedItem, nil) @@ -135,6 +137,7 @@ public extension Item { localPath: localPath, creationDate: newCreationDate, modificationDate: newContentModificationDate, + account: account, options: .init(), requestHandler: { progress.setHandlersFromAfRequest($0) }, taskHandler: { task in @@ -205,6 +208,7 @@ public extension Item { let modifiedItem = Item( metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, + account: account, remoteInterface: remoteInterface ) return (modifiedItem, nil) @@ -254,7 +258,10 @@ public extension Item { while !directoriesToRead.isEmpty { let remoteDirectoryPath = directoriesToRead.removeFirst() let (metadatas, _, _, _, readError) = await Enumerator.readServerUrl( - remoteDirectoryPath, remoteInterface: remoteInterface, dbManager: dbManager + remoteDirectoryPath, + account: account, + remoteInterface: remoteInterface, + dbManager: dbManager ) // Important note -- the enumerator will import found items' metadata into the database. // This is important for when we want to start deleting stale items and want to avoid trying @@ -355,7 +362,10 @@ public extension Item { """ ) let (_, _, _, createError) = await remoteInterface.createFolder( - remotePath: childRemoteUrl, options: .init(), taskHandler: { task in + remotePath: childRemoteUrl, + account: account, + options: .init(), + taskHandler: { task in if let domain { NSFileProviderManager(for: domain)?.register( task, @@ -388,6 +398,7 @@ public extension Item { localPath: childUrlPath, creationDate: childUrlAttributes.creationDate, modificationDate: childUrlAttributes.contentModificationDate, + account: account, options: .init(), requestHandler: { progress.setHandlersFromAfRequest($0) }, taskHandler: { task in @@ -421,8 +432,11 @@ public extension Item { let staleItemMetadata = staleItem.value guard dbManager.itemMetadataFromOcId(staleItemMetadata.ocId) != nil else { continue } - let (_, deleteError) = await remoteInterface.delete( - remotePath: staleItem.key, options: .init(), taskHandler: { task in + let (_, _, deleteError) = await remoteInterface.delete( + remotePath: staleItem.key, + account: account, + options: .init(), + taskHandler: { task in if let domain { NSFileProviderManager(for: domain)?.register( task, @@ -454,7 +468,10 @@ public extension Item { for remoteDirectoryPath in remoteDirectoriesPaths { // After everything, check into what the final state is of each folder now let (_, _, _, _, readError) = await Enumerator.readServerUrl( - remoteDirectoryPath, remoteInterface: remoteInterface, dbManager: dbManager + remoteDirectoryPath, + account: account, + remoteInterface: remoteInterface, + dbManager: dbManager ) if let readError, readError != .success { @@ -485,6 +502,7 @@ public extension Item { return Item( metadata: bundleRootMetadata, parentItemIdentifier: parentItemIdentifier, + account: account, remoteInterface: remoteInterface ) } @@ -530,7 +548,7 @@ public extension Item { // remote changes and then, upon user interaction, will try to modify the item. // That is, if the parent item has changed at all (it might not have) if parentItemIdentifier == .rootContainer { - parentItemServerUrl = remoteInterface.account.davFilesUrl + parentItemServerUrl = account.davFilesUrl } else { guard let parentItemMetadata = dbManager.directoryMetadata( ocId: parentItemIdentifier.rawValue diff --git a/Sources/NextcloudFileProviderKit/Item/Item.swift b/Sources/NextcloudFileProviderKit/Item/Item.swift index a734fbe..76072c5 100644 --- a/Sources/NextcloudFileProviderKit/Item/Item.swift +++ b/Sources/NextcloudFileProviderKit/Item/Item.swift @@ -27,6 +27,7 @@ public class Item: NSObject, NSFileProviderItem { public let metadata: ItemMetadata public let parentItemIdentifier: NSFileProviderItemIdentifier + public let account: Account public let remoteInterface: RemoteInterface public var itemIdentifier: NSFileProviderItemIdentifier { @@ -152,7 +153,7 @@ public class Item: NSObject, NSFileProviderItem { public var fileSystemFlags: NSFileProviderFileSystemFlags { if metadata.lock, - (metadata.lockOwnerType != 0 || metadata.lockOwner != remoteInterface.account.username), + (metadata.lockOwnerType != 0 || metadata.lockOwner != account.username), metadata.lockTimeOut ?? Date() > Date() { return [.userReadable] @@ -179,18 +180,19 @@ public class Item: NSObject, NSFileProviderItem { #endif } - public static func rootContainer(remoteInterface: RemoteInterface) -> Item { + public static func rootContainer(account: Account, remoteInterface: RemoteInterface) -> Item { let metadata = ItemMetadata() - metadata.account = remoteInterface.account.ncKitAccount + metadata.account = account.ncKitAccount metadata.directory = true metadata.ocId = NSFileProviderItemIdentifier.rootContainer.rawValue metadata.fileName = "/" metadata.fileNameView = "/" - metadata.serverUrl = remoteInterface.account.davFilesUrl + metadata.serverUrl = account.davFilesUrl metadata.classFile = NKCommon.TypeClassFile.directory.rawValue return Item( metadata: metadata, parentItemIdentifier: .rootContainer, + account: account, remoteInterface: remoteInterface ) } @@ -200,22 +202,25 @@ public class Item: NSObject, NSFileProviderItem { public required init( metadata: ItemMetadata, parentItemIdentifier: NSFileProviderItemIdentifier, + account: Account, remoteInterface: RemoteInterface ) { self.metadata = ItemMetadata(value: metadata) // Safeguard against active items self.parentItemIdentifier = parentItemIdentifier + self.account = account self.remoteInterface = remoteInterface super.init() } public static func storedItem( identifier: NSFileProviderItemIdentifier, + account: Account, remoteInterface: RemoteInterface, dbManager: FilesDatabaseManager = .shared ) -> Item? { // resolve the given identifier to a record in the model guard identifier != .rootContainer else { - return Item.rootContainer(remoteInterface: remoteInterface) + return Item.rootContainer(account: account, remoteInterface: remoteInterface) } guard let metadata = dbManager.itemMetadataFromFileProviderItemIdentifier(identifier), @@ -225,6 +230,7 @@ public class Item: NSObject, NSFileProviderItem { return Item( metadata: metadata, parentItemIdentifier: parentItemIdentifier, + account: account, remoteInterface: remoteInterface ) } diff --git a/Sources/NextcloudFileProviderKit/Metadata/ItemMetadata+NKFile.swift b/Sources/NextcloudFileProviderKit/Metadata/ItemMetadata+NKFile.swift index bc61153..c34fa79 100644 --- a/Sources/NextcloudFileProviderKit/Metadata/ItemMetadata+NKFile.swift +++ b/Sources/NextcloudFileProviderKit/Metadata/ItemMetadata+NKFile.swift @@ -19,7 +19,7 @@ public extension ItemMetadata { // TODO: Convert to async/await static func metadatasFromDirectoryReadNKFiles( _ files: [NKFile], - account: String, + account: Account, completionHandler: @escaping ( _ directoryMetadata: ItemMetadata, _ childDirectoriesMetadatas: [ItemMetadata], diff --git a/Sources/NextcloudFileProviderKit/Utilities/Account.swift b/Sources/NextcloudFileProviderKit/Utilities/Account.swift index 3660372..78f2ef2 100644 --- a/Sources/NextcloudFileProviderKit/Utilities/Account.swift +++ b/Sources/NextcloudFileProviderKit/Utilities/Account.swift @@ -25,11 +25,15 @@ public struct Account: Equatable { public static let webDavFilesUrlSuffix: String = "/remote.php/dav/files/" public let username, id, password, ncKitAccount, serverUrl, davFilesUrl: String + public static func ncKitAccountString(from username: String, serverUrl: String) -> String { + username + " " + serverUrl + } + public init(user: String, id: String, serverUrl: String, password: String) { username = user self.id = id self.password = password - ncKitAccount = user + " " + serverUrl + ncKitAccount = Self.ncKitAccountString(from: user, serverUrl: serverUrl) self.serverUrl = serverUrl davFilesUrl = serverUrl + Self.webDavFilesUrlSuffix + id } diff --git a/Sources/NextcloudFileProviderKit/Utilities/ThumbnailFetching.swift b/Sources/NextcloudFileProviderKit/Utilities/ThumbnailFetching.swift index 0f6344a..5f7b713 100644 --- a/Sources/NextcloudFileProviderKit/Utilities/ThumbnailFetching.swift +++ b/Sources/NextcloudFileProviderKit/Utilities/ThumbnailFetching.swift @@ -15,6 +15,7 @@ fileprivate let logger = Logger(subsystem: Logger.subsystem, category: "thumbnai public func fetchThumbnails( for itemIdentifiers: [NSFileProviderItemIdentifier], requestedSize size: CGSize, + account: Account, usingRemoteInterface remoteInterface: RemoteInterface, perThumbnailCompletionHandler: @escaping ( NSFileProviderItemIdentifier, @@ -35,7 +36,9 @@ public func fetchThumbnails( for itemIdentifier in itemIdentifiers { guard let item = Item.storedItem( - identifier: itemIdentifier, remoteInterface: remoteInterface + identifier: itemIdentifier, + account: account, + remoteInterface: remoteInterface ) else { logger.error( """ diff --git a/Tests/Interface/MockRemoteInterface.swift b/Tests/Interface/MockRemoteInterface.swift index 4aa6216..295d81e 100644 --- a/Tests/Interface/MockRemoteInterface.swift +++ b/Tests/Interface/MockRemoteInterface.swift @@ -13,19 +13,15 @@ import NextcloudKit fileprivate let mockCapabilities = ##"{"ocs":{"meta":{"status":"ok","statuscode":100,"message":"OK","totalitems":"","itemsperpage":""},"data":{"version":{"major":28,"minor":0,"micro":4,"string":"28.0.4","edition":"","extendedSupport":false},"capabilities":{"core":{"pollinterval":60,"webdav-root":"remote.php\/webdav","reference-api":true,"reference-regex":"(\\s|\\n|^)(https?:\\\/\\\/)((?:[-A-Z0-9+_]+\\.)+[-A-Z]+(?:\\\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\\s|\\n|$)"},"bruteforce":{"delay":0,"allow-listed":false},"files":{"bigfilechunking":true,"blacklisted_files":[".htaccess"],"directEditing":{"url":"https:\/\/mock.nc.com\/ocs\/v2.php\/apps\/files\/api\/v1\/directEditing","etag":"c748e8fc588b54fc5af38c4481a19d20","supportsFileId":true},"comments":true,"undelete":true,"versioning":true,"version_labeling":true,"version_deletion":true},"activity":{"apiv2":["filters","filters-api","previews","rich-strings"]},"circles":{"version":"28.0.0","status":{"globalScale":false},"settings":{"frontendEnabled":true,"allowedCircles":262143,"allowedUserTypes":31,"membersLimit":-1},"circle":{"constants":{"flags":{"1":"Single","2":"Personal","4":"System","8":"Visible","16":"Open","32":"Invite","64":"Join Request","128":"Friends","256":"Password Protected","512":"No Owner","1024":"Hidden","2048":"Backend","4096":"Local","8192":"Root","16384":"Circle Invite","32768":"Federated","65536":"Mount point"},"source":{"core":{"1":"Nextcloud Account","2":"Nextcloud Group","4":"Email Address","8":"Contact","16":"Circle","10000":"Nextcloud App"},"extra":{"10001":"Circles App","10002":"Admin Command Line"}}},"config":{"coreFlags":[1,2,4],"systemFlags":[512,1024,2048]}},"member":{"constants":{"level":{"1":"Member","4":"Moderator","8":"Admin","9":"Owner"}},"type":{"0":"single","1":"user","2":"group","4":"mail","8":"contact","16":"circle","10000":"app"}}},"ocm":{"enabled":true,"apiVersion":"1.0-proposal1","endPoint":"https:\/\/mock.nc.com\/ocm","resourceTypes":[{"name":"file","shareTypes":["user","group"],"protocols":{"webdav":"\/public.php\/webdav\/"}}]},"dav":{"chunking":"1.0","bulkupload":"1.0"},"deck":{"version":"1.12.2","canCreateBoards":true,"apiVersions":["1.0","1.1"]},"files_sharing":{"api_enabled":true,"public":{"enabled":true,"password":{"enforced":false,"askForOptionalPassword":false},"expire_date":{"enabled":true,"days":7,"enforced":true},"multiple_links":true,"expire_date_internal":{"enabled":false},"expire_date_remote":{"enabled":false},"send_mail":false,"upload":true,"upload_files_drop":true},"resharing":true,"user":{"send_mail":false,"expire_date":{"enabled":true}},"group_sharing":true,"group":{"enabled":true,"expire_date":{"enabled":true}},"default_permissions":31,"federation":{"outgoing":true,"incoming":true,"expire_date":{"enabled":true},"expire_date_supported":{"enabled":true}},"sharee":{"query_lookup_default":false,"always_show_unique":true},"sharebymail":{"enabled":true,"send_password_by_mail":true,"upload_files_drop":{"enabled":true},"password":{"enabled":true,"enforced":false},"expire_date":{"enabled":true,"enforced":true}}},"fulltextsearch":{"remote":true,"providers":[{"id":"deck","name":"Deck"},{"id":"files","name":"Files"}]},"notes":{"api_version":["0.2","1.3"],"version":"4.9.4"},"notifications":{"ocs-endpoints":["list","get","delete","delete-all","icons","rich-strings","action-web","user-status","exists"],"push":["devices","object-data","delete"],"admin-notifications":["ocs","cli"]},"notify_push":{"type":["files","activities","notifications"],"endpoints":{"websocket":"wss:\/\/mock.nc.com\/push\/ws","pre_auth":"https:\/\/mock.nc.com\/apps\/notify_push\/pre_auth"}},"password_policy":{"minLength":10,"enforceNonCommonPassword":true,"enforceNumericCharacters":false,"enforceSpecialCharacters":false,"enforceUpperLowerCase":false,"api":{"generate":"https:\/\/mock.nc.com\/ocs\/v2.php\/apps\/password_policy\/api\/v1\/generate","validate":"https:\/\/mock.nc.com\/ocs\/v2.php\/apps\/password_policy\/api\/v1\/validate"}},"provisioning_api":{"version":"1.18.0","AccountPropertyScopesVersion":2,"AccountPropertyScopesFederatedEnabled":true,"AccountPropertyScopesPublishedEnabled":true},"richdocuments":{"version":"8.3.4","mimetypes":["application\/vnd.oasis.opendocument.text","application\/vnd.oasis.opendocument.spreadsheet","application\/vnd.oasis.opendocument.graphics","application\/vnd.oasis.opendocument.presentation","application\/vnd.oasis.opendocument.text-flat-xml","application\/vnd.oasis.opendocument.spreadsheet-flat-xml","application\/vnd.oasis.opendocument.graphics-flat-xml","application\/vnd.oasis.opendocument.presentation-flat-xml","application\/vnd.lotus-wordpro","application\/vnd.visio","application\/vnd.ms-visio.drawing","application\/vnd.wordperfect","application\/rtf","text\/rtf","application\/msonenote","application\/msword","application\/vnd.openxmlformats-officedocument.wordprocessingml.document","application\/vnd.openxmlformats-officedocument.wordprocessingml.template","application\/vnd.ms-word.document.macroEnabled.12","application\/vnd.ms-word.template.macroEnabled.12","application\/vnd.ms-excel","application\/vnd.openxmlformats-officedocument.spreadsheetml.sheet","application\/vnd.openxmlformats-officedocument.spreadsheetml.template","application\/vnd.ms-excel.sheet.macroEnabled.12","application\/vnd.ms-excel.template.macroEnabled.12","application\/vnd.ms-excel.addin.macroEnabled.12","application\/vnd.ms-excel.sheet.binary.macroEnabled.12","application\/vnd.ms-powerpoint","application\/vnd.openxmlformats-officedocument.presentationml.presentation","application\/vnd.openxmlformats-officedocument.presentationml.template","application\/vnd.openxmlformats-officedocument.presentationml.slideshow","application\/vnd.ms-powerpoint.addin.macroEnabled.12","application\/vnd.ms-powerpoint.presentation.macroEnabled.12","application\/vnd.ms-powerpoint.template.macroEnabled.12","application\/vnd.ms-powerpoint.slideshow.macroEnabled.12","text\/csv"],"mimetypesNoDefaultOpen":["image\/svg+xml","application\/pdf","text\/plain","text\/spreadsheet"],"mimetypesSecureView":[],"collabora":{"convert-to":{"available":true,"endpoint":"\/cool\/convert-to"},"hasMobileSupport":true,"hasProxyPrefix":false,"hasTemplateSaveAs":false,"hasTemplateSource":true,"hasWASMSupport":false,"hasZoteroSupport":true,"productName":"Collabora Online Development Edition","productVersion":"23.05.10.1","productVersionHash":"baa6eef","serverId":"8bee4df3"},"direct_editing":true,"templates":true,"productName":"Nextcloud Office","editonline_endpoint":"https:\/\/mock.nc.com\/apps\/richdocuments\/editonline","config":{"wopi_url":"https:\/\/mock.nc.com\/","public_wopi_url":"https:\/\/mock.nc.com","wopi_callback_url":"","disable_certificate_verification":null,"edit_groups":null,"use_groups":null,"doc_format":null,"timeout":15}},"spreed":{"features":["audio","video","chat-v2","conversation-v4","guest-signaling","empty-group-room","guest-display-names","multi-room-users","favorites","last-room-activity","no-ping","system-messages","delete-messages","mention-flag","in-call-flags","conversation-call-flags","notification-levels","invite-groups-and-mails","locked-one-to-one-rooms","read-only-rooms","listable-rooms","chat-read-marker","chat-unread","webinary-lobby","start-call-flag","chat-replies","circles-support","force-mute","sip-support","sip-support-nopin","chat-read-status","phonebook-search","raise-hand","room-description","rich-object-sharing","temp-user-avatar-api","geo-location-sharing","voice-message-sharing","signaling-v3","publishing-permissions","clear-history","direct-mention-flag","notification-calls","conversation-permissions","rich-object-list-media","rich-object-delete","unified-search","chat-permission","silent-send","silent-call","send-call-notification","talk-polls","breakout-rooms-v1","recording-v1","avatar","chat-get-context","single-conversation-status","chat-keep-notifications","typing-privacy","remind-me-later","bots-v1","markdown-messages","media-caption","session-state","note-to-self","recording-consent","sip-support-dialout","message-expiration","reactions","chat-reference-id"],"config":{"attachments":{"allowed":true,"folder":"\/Talk"},"call":{"enabled":true,"breakout-rooms":true,"recording":false,"recording-consent":0,"supported-reactions":["\u2764\ufe0f","\ud83c\udf89","\ud83d\udc4f","\ud83d\udc4d","\ud83d\udc4e","\ud83d\ude02","\ud83e\udd29","\ud83e\udd14","\ud83d\ude32","\ud83d\ude25"],"sip-enabled":false,"sip-dialout-enabled":false,"predefined-backgrounds":["1_office.jpg","2_home.jpg","3_abstract.jpg","4_beach.jpg","5_park.jpg","6_theater.jpg","7_library.jpg","8_space_station.jpg"],"can-upload-background":true,"can-enable-sip":true},"chat":{"max-length":32000,"read-privacy":0,"has-translation-providers":false,"typing-privacy":0},"conversations":{"can-create":true},"previews":{"max-gif-size":3145728},"signaling":{"session-ping-limit":200,"hello-v2-token-key":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECOu2NBMo4juGx6hHNIGa550gGaxN\nzqe\/TPxsX3QRjCrkyvdQaltjuRt\/9PddhpbMxcJSzwVLqZRVHylfllD8pg==\n-----END PUBLIC KEY-----\n"}},"version":"18.0.7"},"systemtags":{"enabled":true},"theming":{"name":"Nextcloud","url":"https:\/\/nextcloud.com","slogan":"a safe home for all your data","color":"#6ea68f","color-text":"#000000","color-element":"#6ea68f","color-element-bright":"#6ea68f","color-element-dark":"#6ea68f","logo":"https:\/\/mock.nc.com\/core\/img\/logo\/logo.svg?v=1","background":"#6ea68f","background-plain":true,"background-default":true,"logoheader":"https:\/\/mock.nc.com\/core\/img\/logo\/logo.svg?v=1","favicon":"https:\/\/mock.nc.com\/core\/img\/logo\/logo.svg?v=1"},"user_status":{"enabled":true,"restore":true,"supports_emoji":true},"weather_status":{"enabled":true}}}}}"## public class MockRemoteInterface: RemoteInterface { - public var account: Account public var capabilities = mockCapabilities public var rootItem: MockRemoteItem? - public var delegate: (any NKCommonDelegate)? + public var delegate: (any NextcloudKitDelegate)? - private var accountString: String { account.ncKitAccount } - - public init(account: Account, rootItem: MockRemoteItem? = nil) { - self.account = account + public init(rootItem: MockRemoteItem? = nil) { self.rootItem = rootItem } - func sanitisedPath(_ path: String) -> String { + func sanitisedPath(_ path: String, account: Account) -> String { var sanitisedPath = path let filesPath = account.davFilesUrl if sanitisedPath.hasPrefix(filesPath) { @@ -42,10 +38,10 @@ public class MockRemoteInterface: RemoteInterface { return sanitisedPath } - func item(remotePath: String) -> MockRemoteItem? { + func item(remotePath: String, account: Account) -> MockRemoteItem? { guard let rootItem, !remotePath.isEmpty else { return nil } - let sanitisedPath = sanitisedPath(remotePath) + let sanitisedPath = sanitisedPath(remotePath, account: account) guard sanitisedPath != "/" else { return rootItem } var pathComponents = sanitisedPath.components(separatedBy: "/") @@ -65,8 +61,8 @@ public class MockRemoteInterface: RemoteInterface { return nil } - func parentPath(path: String) -> String { - let sanitisedPath = sanitisedPath(path) + func parentPath(path: String, account: Account) -> String { + let sanitisedPath = sanitisedPath(path, account: account) var pathComponents = sanitisedPath.components(separatedBy: "/") if pathComponents.first?.isEmpty == true { pathComponents.removeFirst() } guard !pathComponents.isEmpty else { return "/" } @@ -74,9 +70,9 @@ public class MockRemoteInterface: RemoteInterface { return account.davFilesUrl + "/" + pathComponents.joined(separator: "/") } - func parentItem(path: String) -> MockRemoteItem? { - let parentRemotePath = parentPath(path: path) - return item(remotePath: parentRemotePath) + func parentItem(path: String, account: Account) -> MockRemoteItem? { + let parentRemotePath = parentPath(path: path, account: account) + return item(remotePath: parentRemotePath, account: account) } func randomIdentifier() -> String { @@ -94,12 +90,13 @@ public class MockRemoteInterface: RemoteInterface { return name } - public func setDelegate(_ delegate: any NKCommonDelegate) { + public func setDelegate(_ delegate: any NextcloudKitDelegate) { self.delegate = delegate } public func createFolder( remotePath: String, + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (URLSessionTask) -> Void = { _ in } ) async -> (account: String, ocId: String?, date: NSDate?, error: NKError) { @@ -107,7 +104,7 @@ public class MockRemoteInterface: RemoteInterface { do { itemName = try name(from: remotePath) } catch { - return (accountString, nil, nil, .urlError) + return (account.ncKitAccount, nil, nil, .urlError) } let item = MockRemoteItem( @@ -120,13 +117,13 @@ public class MockRemoteInterface: RemoteInterface { userId: account.id, serverUrl: account.serverUrl ) - guard let parent = parentItem(path: remotePath) else { - return (accountString, nil, nil, .urlError) + guard let parent = parentItem(path: remotePath, account: account) else { + return (account.ncKitAccount, nil, nil, .urlError) } parent.children.append(item) item.parent = parent - return (accountString, item.identifier, item.creationDate as NSDate, .success) + return (account.ncKitAccount, item.identifier, item.creationDate as NSDate, .success) } public func upload( @@ -134,6 +131,7 @@ public class MockRemoteInterface: RemoteInterface { localPath: String, creationDate: Date? = .init(), modificationDate: Date? = .init(), + account: Account, options: NKRequestOptions = .init(), requestHandler: @escaping (Alamofire.UploadRequest) -> Void = { _ in }, taskHandler: @escaping (URLSessionTask) -> Void = { _ in }, @@ -144,7 +142,7 @@ public class MockRemoteInterface: RemoteInterface { etag: String?, date: NSDate?, size: Int64, - allHeaderFields: [AnyHashable : Any]?, + response: HTTPURLResponse?, afError: AFError?, remoteError: NKError ) { @@ -153,7 +151,7 @@ public class MockRemoteInterface: RemoteInterface { itemName = try name(from: localPath) debugPrint("Handling item upload:", itemName) } catch { - return (accountString, nil, nil, nil, 0, nil, nil, .urlError) + return (account.ncKitAccount, nil, nil, nil, 0, nil, nil, .urlError) } let itemLocalUrl = URL(fileURLWithPath: localPath) @@ -162,11 +160,11 @@ public class MockRemoteInterface: RemoteInterface { itemData = try Data(contentsOf: itemLocalUrl) debugPrint("Acquired data:", itemData) } catch { - return (accountString, nil, nil, nil, 0, nil, nil, .urlError) + return (account.ncKitAccount, nil, nil, nil, 0, nil, nil, .urlError) } - guard let parent = parentItem(path: remotePath) else { - return (accountString, nil, nil, nil, 0, nil, nil, .urlError) + guard let parent = parentItem(path: remotePath, account: account) else { + return (account.ncKitAccount, nil, nil, nil, 0, nil, nil, .urlError) } debugPrint("Parent is:", parent.remotePath) @@ -196,7 +194,7 @@ public class MockRemoteInterface: RemoteInterface { } return ( - accountString, + account.ncKitAccount, item.identifier, item.versionIdentifier, item.modificationDate as NSDate, @@ -211,14 +209,15 @@ public class MockRemoteInterface: RemoteInterface { remotePathSource: String, remotePathDestination: String, overwrite: Bool = false, + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (URLSessionTask) -> Void = { _ in } - ) async -> (account: String, error: NKError) { + ) async -> (account: String, data: Data?, error: NKError) { guard let itemNewName = try? name(from: remotePathDestination), - let sourceItem = item(remotePath: remotePathSource), - let destinationParent = parentItem(path: remotePathDestination), + let sourceItem = item(remotePath: remotePathSource, account: account), + let destinationParent = parentItem(path: remotePathDestination, account: account), (overwrite || !destinationParent.children.contains(where: { $0.name == itemNewName })) - else { return (accountString, .urlError) } + else { return (account.ncKitAccount, nil, .urlError) } sourceItem.name = itemNewName sourceItem.parent?.children.removeAll(where: { $0.identifier == sourceItem.identifier }) @@ -244,12 +243,13 @@ public class MockRemoteInterface: RemoteInterface { children = nextChildren } - return (accountString, .success) + return (account.ncKitAccount, nil, .success) } public func download( remotePath: String, localPath: String, + account: Account, options: NKRequestOptions = .init(), requestHandler: @escaping (DownloadRequest) -> Void = { _ in }, taskHandler: @escaping (URLSessionTask) -> Void = { _ in }, @@ -259,12 +259,12 @@ public class MockRemoteInterface: RemoteInterface { etag: String?, date: NSDate?, length: Int64, - allHeaderFields: [AnyHashable : Any]?, + response: HTTPURLResponse?, afError: AFError?, remoteError: NKError ) { - guard let item = item(remotePath: remotePath) else { - return (accountString, nil, nil, 0, nil, nil, .urlError) + guard let item = item(remotePath: remotePath, account: account) else { + return (account.ncKitAccount, nil, nil, 0, nil, nil, .urlError) } let localUrl = URL(fileURLWithPath: localPath) @@ -279,11 +279,11 @@ public class MockRemoteInterface: RemoteInterface { } } catch let error { print("Could not write item data: \(error)") - return (accountString, nil, nil, 0, nil, nil, .urlError) + return (account.ncKitAccount, nil, nil, 0, nil, nil, .urlError) } return ( - accountString, + account.ncKitAccount, item.versionIdentifier, item.creationDate as NSDate, item.size, @@ -299,18 +299,19 @@ public class MockRemoteInterface: RemoteInterface { showHiddenFiles: Bool = true, includeHiddenFiles: [String] = [], requestBody: Data? = nil, + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (URLSessionTask) -> Void = { _ in } ) async -> (account: String, files: [NKFile], data: Data?, error: NKError) { - guard let item = item(remotePath: remotePath) else { - return (accountString, [], nil, .urlError) + guard let item = item(remotePath: remotePath, account: account) else { + return (account.ncKitAccount, [], nil, .urlError) } switch depth { case .target: - return (accountString, [item.nkfile], nil, .success) + return (account.ncKitAccount, [item.nkfile], nil, .success) case .targetAndDirectChildren: - return (accountString, [item.nkfile] + item.children.map { $0.nkfile }, nil, .success) + return (account.ncKitAccount, [item.nkfile] + item.children.map { $0.nkfile }, nil, .success) case .targetAndAllChildren: var files = [NKFile]() var queue = [item] @@ -322,42 +323,47 @@ public class MockRemoteInterface: RemoteInterface { } queue = nextQueue } - return (accountString, files, nil, .success) + return (account.ncKitAccount, files, nil, .success) } } public func delete( remotePath: String, + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (URLSessionTask) -> Void = { _ in } - ) async -> (account: String, error: NKError) { - guard let item = item(remotePath: remotePath) else { - return (accountString, .urlError) + ) async -> (account: String, response: HTTPURLResponse?, error: NKError) { + guard let item = item(remotePath: remotePath, account: account) else { + return (account.ncKitAccount, nil, .urlError) } item.children = [] item.parent?.children.removeAll(where: { $0.identifier == item.identifier }) item.parent = nil - return (accountString, .success) + + return (account.ncKitAccount, nil, .success) } public func downloadThumbnail( url: URL, + account: Account, options: NKRequestOptions, taskHandler: @escaping (URLSessionTask) -> Void ) async -> (account: String, data: Data?, error: NKError) { // TODO: Implement downloadThumbnail - return (accountString, nil, .success) + return (account.ncKitAccount, nil, .success) } public func fetchCapabilities( + account: Account, options: NKRequestOptions, taskHandler: @escaping (URLSessionTask) -> Void ) async -> (account: String, data: Data?, error: NKError) { - return (accountString, capabilities.data(using: .utf8), .success) + return (account.ncKitAccount, capabilities.data(using: .utf8), .success) } public func fetchUserProfile( + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (URLSessionTask) -> Void = { _ in } ) async -> (account: String, userProfile: NKUserProfile?, data: Data?, error: NKError) { @@ -370,6 +376,7 @@ public class MockRemoteInterface: RemoteInterface { } public func tryAuthenticationAttempt( + account: Account, options: NKRequestOptions = .init(), taskHandler: @escaping (URLSessionTask) -> Void = { _ in } ) async -> AuthenticationAttemptResultState { diff --git a/Tests/Interface/MockRemoteItem.swift b/Tests/Interface/MockRemoteItem.swift index 013774c..496b88f 100644 --- a/Tests/Interface/MockRemoteItem.swift +++ b/Tests/Interface/MockRemoteItem.swift @@ -32,7 +32,7 @@ public class MockRemoteItem: Equatable { let file = NKFile() file.fileName = name file.size = size - file.date = creationDate as NSDate + file.date = creationDate file.directory = directory file.etag = versionIdentifier file.ocId = identifier diff --git a/Tests/InterfaceTests/MockRemoteInterfaceTests.swift b/Tests/InterfaceTests/MockRemoteInterfaceTests.swift index 6c68966..9876278 100644 --- a/Tests/InterfaceTests/MockRemoteInterfaceTests.swift +++ b/Tests/InterfaceTests/MockRemoteInterfaceTests.swift @@ -30,7 +30,7 @@ final class MockRemoteInterfaceTests: XCTestCase { } func testItemForRemotePath() { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let itemA = MockRemoteItem( identifier: "a", versionIdentifier: "a", @@ -83,34 +83,42 @@ final class MockRemoteInterfaceTests: XCTestCase { itemA_B.children = [targetItem] targetItem.parent = itemA_B - XCTAssertEqual(remoteInterface.item( - remotePath: Self.account.davFilesUrl + "/a/b/target"), targetItem + XCTAssertEqual( + remoteInterface.item( + remotePath: Self.account.davFilesUrl + "/a/b/target", account: Self.account + ), targetItem ) } func testItemForRootPath() { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) - XCTAssertEqual(remoteInterface.item(remotePath: Self.account.davFilesUrl), rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) + XCTAssertEqual( + remoteInterface.item(remotePath: Self.account.davFilesUrl, account: Self.account), rootItem + ) } func testPathParentPath() { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let testPath = Self.account.davFilesUrl + "/a/B/c/d" let expectedPath = Self.account.davFilesUrl + "/a/B/c" - XCTAssertEqual(remoteInterface.parentPath(path: testPath), expectedPath) + XCTAssertEqual( + remoteInterface.parentPath(path: testPath, account: Self.account), expectedPath + ) } func testRootPathParentPath() { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let testPath = Self.account.davFilesUrl + "/" let expectedPath = Self.account.davFilesUrl + "/" - XCTAssertEqual(remoteInterface.parentPath(path: testPath), expectedPath) + XCTAssertEqual( + remoteInterface.parentPath(path: testPath, account: Self.account), expectedPath + ) } func testNameFromPath() throws { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let testPath = Self.account.davFilesUrl + "/a/b/c/d" let expectedName = "d" let name = try remoteInterface.name(from: testPath) @@ -118,40 +126,46 @@ final class MockRemoteInterfaceTests: XCTestCase { } func testCreateFolder() async { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let newFolderAPath = Self.account.davFilesUrl + "/A" let newFolderA_BPath = Self.account.davFilesUrl + "/A/B/" - let resultA = await remoteInterface.createFolder(remotePath: newFolderAPath) + let resultA = await remoteInterface.createFolder( + remotePath: newFolderAPath, account: Self.account + ) XCTAssertEqual(resultA.error, .success) - let resultA_B = await remoteInterface.createFolder(remotePath: newFolderA_BPath) + let resultA_B = await remoteInterface.createFolder( + remotePath: newFolderA_BPath, account: Self.account + ) XCTAssertEqual(resultA_B.error, .success) - let itemA = remoteInterface.item(remotePath: newFolderAPath) + let itemA = remoteInterface.item(remotePath: newFolderAPath, account: Self.account) XCTAssertNotNil(itemA) XCTAssertEqual(itemA?.name, "A") XCTAssertTrue(itemA?.directory ?? false) - let itemA_B = remoteInterface.item(remotePath: newFolderA_BPath) + let itemA_B = remoteInterface.item(remotePath: newFolderA_BPath, account: Self.account) XCTAssertNotNil(itemA_B) XCTAssertEqual(itemA_B?.name, "B") XCTAssertTrue(itemA_B?.directory ?? false) } func testUpload() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let fileUrl = URL.temporaryDirectory.appendingPathComponent("file.txt", conformingTo: .text) let fileData = Data("Hello, World!".utf8) let fileSize = Int64(fileData.count) try fileData.write(to: fileUrl) let result = await remoteInterface.upload( - remotePath: Self.account.davFilesUrl, localPath: fileUrl.path + remotePath: Self.account.davFilesUrl, localPath: fileUrl.path, account: Self.account ) XCTAssertEqual(result.remoteError, .success) - let remoteItem = remoteInterface.item(remotePath: Self.account.davFilesUrl + "/file.txt") + let remoteItem = remoteInterface.item( + remotePath: Self.account.davFilesUrl + "/file.txt", account: Self.account + ) XCTAssertNotNil(remoteItem) XCTAssertEqual(remoteItem?.name, "file.txt") @@ -163,7 +177,7 @@ final class MockRemoteInterfaceTests: XCTestCase { } func testMove() async { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let itemA = MockRemoteItem( identifier: "a", name: "a", @@ -215,7 +229,8 @@ final class MockRemoteInterfaceTests: XCTestCase { let result = await remoteInterface.move( remotePathSource: Self.account.davFilesUrl + "/a/c/target", - remotePathDestination: Self.account.davFilesUrl + "/b/targetRenamed" + remotePathDestination: Self.account.davFilesUrl + "/b/targetRenamed", + account: Self.account ) XCTAssertEqual(result.error, .success) XCTAssertEqual(itemB.children, [targetItem]) @@ -226,7 +241,7 @@ final class MockRemoteInterfaceTests: XCTestCase { } func testDownload() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let fileUrl = FileManager.default.temporaryDirectory.appendingPathComponent( "file.txt", conformingTo: .text ) @@ -235,10 +250,14 @@ final class MockRemoteInterfaceTests: XCTestCase { return } let fileData = Data("Hello, World!".utf8) - let _ = await remoteInterface.upload(remotePath: "/", localPath: fileUrl.path) + let _ = await remoteInterface.upload( + remotePath: "/", localPath: fileUrl.path, account: Self.account + ) let result = await remoteInterface.download( - remotePath: Self.account.davFilesUrl + "/file.txt", localPath: fileUrl.path + remotePath: Self.account.davFilesUrl + "/file.txt", + localPath: fileUrl.path, + account: Self.account ) XCTAssertEqual(result.remoteError, .success) @@ -247,7 +266,7 @@ final class MockRemoteInterfaceTests: XCTestCase { } func testEnumerate() async { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let itemA = MockRemoteItem( identifier: "a", name: "a", @@ -334,18 +353,22 @@ final class MockRemoteInterfaceTests: XCTestCase { itemC_A.children = [itemC_A_A] itemC_A_A.parent = itemC_A - let result = await remoteInterface.enumerate(remotePath: "/", depth: .target) + let result = await remoteInterface.enumerate( + remotePath: "/", depth: .target, account: Self.account + ) XCTAssertEqual(result.error, .success) XCTAssertEqual(result.files.count, 1) let targetRootFile = result.files.first let expectedRoot = remoteInterface.rootItem XCTAssertEqual(targetRootFile?.ocId, expectedRoot?.identifier) XCTAssertEqual(targetRootFile?.fileName, expectedRoot?.name) - XCTAssertEqual(targetRootFile?.date, expectedRoot?.creationDate as? NSDate) + XCTAssertEqual(targetRootFile?.date, expectedRoot?.creationDate) XCTAssertEqual(targetRootFile?.etag, expectedRoot?.versionIdentifier) let resultChildren = await remoteInterface.enumerate( - remotePath: Self.account.davFilesUrl, depth: .targetAndDirectChildren + remotePath: Self.account.davFilesUrl, + depth: .targetAndDirectChildren, + account: Self.account ) XCTAssertEqual(resultChildren.error, .success) XCTAssertEqual(resultChildren.files.count, 4) @@ -360,7 +383,9 @@ final class MockRemoteInterfaceTests: XCTestCase { ) let resultAChildren = await remoteInterface.enumerate( - remotePath: Self.account.davFilesUrl + "/a", depth: .targetAndDirectChildren + remotePath: Self.account.davFilesUrl + "/a", + depth: .targetAndDirectChildren, + account: Self.account ) XCTAssertEqual(resultAChildren.error, .success) XCTAssertEqual(resultAChildren.files.count, 3) @@ -370,7 +395,9 @@ final class MockRemoteInterfaceTests: XCTestCase { ) let resultCChildren = await remoteInterface.enumerate( - remotePath: Self.account.davFilesUrl + "/c", depth: .targetAndDirectChildren + remotePath: Self.account.davFilesUrl + "/c", + depth: .targetAndDirectChildren, + account: Self.account ) XCTAssertEqual(resultCChildren.error, .success) XCTAssertEqual(resultCChildren.files.count, 2) @@ -380,7 +407,9 @@ final class MockRemoteInterfaceTests: XCTestCase { ) let resultCRecursiveChildren = await remoteInterface.enumerate( - remotePath: Self.account.davFilesUrl + "/c", depth: .targetAndAllChildren + remotePath: Self.account.davFilesUrl + "/c", + depth: .targetAndAllChildren, + account: Self.account ) XCTAssertEqual(resultCRecursiveChildren.error, .success) XCTAssertEqual(resultCRecursiveChildren.files.count, 3) @@ -391,7 +420,7 @@ final class MockRemoteInterfaceTests: XCTestCase { } func testDelete() async { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let itemA = MockRemoteItem( identifier: "a", name: "a", @@ -441,26 +470,30 @@ final class MockRemoteInterfaceTests: XCTestCase { itemA_C.children = [itemA_C_D] itemA_C_D.parent = itemA_C - let result = await remoteInterface.delete(remotePath: Self.account.davFilesUrl + "/a/c") + let result = await remoteInterface.delete( + remotePath: Self.account.davFilesUrl + "/a/c", account: Self.account + ) XCTAssertEqual(result.error, .success) XCTAssertEqual(itemA.children, []) } func testFetchUserProfile() async { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) - let (account, profile, _, error) = await remoteInterface.fetchUserProfile() + let remoteInterface = MockRemoteInterface(rootItem: rootItem) + let (account, profile, _, error) = await remoteInterface.fetchUserProfile( + account: Self.account + ) XCTAssertEqual(error, .success) XCTAssertEqual(account, Self.account.ncKitAccount) XCTAssertNotNil(profile) } func testTryAuthenticationAttempt() async { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) - let state = await remoteInterface.tryAuthenticationAttempt() + let remoteInterface = MockRemoteInterface(rootItem: rootItem) + let state = await remoteInterface.tryAuthenticationAttempt(account: Self.account) XCTAssertEqual(state, .success) - let newMri = - MockRemoteInterface(account: Account(user: "", id: "", serverUrl: "", password: "")) - let failState = await newMri.tryAuthenticationAttempt() + let failState = await remoteInterface.tryAuthenticationAttempt( + account: Account(user: "", id: "", serverUrl: "", password: "") + ) XCTAssertEqual(failState, .authenticationError) } } diff --git a/Tests/NextcloudFileProviderKitTests/EnumeratorTests.swift b/Tests/NextcloudFileProviderKitTests/EnumeratorTests.swift index 89e5fa2..7cd6f10 100644 --- a/Tests/NextcloudFileProviderKitTests/EnumeratorTests.swift +++ b/Tests/NextcloudFileProviderKitTests/EnumeratorTests.swift @@ -94,10 +94,11 @@ final class EnumeratorTests: XCTestCase { func testRootEnumeration() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) // Avoid build-time warning about unused variable, ensure compiler won't free - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let enumerator = Enumerator( enumeratedItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -129,6 +130,7 @@ final class EnumeratorTests: XCTestCase { let storedFolderItem = try XCTUnwrap( Item.storedItem( identifier: .init(remoteFolder.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -148,10 +150,11 @@ final class EnumeratorTests: XCTestCase { func testWorkingSetEnumeration() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let enumerator = Enumerator( enumeratedItemIdentifier: .workingSet, + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -177,10 +180,11 @@ final class EnumeratorTests: XCTestCase { func testWorkingSetFastChangeEnumeration() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let enumerator = Enumerator( enumeratedItemIdentifier: .workingSet, + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -220,10 +224,11 @@ final class EnumeratorTests: XCTestCase { func testWorkingSetSlowChangeEnumeration() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let enumerator = Enumerator( enumeratedItemIdentifier: .workingSet, + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager, fastEnumeration: false @@ -250,7 +255,7 @@ final class EnumeratorTests: XCTestCase { func testFolderEnumeration() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let oldEtag = "OLD" let folderMetadata = ItemMetadata() @@ -270,6 +275,7 @@ final class EnumeratorTests: XCTestCase { let enumerator = Enumerator( enumeratedItemIdentifier: .init(remoteFolder.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -284,6 +290,7 @@ final class EnumeratorTests: XCTestCase { let storedFolderItem = try XCTUnwrap( Item.storedItem( identifier: .init(remoteFolder.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -309,7 +316,7 @@ final class EnumeratorTests: XCTestCase { func testEnumerateFile() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let folderMetadata = ItemMetadata() folderMetadata.ocId = remoteFolder.identifier @@ -343,6 +350,7 @@ final class EnumeratorTests: XCTestCase { let enumerator = Enumerator( enumeratedItemIdentifier: .init(remoteItemA.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -364,7 +372,7 @@ final class EnumeratorTests: XCTestCase { func testFolderAndContentsChangeEnumeration() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) remoteFolder.children.removeAll(where: { $0.identifier == remoteItemB.identifier }) remoteFolder.children.append(remoteItemC) @@ -417,6 +425,7 @@ final class EnumeratorTests: XCTestCase { let enumerator = Enumerator( enumeratedItemIdentifier: .init(remoteFolder.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -463,6 +472,7 @@ final class EnumeratorTests: XCTestCase { let storedFolderItem = try XCTUnwrap( Item.storedItem( identifier: .init(remoteFolder.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -497,7 +507,7 @@ final class EnumeratorTests: XCTestCase { func testFileMoveChangeEnumeration() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) remoteFolder.children.removeAll(where: { $0.identifier == remoteItemA.identifier }) rootItem.children.append(remoteItemA) @@ -553,6 +563,7 @@ final class EnumeratorTests: XCTestCase { let enumerator = Enumerator( enumeratedItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -577,6 +588,7 @@ final class EnumeratorTests: XCTestCase { let storedItemA = try XCTUnwrap( Item.storedItem( identifier: .init(remoteItemA.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -591,7 +603,9 @@ final class EnumeratorTests: XCTestCase { Int(remoteItemA.modificationDate.timeIntervalSince1970) ) - let storedRootItem = Item.rootContainer(remoteInterface: remoteInterface) + let storedRootItem = Item.rootContainer( + account: Self.account, remoteInterface: remoteInterface + ) print(storedRootItem.metadata.serverUrl) storedRootItem.dbManager = Self.dbManager XCTAssertEqual(storedRootItem.childItemCount?.intValue, 3) // All items @@ -599,6 +613,7 @@ final class EnumeratorTests: XCTestCase { let storedFolder = try XCTUnwrap( Item.storedItem( identifier: .init(remoteFolder.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -610,7 +625,7 @@ final class EnumeratorTests: XCTestCase { func testFileLockStateEnumeration() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) remoteFolder.children.append(remoteItemC) remoteItemC.parent = remoteFolder @@ -645,6 +660,7 @@ final class EnumeratorTests: XCTestCase { let enumerator = Enumerator( enumeratedItemIdentifier: .init(remoteFolder.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -677,6 +693,7 @@ final class EnumeratorTests: XCTestCase { let storedItemA = try XCTUnwrap( Item.storedItem( identifier: .init(remoteItemA.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -684,6 +701,7 @@ final class EnumeratorTests: XCTestCase { let storedItemB = try XCTUnwrap( Item.storedItem( identifier: .init(remoteItemB.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -691,6 +709,7 @@ final class EnumeratorTests: XCTestCase { let storedItemC = try XCTUnwrap( Item.storedItem( identifier: .init(remoteItemC.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -710,7 +729,7 @@ final class EnumeratorTests: XCTestCase { func testEnsureNoEmptyItemNameEnumeration() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) // Avoid build-time warning about unused variable, ensure compiler won't free - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) remoteItemA.name = "" remoteItemA.parent = remoteInterface.rootItem @@ -718,6 +737,7 @@ final class EnumeratorTests: XCTestCase { let enumerator = Enumerator( enumeratedItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -736,6 +756,7 @@ final class EnumeratorTests: XCTestCase { let storedItemA = try XCTUnwrap( Item.storedItem( identifier: .init(remoteItemA.identifier), + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager ) @@ -749,11 +770,12 @@ final class EnumeratorTests: XCTestCase { func testListenerInvocations() async throws { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let listener = MockEnumerationListener() let enumerator = Enumerator( enumeratedItemIdentifier: .workingSet, + account: Self.account, remoteInterface: remoteInterface, dbManager: Self.dbManager, listener: listener diff --git a/Tests/NextcloudFileProviderKitTests/ItemCreateTests.swift b/Tests/NextcloudFileProviderKitTests/ItemCreateTests.swift index 9395080..8b3b777 100644 --- a/Tests/NextcloudFileProviderKitTests/ItemCreateTests.swift +++ b/Tests/NextcloudFileProviderKitTests/ItemCreateTests.swift @@ -40,7 +40,7 @@ final class ItemCreateTests: XCTestCase { } func testCreateFolder() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let folderItemMetadata = ItemMetadata() folderItemMetadata.name = "folder" folderItemMetadata.fileName = "folder" @@ -52,11 +52,13 @@ final class ItemCreateTests: XCTestCase { let folderItemTemplate = Item( metadata: folderItemMetadata, parentItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface ) let (createdItemMaybe, error) = await Item.create( basedOn: folderItemTemplate, contents: nil, + account: Self.account, remoteInterface: remoteInterface, progress: Progress(), dbManager: Self.dbManager @@ -86,7 +88,7 @@ final class ItemCreateTests: XCTestCase { } func testCreateFile() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let fileItemMetadata = ItemMetadata() fileItemMetadata.fileName = "file" fileItemMetadata.fileNameView = "file" @@ -100,11 +102,13 @@ final class ItemCreateTests: XCTestCase { let fileItemTemplate = Item( metadata: fileItemMetadata, parentItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface ) let (createdItemMaybe, error) = await Item.create( basedOn: fileItemTemplate, contents: tempUrl, + account: Self.account, remoteInterface: remoteInterface, progress: Progress(), dbManager: Self.dbManager @@ -133,7 +137,7 @@ final class ItemCreateTests: XCTestCase { } func testCreateFileIntoFolder() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let folderItemMetadata = ItemMetadata() folderItemMetadata.name = "folder" @@ -146,12 +150,14 @@ final class ItemCreateTests: XCTestCase { let folderItemTemplate = Item( metadata: folderItemMetadata, parentItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface ) let (createdFolderItemMaybe, folderError) = await Item.create( basedOn: folderItemTemplate, contents: nil, + account: Self.account, remoteInterface: remoteInterface, progress: Progress(), dbManager: Self.dbManager @@ -172,6 +178,7 @@ final class ItemCreateTests: XCTestCase { let fileItemTemplate = Item( metadata: fileItemMetadata, parentItemIdentifier: createdFolderItem.itemIdentifier, + account: Self.account, remoteInterface: remoteInterface ) @@ -181,6 +188,7 @@ final class ItemCreateTests: XCTestCase { let (createdFileItemMaybe, fileError) = await Item.create( basedOn: fileItemTemplate, contents: tempUrl, + account: Self.account, remoteInterface: remoteInterface, progress: Progress(), dbManager: Self.dbManager @@ -218,7 +226,7 @@ final class ItemCreateTests: XCTestCase { let keynoteBundleFilename = "test.key" - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let bundleItemMetadata = ItemMetadata() bundleItemMetadata.name = keynoteBundleFilename bundleItemMetadata.fileName = keynoteBundleFilename @@ -289,6 +297,7 @@ final class ItemCreateTests: XCTestCase { let bundleItemTemplate = Item( metadata: bundleItemMetadata, parentItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface ) @@ -296,6 +305,7 @@ final class ItemCreateTests: XCTestCase { let (createdBundleItemMaybe, bundleError) = await Item.create( basedOn: bundleItemTemplate, contents: tempUrl, + account: Self.account, remoteInterface: remoteInterface, progress: Progress(), dbManager: Self.dbManager diff --git a/Tests/NextcloudFileProviderKitTests/ItemDeleteTests.swift b/Tests/NextcloudFileProviderKitTests/ItemDeleteTests.swift index 3eac433..7c0b7fa 100644 --- a/Tests/NextcloudFileProviderKitTests/ItemDeleteTests.swift +++ b/Tests/NextcloudFileProviderKitTests/ItemDeleteTests.swift @@ -38,7 +38,7 @@ final class ItemDeleteTests: XCTestCase { } func testDeleteFile() async { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let itemIdentifier = "file" let remoteItem = MockRemoteItem( identifier: itemIdentifier, @@ -63,6 +63,7 @@ final class ItemDeleteTests: XCTestCase { let item = Item( metadata: itemMetadata, parentItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface ) @@ -74,7 +75,7 @@ final class ItemDeleteTests: XCTestCase { } func testDeleteFolderAndContents() async { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let remoteFolder = MockRemoteItem( identifier: "folder", name: "folder", @@ -121,6 +122,7 @@ final class ItemDeleteTests: XCTestCase { let folder = Item( metadata: folderMetadata, parentItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface ) diff --git a/Tests/NextcloudFileProviderKitTests/ItemFetchTests.swift b/Tests/NextcloudFileProviderKitTests/ItemFetchTests.swift index 0429f68..ed11dae 100644 --- a/Tests/NextcloudFileProviderKitTests/ItemFetchTests.swift +++ b/Tests/NextcloudFileProviderKitTests/ItemFetchTests.swift @@ -39,7 +39,7 @@ final class ItemFetchTests: XCTestCase { } func testFetchFileContents() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let remoteItem = MockRemoteItem( identifier: "item", versionIdentifier: "0", @@ -72,6 +72,7 @@ final class ItemFetchTests: XCTestCase { let item = Item( metadata: itemMetadata, parentItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface ) @@ -95,7 +96,7 @@ final class ItemFetchTests: XCTestCase { } func testFetchDirectoryContents() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let remoteDirectory = MockRemoteItem( identifier: "directory", versionIdentifier: "0", @@ -250,6 +251,7 @@ final class ItemFetchTests: XCTestCase { let item = Item( metadata: directoryMetadata, parentItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface ) item.dbManager = Self.dbManager diff --git a/Tests/NextcloudFileProviderKitTests/ItemModifyTests.swift b/Tests/NextcloudFileProviderKitTests/ItemModifyTests.swift index 7f3353a..61850cf 100644 --- a/Tests/NextcloudFileProviderKitTests/ItemModifyTests.swift +++ b/Tests/NextcloudFileProviderKitTests/ItemModifyTests.swift @@ -40,7 +40,7 @@ final class ItemModifyTests: XCTestCase { } func testModifyFileContents() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let remoteItem = MockRemoteItem( identifier: "item", versionIdentifier: "0", @@ -108,12 +108,14 @@ final class ItemModifyTests: XCTestCase { let item = Item( metadata: itemMetadata, parentItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface ) let targetItem = Item( metadata: targetItemMetadata, parentItemIdentifier: .init(remoteFolder.identifier), + account: Self.account, remoteInterface: remoteInterface ) targetItem.dbManager = Self.dbManager @@ -148,7 +150,7 @@ final class ItemModifyTests: XCTestCase { let db = Self.dbManager.ncDatabase() // Strong ref for in memory test db debugPrint(db) - let remoteInterface = MockRemoteInterface(account: Self.account, rootItem: rootItem) + let remoteInterface = MockRemoteInterface(rootItem: rootItem) let keynoteBundleFilename = "test.key" let keynoteIndexZipFilename = "Index.zip" @@ -509,6 +511,7 @@ final class ItemModifyTests: XCTestCase { let bundleItem = Item( metadata: bundleItemMetadata, parentItemIdentifier: .rootContainer, + account: Self.account, remoteInterface: remoteInterface ) bundleItem.dbManager = Self.dbManager @@ -591,6 +594,7 @@ final class ItemModifyTests: XCTestCase { let targetItem = Item( metadata: targetBundleMetadata, parentItemIdentifier: .init(remoteFolder.identifier), + account: Self.account, remoteInterface: remoteInterface ) targetItem.dbManager = Self.dbManager diff --git a/Tests/NextcloudFileProviderKitTests/ItemPropertyTests.swift b/Tests/NextcloudFileProviderKitTests/ItemPropertyTests.swift index 476d805..18f999f 100644 --- a/Tests/NextcloudFileProviderKitTests/ItemPropertyTests.swift +++ b/Tests/NextcloudFileProviderKitTests/ItemPropertyTests.swift @@ -36,7 +36,8 @@ final class ItemPropertyTests: XCTestCase { let item = Item( metadata: metadata, parentItemIdentifier: .rootContainer, - remoteInterface: MockRemoteInterface(account: Self.account) + account: Self.account, + remoteInterface: MockRemoteInterface() ) XCTAssertEqual(item.contentType, UTType.text) } @@ -59,7 +60,8 @@ final class ItemPropertyTests: XCTestCase { let item = Item( metadata: metadata, parentItemIdentifier: .rootContainer, - remoteInterface: MockRemoteInterface(account: Self.account) + account: Self.account, + remoteInterface: MockRemoteInterface() ) XCTAssertEqual(item.contentType, UTType.pdf) } @@ -82,7 +84,8 @@ final class ItemPropertyTests: XCTestCase { let item = Item( metadata: metadata, parentItemIdentifier: .rootContainer, - remoteInterface: MockRemoteInterface(account: Self.account) + account: Self.account, + remoteInterface: MockRemoteInterface() ) XCTAssertEqual(item.contentType, UTType.folder) } @@ -106,7 +109,8 @@ final class ItemPropertyTests: XCTestCase { let item = Item( metadata: metadata, parentItemIdentifier: .rootContainer, - remoteInterface: MockRemoteInterface(account: Self.account) + account: Self.account, + remoteInterface: MockRemoteInterface() ) XCTAssertEqual(item.contentType, UTType.package) } @@ -130,7 +134,8 @@ final class ItemPropertyTests: XCTestCase { let item = Item( metadata: metadata, parentItemIdentifier: .rootContainer, - remoteInterface: MockRemoteInterface(account: Self.account) + account: Self.account, + remoteInterface: MockRemoteInterface() ) XCTAssertEqual(item.contentType, UTType.bundle) } @@ -154,7 +159,8 @@ final class ItemPropertyTests: XCTestCase { let item = Item( metadata: metadata, parentItemIdentifier: .rootContainer, - remoteInterface: MockRemoteInterface(account: Self.account) + account: Self.account, + remoteInterface: MockRemoteInterface() ) XCTAssertEqual(item.contentType, UTType.folder) } @@ -178,7 +184,8 @@ final class ItemPropertyTests: XCTestCase { let item = Item( metadata: metadata, parentItemIdentifier: .rootContainer, - remoteInterface: MockRemoteInterface(account: Self.account) + account: Self.account, + remoteInterface: MockRemoteInterface() ) XCTAssertTrue(item.contentType.conforms(to: .bundle)) } @@ -206,7 +213,8 @@ final class ItemPropertyTests: XCTestCase { let item = Item( metadata: metadata, parentItemIdentifier: .rootContainer, - remoteInterface: MockRemoteInterface(account: Self.account) + account: Self.account, + remoteInterface: MockRemoteInterface() ) XCTAssertNotNil(item.userInfo?["locked"]) @@ -236,7 +244,8 @@ final class ItemPropertyTests: XCTestCase { let item = Item( metadata: metadata, parentItemIdentifier: .rootContainer, - remoteInterface: MockRemoteInterface(account: Self.account) + account: Self.account, + remoteInterface: MockRemoteInterface() ) XCTAssertNil(item.userInfo?["locked"]) diff --git a/Tests/NextcloudFileProviderKitTests/RemoteChangeObserverTests.swift b/Tests/NextcloudFileProviderKitTests/RemoteChangeObserverTests.swift index f497207..31bf509 100644 --- a/Tests/NextcloudFileProviderKitTests/RemoteChangeObserverTests.swift +++ b/Tests/NextcloudFileProviderKitTests/RemoteChangeObserverTests.swift @@ -44,7 +44,7 @@ final class RemoteChangeObserverTests: XCTestCase { } func testAuthentication() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account) + let remoteInterface = MockRemoteInterface() remoteInterface.capabilities = mockCapabilities var authenticated = false @@ -56,6 +56,7 @@ final class RemoteChangeObserverTests: XCTestCase { } remoteChangeObserver = RemoteChangeObserver( + account: Self.account, remoteInterface: remoteInterface, changeNotificationInterface: MockChangeNotificationInterface(), domain: nil @@ -83,9 +84,10 @@ final class RemoteChangeObserverTests: XCTestCase { let incorrectAccount = Account(user: username, id: userId, serverUrl: serverUrl, password: "wrong!") - let remoteInterface = MockRemoteInterface(account: incorrectAccount) + let remoteInterface = MockRemoteInterface() remoteInterface.capabilities = mockCapabilities remoteChangeObserver = RemoteChangeObserver( + account: incorrectAccount, remoteInterface: remoteInterface, changeNotificationInterface: MockChangeNotificationInterface(), domain: nil @@ -99,7 +101,7 @@ final class RemoteChangeObserverTests: XCTestCase { } } XCTAssertTrue(remoteChangeObserver.webSocketAuthenticationFailCount > 0) - remoteInterface.account = Self.account + remoteChangeObserver.account = Self.account for _ in 0...Self.timeout { try await Task.sleep(nanoseconds: 1_000_000) @@ -114,9 +116,10 @@ final class RemoteChangeObserverTests: XCTestCase { func testStopRetryingConnection() async throws { let incorrectAccount = Account(user: username, id: userId, serverUrl: serverUrl, password: "wrong!") - let remoteInterface = MockRemoteInterface(account: incorrectAccount) + let remoteInterface = MockRemoteInterface() remoteInterface.capabilities = mockCapabilities let remoteChangeObserver = RemoteChangeObserver( + account: incorrectAccount, remoteInterface: remoteInterface, changeNotificationInterface: MockChangeNotificationInterface(), domain: nil @@ -138,7 +141,7 @@ final class RemoteChangeObserverTests: XCTestCase { } func testChangeRecognised() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account) + let remoteInterface = MockRemoteInterface() remoteInterface.capabilities = mockCapabilities var authenticated = false @@ -153,6 +156,7 @@ final class RemoteChangeObserverTests: XCTestCase { let notificationInterface = MockChangeNotificationInterface() notificationInterface.changeHandler = { notified = true } remoteChangeObserver = RemoteChangeObserver( + account: Self.account, remoteInterface: remoteInterface, changeNotificationInterface: notificationInterface, domain: nil @@ -177,7 +181,7 @@ final class RemoteChangeObserverTests: XCTestCase { } func testIgnoreNonFileNotifications() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account) + let remoteInterface = MockRemoteInterface() remoteInterface.capabilities = mockCapabilities var authenticated = false @@ -192,6 +196,7 @@ final class RemoteChangeObserverTests: XCTestCase { let notificationInterface = MockChangeNotificationInterface() notificationInterface.changeHandler = { notified = true } remoteChangeObserver = RemoteChangeObserver( + account: Self.account, remoteInterface: remoteInterface, changeNotificationInterface: notificationInterface, domain: nil @@ -219,11 +224,12 @@ final class RemoteChangeObserverTests: XCTestCase { func testPolling() async throws { var notified = false - let remoteInterface = MockRemoteInterface(account: Self.account) + let remoteInterface = MockRemoteInterface() remoteInterface.capabilities = "" let notificationInterface = MockChangeNotificationInterface() notificationInterface.changeHandler = { notified = true } remoteChangeObserver = RemoteChangeObserver( + account: Self.account, remoteInterface: remoteInterface, changeNotificationInterface: notificationInterface, domain: nil @@ -257,7 +263,7 @@ final class RemoteChangeObserverTests: XCTestCase { } func testRetryOnRemoteClose() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account) + let remoteInterface = MockRemoteInterface() remoteInterface.capabilities = mockCapabilities var authenticated = false @@ -269,6 +275,7 @@ final class RemoteChangeObserverTests: XCTestCase { } remoteChangeObserver = RemoteChangeObserver( + account: Self.account, remoteInterface: remoteInterface, changeNotificationInterface: MockChangeNotificationInterface(), domain: nil @@ -296,7 +303,7 @@ final class RemoteChangeObserverTests: XCTestCase { } func testPinging() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account) + let remoteInterface = MockRemoteInterface() remoteInterface.capabilities = mockCapabilities var authenticated = false @@ -308,6 +315,7 @@ final class RemoteChangeObserverTests: XCTestCase { } remoteChangeObserver = RemoteChangeObserver( + account: Self.account, remoteInterface: remoteInterface, changeNotificationInterface: MockChangeNotificationInterface(), domain: nil @@ -338,7 +346,7 @@ final class RemoteChangeObserverTests: XCTestCase { } func testRetryOnConnectionLoss() async throws { - let remoteInterface = MockRemoteInterface(account: Self.account) + let remoteInterface = MockRemoteInterface() remoteInterface.capabilities = mockCapabilities var authenticated = false @@ -353,6 +361,7 @@ final class RemoteChangeObserverTests: XCTestCase { let notificationInterface = MockChangeNotificationInterface() notificationInterface.changeHandler = { notified = true } remoteChangeObserver = RemoteChangeObserver( + account: Self.account, remoteInterface: remoteInterface, changeNotificationInterface: notificationInterface, domain: nil