diff --git a/.gitignore b/.gitignore index 9ce0965d..d1dbce30 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ Package.resolved /.env-vars *.generated.swift +/.build diff --git a/Package.swift b/Package.swift index e93c7b9c..fbc7a318 100644 --- a/Package.swift +++ b/Package.swift @@ -10,10 +10,10 @@ import PackageDescription let package = Package( name: "NextcloudKit", platforms: [ - .macOS(.v10_15), - .iOS(.v13), - .tvOS(.v13), - .watchOS(.v6), + .iOS(.v14), + .macOS(.v11), + .tvOS(.v14), + .watchOS(.v7), .visionOS(.v1) ], products: [ @@ -41,4 +41,28 @@ let package = Package( name: "NextcloudKitIntegrationTests", dependencies: ["NextcloudKit", "Mocker"]) ] + + /* Test simulate 6 + targets: [ + .target( + name: "NextcloudKit", + dependencies: ["Alamofire", "SwiftyJSON", "SwiftyXMLParser"], + swiftSettings: [ + .enableUpcomingFeature("StrictConcurrency"), // simulate Swift 6 + .enableExperimentalFeature("StrictConcurrency"), + .unsafeFlags(["-Xfrontend", "-warn-concurrency"]), + .unsafeFlags(["-Xfrontend", "-enable-actor-data-race-checks"]) + ] + ), + .testTarget( + name: "NextcloudKitUnitTests", + dependencies: ["NextcloudKit", "Mocker"], + resources: [ + .process("Resources") + ]), + .testTarget( + name: "NextcloudKitIntegrationTests", + dependencies: ["NextcloudKit", "Mocker"]) + ] + */ ) diff --git a/Sources/NextcloudKit/Log/NKLogFileManager.swift b/Sources/NextcloudKit/Log/NKLogFileManager.swift index 61696dac..684e1da1 100644 --- a/Sources/NextcloudKit/Log/NKLogFileManager.swift +++ b/Sources/NextcloudKit/Log/NKLogFileManager.swift @@ -53,7 +53,7 @@ public enum NKLogTagEmoji: String { /// A logger that writes log messages to a file in a subdirectory of the user's Documents folder, /// rotates the log daily /// Compatible with iOS 13.0+ and Swift 6. -public final class NKLogFileManager { +public final class NKLogFileManager: @unchecked Sendable { // MARK: - Singleton @@ -79,13 +79,13 @@ public final class NKLogFileManager { private let logDirectory: URL public var logLevel: NKLogLevel private var currentLogDate: String - private let logQueue = DispatchQueue(label: "LogWriterQueue", attributes: .concurrent) - private let rotationQueue = DispatchQueue(label: "LogRotationQueue") + private let logQueue = DispatchQueue(label: "com.nextcloud.LogWriterQueue", attributes: .concurrent) + private let rotationQueue = DispatchQueue(label: "com.nextcloud.LogRotationQueue") private let fileManager = FileManager.default // Cache for dynamic format strings, populated at runtime. Thread-safe via serial queue. private static var cachedDynamicFormatters: [String: DateFormatter] = [:] - private static let formatterAccessQueue = DispatchQueue(label: "com.yourapp.dateformatter.cache") + private static let formatterAccessQueue = DispatchQueue(label: "com.nextcloud.dateformatter.cache") // MARK: - Initialization diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift new file mode 100644 index 00000000..51d2aec6 --- /dev/null +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation + +public enum NKEditorDetailsConverter { + + /// Parses and converts raw JSON `Data` into `[NKEditorDetailsEditors]` and `[NKEditorDetailsCreators]`. + /// - Parameter data: Raw JSON `Data` from the editors/creators endpoint. + /// - Returns: A tuple with editors and creators. + /// - Throws: Decoding error if parsing fails. + public static func from(data: Data) throws -> (editors: [NKEditorDetailsEditor], creators: [NKEditorDetailsCreator]) { + let decoded = try JSONDecoder().decode(NKEditorDetailsResponse.self, from: data) + let editors = decoded.ocs.data.editorsArray() + let creators = decoded.ocs.data.creatorsArray() + + if NKLogFileManager.shared.logLevel == .verbose { + data.printJson() + } + + return (editors, creators) + } +} diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse+NKConversion.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse+NKConversion.swift new file mode 100644 index 00000000..7d4a1953 --- /dev/null +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse+NKConversion.swift @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation + +public extension NKEditorDetailsResponse.OCS.DataClass { + func editorsArray() -> [NKEditorDetailsEditor] { + Array(editors.values) + } + + func creatorsArray() -> [NKEditorDetailsCreator] { + Array(creators.values) + } +} diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift new file mode 100644 index 00000000..72055382 --- /dev/null +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation + +public struct NKEditorDetailsResponse: Codable, Sendable { + public let ocs: OCS + + public struct OCS: Codable, Sendable { + public let data: DataClass + + public struct DataClass: Codable, Sendable { + public let editors: [String: NKEditorDetailsEditor] + public let creators: [String: NKEditorDetailsCreator] + } + } +} + +public struct NKEditorTemplateResponse: Codable, Sendable { + public let ocs: OCS + + public struct OCS: Codable, Sendable { + public let data: DataClass + + public struct DataClass: Codable, Sendable { + public let editors: [NKEditorTemplate] + } + } +} + +public struct NKEditorDetailsEditor: Codable, Sendable { + public let identifier: String + public let mimetypes: [String] + public let name: String + public let optionalMimetypes: [String] + public let secure: Bool + + enum CodingKeys: String, CodingKey { + case identifier = "id" + case mimetypes + case name + case optionalMimetypes + case secure + } +} + +public struct NKEditorDetailsCreator: Codable, Sendable { + public let identifier: String + public let templates: Bool + public let mimetype: String + public let name: String + public let editor: String + public let ext: String + + enum CodingKeys: String, CodingKey { + case identifier = "id" + case templates + case mimetype + case name + case editor + case ext = "extension" + } +} + +public struct NKEditorTemplate: Codable, Sendable { + public var ext: String + public var identifier: String + public var name: String + public var preview: String + + enum CodingKeys: String, CodingKey { + case ext = "extension" + case identifier = "id" + case name + case preview + } + + public init(ext: String = "", identifier: String = "", name: String = "", preview: String = "") { + self.ext = ext + self.identifier = identifier + self.name = name + self.preview = preview + } +} diff --git a/Sources/NextcloudKit/NKDataFileXML.swift b/Sources/NextcloudKit/Models/NKDataFileXML.swift similarity index 97% rename from Sources/NextcloudKit/NKDataFileXML.swift rename to Sources/NextcloudKit/Models/NKDataFileXML.swift index d079d50f..386c994f 100644 --- a/Sources/NextcloudKit/NKDataFileXML.swift +++ b/Sources/NextcloudKit/Models/NKDataFileXML.swift @@ -253,7 +253,7 @@ public class NKDataFileXML: NSObject { return xml["ocs", "data", "apppassword"].text } - func convertDataFile(xmlData: Data, nkSession: NKSession, showHiddenFiles: Bool, includeHiddenFiles: [String]) -> [NKFile] { + func convertDataFile(xmlData: Data, nkSession: NKSession, showHiddenFiles: Bool, includeHiddenFiles: [String]) async -> [NKFile] { var files: [NKFile] = [] let rootFiles = "/" + nkSession.dav + "/files/" guard let baseUrl = self.nkCommonInstance.getHostName(urlString: nkSession.urlBase) else { @@ -534,12 +534,14 @@ public class NKDataFileXML: NSObject { file.downloadLimits.append(NKDownloadLimit(count: count, limit: limit, token: token)) } - let results = self.nkCommonInstance.getInternalType(fileName: file.fileName, mimeType: file.contentType, directory: file.directory, account: nkSession.account) + let results = await self.nkCommonInstance.typeIdentifiers.getInternalType(fileName: file.fileName, mimeType: file.contentType, directory: file.directory, account: nkSession.account) file.contentType = results.mimeType file.iconName = results.iconName file.name = "files" file.classFile = results.classFile + file.typeIdentifier = results.typeIdentifier + file.urlBase = nkSession.urlBase file.user = nkSession.user file.userId = nkSession.userId @@ -557,8 +559,8 @@ public class NKDataFileXML: NSObject { } if index < files.count - 1, (files[index].fileName as NSString).deletingPathExtension == (files[index + 1].fileName as NSString) .deletingPathExtension, - files[index].classFile == NKCommon.TypeClassFile.image.rawValue, - files[index + 1].classFile == NKCommon.TypeClassFile.video.rawValue { + files[index].classFile == NKTypeClassFile.image.rawValue, + files[index + 1].classFile == NKTypeClassFile.video.rawValue { files[index].livePhotoFile = files[index + 1].fileId files[index + 1].livePhotoFile = files[index].fileId } @@ -567,7 +569,7 @@ public class NKDataFileXML: NSObject { return files } - func convertDataTrash(xmlData: Data, nkSession: NKSession, showHiddenFiles: Bool) -> [NKTrash] { + func convertDataTrash(xmlData: Data, nkSession: NKSession, showHiddenFiles: Bool) async -> [NKTrash] { var files: [NKTrash] = [] var first: Bool = true guard let baseUrl = self.nkCommonInstance.getHostName(urlString: nkSession.urlBase) else { @@ -581,7 +583,7 @@ public class NKDataFileXML: NSObject { first = false continue } - let file = NKTrash() + var file = NKTrash() if let href = element["d:href"].text { var fileNamePath = href @@ -644,11 +646,12 @@ public class NKDataFileXML: NSObject { file.trashbinDeletionTime = Date(timeIntervalSince1970: trashbinDeletionTimeDouble) } - let results = self.nkCommonInstance.getInternalType(fileName: file.trashbinFileName, mimeType: file.contentType, directory: file.directory, account: nkSession.account) + let results = await self.nkCommonInstance.typeIdentifiers.getInternalType(fileName: file.trashbinFileName, mimeType: file.contentType, directory: file.directory, account: nkSession.account) file.contentType = results.mimeType file.classFile = results.classFile file.iconName = results.iconName + file.typeIdentifier = results.typeIdentifier files.append(file) } diff --git a/Sources/NextcloudKit/Models/NKEditorDetailsCreators.swift b/Sources/NextcloudKit/Models/NKEditorDetailsCreators.swift deleted file mode 100644 index 75243edc..00000000 --- a/Sources/NextcloudKit/Models/NKEditorDetailsCreators.swift +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2019 Marino Faggiana -// SPDX-FileCopyrightText: 2023 Claudio Cambra -// SPDX-License-Identifier: GPL-3.0-or-later - -import Foundation - -public class NKEditorDetailsCreators: NSObject { - public var editor = "" - public var ext = "" - public var identifier = "" - public var mimetype = "" - public var name = "" - public var templates: Int = 0 -} diff --git a/Sources/NextcloudKit/Models/NKEditorDetailsEditors.swift b/Sources/NextcloudKit/Models/NKEditorDetailsEditors.swift deleted file mode 100644 index 322b2db5..00000000 --- a/Sources/NextcloudKit/Models/NKEditorDetailsEditors.swift +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2019 Marino Faggiana -// SPDX-FileCopyrightText: 2023 Claudio Cambra -// SPDX-License-Identifier: GPL-3.0-or-later - -import Foundation - -public class NKEditorDetailsEditors: NSObject { - public var mimetypes: [String] = [] - public var name = "" - public var optionalMimetypes: [String] = [] - public var secure: Int = 0 -} diff --git a/Sources/NextcloudKit/Models/NKEditorTemplates.swift b/Sources/NextcloudKit/Models/NKEditorTemplates.swift deleted file mode 100644 index a14c25bf..00000000 --- a/Sources/NextcloudKit/Models/NKEditorTemplates.swift +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2019 Marino Faggiana -// SPDX-FileCopyrightText: 2023 Claudio Cambra -// SPDX-License-Identifier: GPL-3.0-or-later - -import Foundation - -public class NKEditorTemplates: NSObject { - public var delete = "" - public var ext = "" - public var identifier = "" - public var name = "" - public var preview = "" - public var type = "" -} diff --git a/Sources/NextcloudKit/Models/NKFile.swift b/Sources/NextcloudKit/Models/NKFile.swift index 719d3b0a..6e457157 100644 --- a/Sources/NextcloudKit/Models/NKFile.swift +++ b/Sources/NextcloudKit/Models/NKFile.swift @@ -80,6 +80,7 @@ public struct NKFile: Sendable { } public var exifPhotos: [[String: String?]] public var placePhotos: String? + public var typeIdentifier: String public init( account: String = "", @@ -142,68 +143,70 @@ public struct NKFile: Sendable { isFlaggedAsLivePhotoByServer: Bool = false, datePhotosOriginal: Date? = nil, exifPhotos: [[String : String?]] = .init(), - placePhotos: String? = nil) { + placePhotos: String? = nil, + typeIdentifier: String = "") { - self.account = account - self.classFile = classFile - self.commentsUnread = commentsUnread - self.contentType = contentType - self.checksums = checksums - self.creationDate = creationDate - self.dataFingerprint = dataFingerprint - self.date = date - self.directory = directory - self.downloadURL = downloadURL - self.downloadLimits = downloadLimits - self.e2eEncrypted = e2eEncrypted - self.etag = etag - self.favorite = favorite - self.fileId = fileId - self.fileName = fileName - self.hasPreview = hasPreview - self.iconName = iconName - self.mountType = mountType - self.name = name - self.note = note - self.ocId = ocId - self.ownerId = ownerId - self.ownerDisplayName = ownerDisplayName - self.lock = lock - self.lockOwner = lockOwner - self.lockOwnerEditor = lockOwnerEditor - self.lockOwnerType = lockOwnerType - self.lockOwnerDisplayName = lockOwnerDisplayName - self.lockTime = lockTime - self.lockTimeOut = lockTimeOut - self.path = path - self.permissions = permissions - self.quotaUsedBytes = quotaUsedBytes - self.quotaAvailableBytes = quotaAvailableBytes - self.resourceType = resourceType - self.richWorkspace = richWorkspace - self.sharePermissionsCollaborationServices = sharePermissionsCollaborationServices - self.sharePermissionsCloudMesh = sharePermissionsCloudMesh - self.shareType = shareType - self.size = size - self.serverUrl = serverUrl - self.tags = tags - self.trashbinFileName = trashbinFileName - self.trashbinOriginalLocation = trashbinOriginalLocation - self.trashbinDeletionTime = trashbinDeletionTime - self.uploadDate = uploadDate - self.urlBase = urlBase - self.user = user - self.userId = userId - self.latitude = latitude - self.longitude = longitude - self.altitude = altitude - self.height = height - self.width = width - self.hidden = hidden - self.livePhotoFile = livePhotoFile - self.isFlaggedAsLivePhotoByServer = isFlaggedAsLivePhotoByServer - self.datePhotosOriginal = datePhotosOriginal - self.exifPhotos = exifPhotos - self.placePhotos = placePhotos + self.account = account + self.classFile = classFile + self.commentsUnread = commentsUnread + self.contentType = contentType + self.checksums = checksums + self.creationDate = creationDate + self.dataFingerprint = dataFingerprint + self.date = date + self.directory = directory + self.downloadURL = downloadURL + self.downloadLimits = downloadLimits + self.e2eEncrypted = e2eEncrypted + self.etag = etag + self.favorite = favorite + self.fileId = fileId + self.fileName = fileName + self.hasPreview = hasPreview + self.iconName = iconName + self.mountType = mountType + self.name = name + self.note = note + self.ocId = ocId + self.ownerId = ownerId + self.ownerDisplayName = ownerDisplayName + self.lock = lock + self.lockOwner = lockOwner + self.lockOwnerEditor = lockOwnerEditor + self.lockOwnerType = lockOwnerType + self.lockOwnerDisplayName = lockOwnerDisplayName + self.lockTime = lockTime + self.lockTimeOut = lockTimeOut + self.path = path + self.permissions = permissions + self.quotaUsedBytes = quotaUsedBytes + self.quotaAvailableBytes = quotaAvailableBytes + self.resourceType = resourceType + self.richWorkspace = richWorkspace + self.sharePermissionsCollaborationServices = sharePermissionsCollaborationServices + self.sharePermissionsCloudMesh = sharePermissionsCloudMesh + self.shareType = shareType + self.size = size + self.serverUrl = serverUrl + self.tags = tags + self.trashbinFileName = trashbinFileName + self.trashbinOriginalLocation = trashbinOriginalLocation + self.trashbinDeletionTime = trashbinDeletionTime + self.uploadDate = uploadDate + self.urlBase = urlBase + self.user = user + self.userId = userId + self.latitude = latitude + self.longitude = longitude + self.altitude = altitude + self.height = height + self.width = width + self.hidden = hidden + self.livePhotoFile = livePhotoFile + self.isFlaggedAsLivePhotoByServer = isFlaggedAsLivePhotoByServer + self.datePhotosOriginal = datePhotosOriginal + self.exifPhotos = exifPhotos + self.placePhotos = placePhotos + self.typeIdentifier = typeIdentifier } } diff --git a/Sources/NextcloudKit/Models/NKFileProperty.swift b/Sources/NextcloudKit/Models/NKFileProperty.swift deleted file mode 100644 index 1df5e71e..00000000 --- a/Sources/NextcloudKit/Models/NKFileProperty.swift +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2019 Marino Faggiana -// SPDX-FileCopyrightText: 2023 Claudio Cambra -// SPDX-License-Identifier: GPL-3.0-or-later - -import Foundation - -#if swift(<6.0) -public class NKFileProperty: NSObject { - public var classFile: String = "" - public var iconName: String = "" - public var name: String = "" - public var ext: String = "" -} -#else -public struct NKFileProperty: Sendable { - public var classFile: String = "" - public var iconName: String = "" - public var name: String = "" - public var ext: String = "" -} -#endif diff --git a/Sources/NextcloudKit/NKShareAccounts.swift b/Sources/NextcloudKit/Models/NKShareAccounts.swift similarity index 100% rename from Sources/NextcloudKit/NKShareAccounts.swift rename to Sources/NextcloudKit/Models/NKShareAccounts.swift diff --git a/Sources/NextcloudKit/Models/NKTrash.swift b/Sources/NextcloudKit/Models/NKTrash.swift index 7e554652..182820b0 100644 --- a/Sources/NextcloudKit/Models/NKTrash.swift +++ b/Sources/NextcloudKit/Models/NKTrash.swift @@ -5,9 +5,10 @@ import Foundation -public class NKTrash: NSObject { +public struct NKTrash: Sendable { public var ocId = "" public var contentType = "" + public var typeIdentifier = "" public var date = Date() public var directory: Bool = false public var fileId = "" diff --git a/Sources/NextcloudKit/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 598e8d2e..3a062209 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -6,17 +6,18 @@ import Foundation import Alamofire -#if os(iOS) -import MobileCoreServices -#else -import CoreServices -#endif +public enum NKTypeReachability: Int { + case unknown = 0 + case notReachable = 1 + case reachableEthernetOrWiFi = 2 + case reachableCellular = 3 +} public protocol NextcloudKitDelegate: AnyObject, Sendable { func authenticationChallenge(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) - func networkReachabilityObserver(_ typeReachability: NKCommon.TypeReachability) + func networkReachabilityObserver(_ typeReachability: NKTypeReachability) func request(_ request: DataRequest, didParseResponse response: AFDataResponse) @@ -33,7 +34,7 @@ public extension NextcloudKitDelegate { func authenticationChallenge(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { } func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { } - func networkReachabilityObserver(_ typeReachability: NKCommon.TypeReachability) { } + func networkReachabilityObserver(_ typeReachability: NKTypeReachability) { } func request(_ request: DataRequest, didParseResponse response: AFDataResponse) { } @@ -47,9 +48,10 @@ public extension NextcloudKitDelegate { } public struct NKCommon: Sendable { - public var nksessions = ThreadSafeArray() + public var nksessions = SynchronizedNKSessionArray() public var delegate: NextcloudKitDelegate? public var groupIdentifier: String? + public let typeIdentifiers: NKTypeIdentifiers = .shared // Foreground public let identifierSessionDownload: String = "com.nextcloud.nextcloudkit.session.download" @@ -74,230 +76,11 @@ public struct NKCommon: Sendable { public let groupDefaultsUnavailable = "Unavailable" public let groupDefaultsToS = "ToS" - public enum TypeReachability: Int { - case unknown = 0 - case notReachable = 1 - case reachableEthernetOrWiFi = 2 - case reachableCellular = 3 - } - - public enum TypeClassFile: String { - case audio = "audio" - case compress = "compress" - case directory = "directory" - case document = "document" - case image = "image" - case unknow = "unknow" - case url = "url" - case video = "video" - } - public enum TypeIconFile: String { - case audio = "audio" - case code = "code" - case compress = "compress" - case directory = "directory" - case document = "document" - case image = "image" - case movie = "movie" - case pdf = "pdf" - case ppt = "ppt" - case txt = "txt" - case unknow = "file" - case url = "url" - case xls = "xls" - } - - public struct UTTypeConformsToServer: Sendable { - var typeIdentifier: String - var classFile: String - var editor: String - var iconName: String - var name: String - var account: String - } - -#if swift(<6.0) - internal var utiCache = NSCache() - internal var mimeTypeCache = NSCache() - internal var filePropertiesCache = NSCache() -#else - internal var utiCache = [String: String]() - internal var mimeTypeCache = [String: String]() - internal var filePropertiesCache = [String: NKFileProperty]() -#endif - internal var internalTypeIdentifiers = ThreadSafeArray() // MARK: - Init - init() { - - } - - // MARK: - Type Identifier - - mutating public func clearInternalTypeIdentifier(account: String) { - internalTypeIdentifiers = internalTypeIdentifiers.filter({ $0.account != account }) - } - - mutating public func addInternalTypeIdentifier(typeIdentifier: String, classFile: String, editor: String, iconName: String, name: String, account: String) { - if !internalTypeIdentifiers.contains(where: { $0.typeIdentifier == typeIdentifier && $0.editor == editor && $0.account == account}) { - let newUTI = UTTypeConformsToServer(typeIdentifier: typeIdentifier, classFile: classFile, editor: editor, iconName: iconName, name: name, account: account) - internalTypeIdentifiers.append(newUTI) - } - } - - mutating public func getInternalType(fileName: String, mimeType: String, directory: Bool, account: String) -> (mimeType: String, classFile: String, iconName: String, typeIdentifier: String, fileNameWithoutExt: String, ext: String) { - var ext = (fileName as NSString).pathExtension.lowercased() - var mimeType = mimeType - var classFile = "", iconName = "", typeIdentifier = "", fileNameWithoutExt = "" - var inUTI: CFString? - -#if swift(<6.0) - if let cachedUTI = utiCache.object(forKey: ext as NSString) { - inUTI = cachedUTI - } -#else - if let cachedUTI = utiCache[ext] { - inUTI = cachedUTI as CFString - } -#endif - if inUTI == nil { - if let unmanagedFileUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil) { - inUTI = unmanagedFileUTI.takeRetainedValue() - if let inUTI { -#if swift(<6.0) - utiCache.setObject(inUTI, forKey: ext as NSString) -#else - utiCache[ext] = inUTI as String -#endif - } - } - } - - if let inUTI { - typeIdentifier = inUTI as String - fileNameWithoutExt = (fileName as NSString).deletingPathExtension - - // contentType detect - if mimeType.isEmpty { -#if swift(<6.0) - if let cachedMimeUTI = mimeTypeCache.object(forKey: inUTI) { - mimeType = cachedMimeUTI as String - } -#else - if let cachedMimeUTI = mimeTypeCache[inUTI as String] { - mimeType = cachedMimeUTI - } -#endif - - if mimeType.isEmpty { - if let mimeUTI = UTTypeCopyPreferredTagWithClass(inUTI, kUTTagClassMIMEType) { - let mimeUTIString = mimeUTI.takeRetainedValue() as String - - mimeType = mimeUTIString -#if swift(<6.0) - mimeTypeCache.setObject(mimeUTIString as NSString, forKey: inUTI) -#else - mimeTypeCache[inUTI as String] = mimeUTIString as String -#endif - } - } - } - - if directory { - mimeType = "httpd/unix-directory" - classFile = TypeClassFile.directory.rawValue - iconName = TypeIconFile.directory.rawValue - typeIdentifier = kUTTypeFolder as String - fileNameWithoutExt = fileName - ext = "" - } else { - var fileProperties: NKFileProperty - -#if swift(<6.0) - if let cachedFileProperties = filePropertiesCache.object(forKey: inUTI) { - fileProperties = cachedFileProperties - } else { - fileProperties = getFileProperties(inUTI: inUTI) - filePropertiesCache.setObject(fileProperties, forKey: inUTI) - } -#else - if let cachedFileProperties = filePropertiesCache[inUTI as String] { - fileProperties = cachedFileProperties - } else { - fileProperties = getFileProperties(inUTI: inUTI) - filePropertiesCache[inUTI as String] = fileProperties - } -#endif - - classFile = fileProperties.classFile - iconName = fileProperties.iconName - } - } - return(mimeType: mimeType, classFile: classFile, iconName: iconName, typeIdentifier: typeIdentifier, fileNameWithoutExt: fileNameWithoutExt, ext: ext) - } - - public func getFileProperties(inUTI: CFString) -> NKFileProperty { - let fileProperty = NKFileProperty() - let typeIdentifier: String = inUTI as String - - if let fileExtension = UTTypeCopyPreferredTagWithClass(inUTI as CFString, kUTTagClassFilenameExtension) { - fileProperty.ext = String(fileExtension.takeRetainedValue()) - } - - if UTTypeConformsTo(inUTI, kUTTypeImage) { - fileProperty.classFile = TypeClassFile.image.rawValue - fileProperty.iconName = TypeIconFile.image.rawValue - fileProperty.name = "image" - } else if UTTypeConformsTo(inUTI, kUTTypeMovie) { - fileProperty.classFile = TypeClassFile.video.rawValue - fileProperty.iconName = TypeIconFile.movie.rawValue - fileProperty.name = "movie" - } else if UTTypeConformsTo(inUTI, kUTTypeAudio) { - fileProperty.classFile = TypeClassFile.audio.rawValue - fileProperty.iconName = TypeIconFile.audio.rawValue - fileProperty.name = "audio" - } else if UTTypeConformsTo(inUTI, kUTTypeZipArchive) { - fileProperty.classFile = TypeClassFile.compress.rawValue - fileProperty.iconName = TypeIconFile.compress.rawValue - fileProperty.name = "archive" - } else if UTTypeConformsTo(inUTI, kUTTypeHTML) { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.code.rawValue - fileProperty.name = "code" - } else if UTTypeConformsTo(inUTI, kUTTypePDF) { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.pdf.rawValue - fileProperty.name = "document" - } else if UTTypeConformsTo(inUTI, kUTTypeRTF) { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.txt.rawValue - fileProperty.name = "document" - } else if UTTypeConformsTo(inUTI, kUTTypeText) { - if fileProperty.ext.isEmpty { fileProperty.ext = "txt" } - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.txt.rawValue - fileProperty.name = "text" - } else { - if let result = internalTypeIdentifiers.first(where: {$0.typeIdentifier == typeIdentifier}) { - fileProperty.classFile = result.classFile - fileProperty.iconName = result.iconName - fileProperty.name = result.name - } else { - if UTTypeConformsTo(inUTI, kUTTypeContent) { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "document" - } else { - fileProperty.classFile = TypeClassFile.unknow.rawValue - fileProperty.iconName = TypeIconFile.unknow.rawValue - fileProperty.name = "file" - } - } - } - return fileProperty - } + init() { } // MARK: - Chunked File @@ -411,7 +194,7 @@ public struct NKCommon: Sendable { guard let groupDefaults = UserDefaults(suiteName: groupIdentifier) else { return } - let capabilities = NCCapabilities.shared.getCapabilitiesBlocking(for: account) + let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: account) /// Unavailable if errorCode == 503 { @@ -446,18 +229,12 @@ public struct NKCommon: Sendable { return "\(identifier).\(account)" } - public func getSession(account: String) -> NKSession? { - var session: NKSession? - nksessions.forEach { result in - if result.account == account { - session = result - } - } - return session - } + public func getStandardHeaders(account: String, options: NKRequestOptions? = nil) -> HTTPHeaders? { - guard let session = nksessions.filter({ $0.account == account }).first else { return nil} + guard let session = nksessions.session(forAccount: account) else { + return nil + } var headers: HTTPHeaders = [] headers.update(.authorization(username: session.user, password: session.password)) diff --git a/Sources/NextcloudKit/NKMonitor.swift b/Sources/NextcloudKit/NKMonitor.swift index 9b2b8388..e579130e 100644 --- a/Sources/NextcloudKit/NKMonitor.swift +++ b/Sources/NextcloudKit/NKMonitor.swift @@ -7,7 +7,7 @@ import Alamofire final class NKMonitor: EventMonitor, Sendable { let nkCommonInstance: NKCommon - let queue = DispatchQueue(label: "com.nextcloudkit.monitor") + let queue = DispatchQueue(label: "com.nextcloud.NKMonitor") init(nkCommonInstance: NKCommon) { self.nkCommonInstance = nkCommonInstance diff --git a/Sources/NextcloudKit/NKSession.swift b/Sources/NextcloudKit/NKSession.swift index df224cb7..02d681ee 100644 --- a/Sources/NextcloudKit/NKSession.swift +++ b/Sources/NextcloudKit/NKSession.swift @@ -17,7 +17,6 @@ public struct NKSession: Sendable { public let httpMaximumConnectionsPerHostInDownload: Int public let httpMaximumConnectionsPerHostInUpload: Int public let dav: String = "remote.php/dav" - public var internalTypeIdentifiers: [NKCommon.UTTypeConformsToServer] = [] public let sessionData: Alamofire.Session public let sessionDataNoCache: Alamofire.Session public let sessionDownloadBackground: URLSession diff --git a/Sources/NextcloudKit/NextcloudKit+API.swift b/Sources/NextcloudKit/NextcloudKit+API.swift index b168959b..ceaec9e2 100644 --- a/Sources/NextcloudKit/NextcloudKit+API.swift +++ b/Sources/NextcloudKit/NextcloudKit+API.swift @@ -58,7 +58,7 @@ public extension NextcloudKit { options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } @@ -84,7 +84,7 @@ public extension NextcloudKit { completion: @escaping (_ account: String, _ externalSite: [NKExternalSite], _ responseData: AFDataResponse?, _ error: NKError) -> Void) { var externalSites: [NKExternalSite] = [] let endpoint = "ocs/v2.php/apps/external/api/v1" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, externalSites, nil, .urlError) } @@ -219,7 +219,7 @@ public extension NextcloudKit { options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } @@ -265,7 +265,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ width: Int, _ height: Int, _ etag: String?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "index.php/core/preview?fileId=\(fileId)&x=\(width)&y=\(height)&a=\(crop)&mode=\(cropMode)&forceIcon=\(forceIcon)&mimeFallback=\(mimeFallback)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, width, height, nil, nil, .urlError) } @@ -315,7 +315,7 @@ public extension NextcloudKit { completion: @escaping (_ account: String, _ width: Int, _ height: Int, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "index.php/apps/files_trashbin/preview?fileId=\(fileId)&x=\(width)&y=\(height)&a=\(crop)&mode=\(cropMode)&forceIcon=\(forceIcon)&mimeFallback=\(mimeFallback)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, width, height, nil, .urlError) } @@ -345,7 +345,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ imageAvatar: UIImage?, _ imageOriginal: UIImage?, _ etag: String?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "index.php/avatar/\(user)/\(sizeImage)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, nil, nil, .urlError) } @@ -464,7 +464,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { guard let url = serverUrl.asUrl, - let nkSession = nkCommonInstance.getSession(account: account), + let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } @@ -494,7 +494,7 @@ public extension NextcloudKit { /// options.contentType = "application/json" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -538,7 +538,7 @@ public extension NextcloudKit { /// options.contentType = "application/json" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -711,7 +711,7 @@ public extension NextcloudKit { parameters["previews"] = "true" } - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, activities, activityFirstKnown, activityLastGiven, nil, .urlError) } @@ -783,7 +783,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ notifications: [NKNotifications]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/notifications/api/v2/notifications" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -852,7 +852,7 @@ public extension NextcloudKit { options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } @@ -894,7 +894,7 @@ public extension NextcloudKit { "fileId": fileId, "format": "json" ] - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -928,7 +928,7 @@ public extension NextcloudKit { /// options.contentType = "application/json" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+Assistant.swift b/Sources/NextcloudKit/NextcloudKit+Assistant.swift index cd43104f..e9eb9268 100644 --- a/Sources/NextcloudKit/NextcloudKit+Assistant.swift +++ b/Sources/NextcloudKit/NextcloudKit+Assistant.swift @@ -12,7 +12,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ types: [NKTextProcessingTaskType]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/textprocessing/tasktypes" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -49,7 +49,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ task: NKTextProcessingTask?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "/ocs/v2.php/textprocessing/schedule" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -84,7 +84,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ task: NKTextProcessingTask?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "/ocs/v2.php/textprocessing/task/\(taskId)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -118,7 +118,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ task: NKTextProcessingTask?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "/ocs/v2.php/textprocessing/task/\(taskId)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -152,7 +152,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ task: [NKTextProcessingTask]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "/ocs/v2.php/textprocessing/tasks/app/\(appId)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift index ec68ca44..e3460146 100644 --- a/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift +++ b/Sources/NextcloudKit/NextcloudKit+AssistantV2.swift @@ -13,7 +13,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ types: [TaskTypeData]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/taskprocessing/tasktypes" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -52,7 +52,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ task: AssistantTask?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "/ocs/v2.php/taskprocessing/schedule" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -89,7 +89,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ tasks: TaskList?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "/ocs/v2.php/taskprocessing/tasks?taskType=\(taskType)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -123,7 +123,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "/ocs/v2.php/taskprocessing/task/\(taskId)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+Capabilities.swift b/Sources/NextcloudKit/NextcloudKit+Capabilities.swift index 3d5f7a6a..8c116459 100644 --- a/Sources/NextcloudKit/NextcloudKit+Capabilities.swift +++ b/Sources/NextcloudKit/NextcloudKit+Capabilities.swift @@ -25,9 +25,9 @@ public extension NextcloudKit { func getCapabilities(account: String, options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ capabilities: NCCapabilities.Capabilities?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + completion: @escaping (_ account: String, _ capabilities: NKCapabilities.Capabilities?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v1.php/cloud/capabilities" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -68,7 +68,7 @@ public extension NextcloudKit { func getCapabilitiesAsync(account: String, options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }) async -> (account: String, - capabilities: NCCapabilities.Capabilities?, + capabilities: NKCapabilities.Capabilities?, responseData: AFDataResponse?, error: NKError) { await withUnsafeContinuation { continuation in @@ -86,7 +86,7 @@ public extension NextcloudKit { /// - data: The raw JSON data returned from the capabilities endpoint. /// - Returns: A fully populated `NCCapabilities.Capabilities` object. /// - Throws: An error if decoding fails or data is missing. - func setCapabilitiesAsync(account: String, data: Data? = nil) async throws -> NCCapabilities.Capabilities { + func setCapabilitiesAsync(account: String, data: Data? = nil) async throws -> NKCapabilities.Capabilities { guard let jsonData = data else { throw NSError(domain: "SetCapabilities", code: 0, userInfo: [NSLocalizedDescriptionKey: "Missing JSON data"]) } @@ -347,7 +347,7 @@ public extension NextcloudKit { let json = data.capabilities // Initialize capabilities - let capabilities = NCCapabilities.Capabilities() + let capabilities = NKCapabilities.Capabilities() // Version info capabilities.serverVersion = data.version.string @@ -410,7 +410,7 @@ public extension NextcloudKit { capabilities.termsOfService = json.termsOfService?.enabled ?? false // Persist capabilities in shared store - await NCCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) + await NKCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) return capabilities } catch { nkLog(error: "Could not decode json capabilities: \(error.localizedDescription)") @@ -421,65 +421,69 @@ public extension NextcloudKit { /// A concurrency-safe store for capabilities associated with Nextcloud accounts. actor CapabilitiesStore { - private var store: [String: NCCapabilities.Capabilities] = [:] + private var store: [String: NKCapabilities.Capabilities] = [:] - func get(_ account: String) -> NCCapabilities.Capabilities? { + func get(_ account: String) -> NKCapabilities.Capabilities? { return store[account] } - func set(_ account: String, value: NCCapabilities.Capabilities) { + func set(_ account: String, value: NKCapabilities.Capabilities) { store[account] = value } } /// Singleton container and public API for accessing and caching capabilities. -final public class NCCapabilities: Sendable { - public static let shared = NCCapabilities() +final public class NKCapabilities: Sendable { + public static let shared = NKCapabilities() private let store = CapabilitiesStore() public class Capabilities: @unchecked Sendable { - public var serverVersionMajor: Int = 0 - public var serverVersion: String = "" - public var fileSharingApiEnabled: Bool = false - public var fileSharingPubPasswdEnforced: Bool = false - public var fileSharingPubExpireDateEnforced: Bool = false - public var fileSharingPubExpireDateDays: Int = 0 - public var fileSharingInternalExpireDateEnforced: Bool = false - public var fileSharingInternalExpireDateDays: Int = 0 - public var fileSharingRemoteExpireDateEnforced: Bool = false - public var fileSharingRemoteExpireDateDays: Int = 0 - public var fileSharingDefaultPermission: Int = 0 - public var fileSharingDownloadLimit: Bool = false - public var fileSharingDownloadLimitDefaultLimit: Int = 1 - public var themingColor: String = "" - public var themingColorElement: String = "" - public var themingColorText: String = "" - public var themingName: String = "" - public var themingSlogan: String = "" - public var e2EEEnabled: Bool = false - public var e2EEApiVersion: String = "" - public var richDocumentsEnabled: Bool = false - public var richDocumentsMimetypes: [String] = [] - public var activity: [String] = [] - public var notification: [String] = [] - public var filesUndelete: Bool = false - public var filesLockVersion: String = "" // NC 24 - public var filesComments: Bool = false // NC 20 - public var filesBigfilechunking: Bool = false - public var userStatusEnabled: Bool = false - public var externalSites: Bool = false - public var activityEnabled: Bool = false - public var groupfoldersEnabled: Bool = false // NC27 - public var assistantEnabled: Bool = false // NC28 - public var isLivePhotoServerAvailable: Bool = false // NC28 - public var securityGuardDiagnostics = false - public var forbiddenFileNames: [String] = [] - public var forbiddenFileNameBasenames: [String] = [] - public var forbiddenFileNameCharacters: [String] = [] - public var forbiddenFileNameExtensions: [String] = [] - public var recommendations: Bool = false - public var termsOfService: Bool = false + public var serverVersionMajor: Int = 0 + public var serverVersion: String = "" + public var fileSharingApiEnabled: Bool = false + public var fileSharingPubPasswdEnforced: Bool = false + public var fileSharingPubExpireDateEnforced: Bool = false + public var fileSharingPubExpireDateDays: Int = 0 + public var fileSharingInternalExpireDateEnforced: Bool = false + public var fileSharingInternalExpireDateDays: Int = 0 + public var fileSharingRemoteExpireDateEnforced: Bool = false + public var fileSharingRemoteExpireDateDays: Int = 0 + public var fileSharingDefaultPermission: Int = 0 + public var fileSharingDownloadLimit: Bool = false + public var fileSharingDownloadLimitDefaultLimit: Int = 1 + public var themingColor: String = "" + public var themingColorElement: String = "" + public var themingColorText: String = "" + public var themingName: String = "" + public var themingSlogan: String = "" + public var e2EEEnabled: Bool = false + public var e2EEApiVersion: String = "" + public var richDocumentsEnabled: Bool = false + public var richDocumentsMimetypes: [String] = [] + public var activity: [String] = [] + public var notification: [String] = [] + public var filesUndelete: Bool = false + public var filesLockVersion: String = "" // NC 24 + public var filesComments: Bool = false // NC 20 + public var filesBigfilechunking: Bool = false + public var userStatusEnabled: Bool = false + public var externalSites: Bool = false + public var activityEnabled: Bool = false + public var groupfoldersEnabled: Bool = false // NC27 + public var assistantEnabled: Bool = false // NC28 + public var isLivePhotoServerAvailable: Bool = false // NC28 + public var securityGuardDiagnostics = false + public var forbiddenFileNames: [String] = [] + public var forbiddenFileNameBasenames: [String] = [] + public var forbiddenFileNameCharacters: [String] = [] + public var forbiddenFileNameExtensions: [String] = [] + public var recommendations: Bool = false + public var termsOfService: Bool = false + + public var directEditingEditors: [NKEditorDetailsEditor] = [] + public var directEditingCreators: [NKEditorDetailsCreator] = [] + public var directEditingTemplates: [NKEditorTemplate] = [] public init() {} } diff --git a/Sources/NextcloudKit/NextcloudKit+Comments.swift b/Sources/NextcloudKit/NextcloudKit+Comments.swift index 05c35d7f..da6e4ead 100644 --- a/Sources/NextcloudKit/NextcloudKit+Comments.swift +++ b/Sources/NextcloudKit/NextcloudKit+Comments.swift @@ -14,7 +14,7 @@ public extension NextcloudKit { /// options.contentType = "application/xml" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } } @@ -60,7 +60,7 @@ public extension NextcloudKit { /// options.contentType = "application/json" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } @@ -100,7 +100,7 @@ public extension NextcloudKit { /// options.contentType = "application/xml" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } @@ -137,7 +137,7 @@ public extension NextcloudKit { options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } @@ -166,7 +166,7 @@ public extension NextcloudKit { /// options.contentType = "application/xml" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } diff --git a/Sources/NextcloudKit/NextcloudKit+Dashboard.swift b/Sources/NextcloudKit/NextcloudKit+Dashboard.swift index 098354e3..66287df4 100644 --- a/Sources/NextcloudKit/NextcloudKit+Dashboard.swift +++ b/Sources/NextcloudKit/NextcloudKit+Dashboard.swift @@ -13,7 +13,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ dashboardWidgets: [NCCDashboardWidget]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/dashboard/api/v1/widgets" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -49,7 +49,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ dashboardApplications: [NCCDashboardApplication]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/dashboard/api/v1/widget-items?widgets[]=\(items)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+Download.swift b/Sources/NextcloudKit/NextcloudKit+Download.swift index 07707052..c6159139 100644 --- a/Sources/NextcloudKit/NextcloudKit+Download.swift +++ b/Sources/NextcloudKit/NextcloudKit+Download.swift @@ -14,7 +14,7 @@ public extension NextcloudKit { requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in }, taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, progressHandler: @escaping (_ progress: Progress) -> Void = { _ in }, - completionHandler: @escaping (_ account: String, _ etag: String?, _ date: Date?, _ lenght: Int64, _ responseData: AFDownloadResponse?, _ afError: AFError?, _ nKError: NKError) -> Void) { + completionHandler: @escaping (_ account: String, _ etag: String?, _ date: Date?, _ lenght: Int64, _ headers: [AnyHashable: Any]?, _ afError: AFError?, _ nKError: NKError) -> Void) { var convertible: URLConvertible? if serverUrlFileName is URL { convertible = serverUrlFileName as? URLConvertible @@ -22,7 +22,7 @@ public extension NextcloudKit { convertible = (serverUrlFileName as? String)?.encodedToUrl } guard let url = convertible, - let nkSession = nkCommonInstance.getSession(account: account), + let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completionHandler(account, nil, nil, 0, nil, nil, .urlError) } } @@ -42,7 +42,7 @@ public extension NextcloudKit { switch response.result { case .failure(let error): let resultError = NKError(error: error, afResponse: response, responseData: nil) - options.queue.async { completionHandler(account, nil, nil, 0, response, error, resultError) } + options.queue.async { completionHandler(account, nil, nil, 0, response.response?.allHeaderFields, error, resultError) } case .success: var date: Date? var etag: String? @@ -63,7 +63,7 @@ public extension NextcloudKit { date = dateRaw.parsedDate(using: "yyyy-MM-dd HH:mm:ss") } - options.queue.async { completionHandler(account, etag, date, length, response, nil, .success) } + options.queue.async { completionHandler(account, etag, date, length, response.response?.allHeaderFields, nil, .success) } } } diff --git a/Sources/NextcloudKit/NextcloudKit+E2EE.swift b/Sources/NextcloudKit/NextcloudKit+E2EE.swift index 913ec120..28640a69 100644 --- a/Sources/NextcloudKit/NextcloudKit+E2EE.swift +++ b/Sources/NextcloudKit/NextcloudKit+E2EE.swift @@ -18,7 +18,7 @@ public extension NextcloudKit { version = optionsVesion } let endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/encrypted/\(fileId)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } @@ -69,7 +69,7 @@ public extension NextcloudKit { version = optionsVesion } let endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/lock/\(fileId)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -131,7 +131,7 @@ public extension NextcloudKit { version = optionsVesion } let endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/meta-data/\(fileId)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, nil, .urlError) } @@ -177,7 +177,7 @@ public extension NextcloudKit { version = optionsVesion } let endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/meta-data/\(fileId)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -249,7 +249,7 @@ public extension NextcloudKit { } else { endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/public-key" } - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, nil, .urlError) } @@ -300,7 +300,7 @@ public extension NextcloudKit { version = optionsVesion } let endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/private-key" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -336,7 +336,7 @@ public extension NextcloudKit { version = optionsVesion } let endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/server-key" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -373,7 +373,7 @@ public extension NextcloudKit { version = optionsVesion } let endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/public-key" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -412,7 +412,7 @@ public extension NextcloudKit { version = optionsVesion } let endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/private-key" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -449,7 +449,7 @@ public extension NextcloudKit { version = optionsVesion } let endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/public-key" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } @@ -477,7 +477,7 @@ public extension NextcloudKit { version = optionsVesion } let endpoint = "ocs/v2.php/apps/end_to_end_encryption/api/\(version)/private-key" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+FilesLock.swift b/Sources/NextcloudKit/NextcloudKit+FilesLock.swift index b7b75be1..516399ea 100644 --- a/Sources/NextcloudKit/NextcloudKit+FilesLock.swift +++ b/Sources/NextcloudKit/NextcloudKit+FilesLock.swift @@ -18,7 +18,7 @@ public extension NextcloudKit { return options.queue.async { completion(account, nil, .urlError) } } let method = HTTPMethod(rawValue: shouldLock ? "LOCK" : "UNLOCK") - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } diff --git a/Sources/NextcloudKit/NextcloudKit+Groupfolders.swift b/Sources/NextcloudKit/NextcloudKit+Groupfolders.swift index 3620a582..2f2987b1 100644 --- a/Sources/NextcloudKit/NextcloudKit+Groupfolders.swift +++ b/Sources/NextcloudKit/NextcloudKit+Groupfolders.swift @@ -13,7 +13,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ results: [NKGroupfolders]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "index.php/apps/groupfolders/folders?applicable=1" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+Hovercard.swift b/Sources/NextcloudKit/NextcloudKit+Hovercard.swift index 8a993854..9c582f83 100644 --- a/Sources/NextcloudKit/NextcloudKit+Hovercard.swift +++ b/Sources/NextcloudKit/NextcloudKit+Hovercard.swift @@ -13,7 +13,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ result: NKHovercard?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/hovercard/v1/\(userId)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+Livephoto.swift b/Sources/NextcloudKit/NextcloudKit+Livephoto.swift index 08e7e641..313f3bc4 100644 --- a/Sources/NextcloudKit/NextcloudKit+Livephoto.swift +++ b/Sources/NextcloudKit/NextcloudKit+Livephoto.swift @@ -13,7 +13,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { guard let url = serverUrlfileNamePath.encodedToUrl, - let nkSession = nkCommonInstance.getSession(account: account) else { + let nkSession = nkCommonInstance.nksessions.session(forAccount: account) else { return options.queue.async { completion(account, nil, .urlError) } } let method = HTTPMethod(rawValue: "PROPPATCH") diff --git a/Sources/NextcloudKit/NextcloudKit+Login.swift b/Sources/NextcloudKit/NextcloudKit+Login.swift index 20deb2e6..575111f7 100644 --- a/Sources/NextcloudKit/NextcloudKit+Login.swift +++ b/Sources/NextcloudKit/NextcloudKit+Login.swift @@ -56,7 +56,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/core/apppassword" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = self.nkCommonInstance.createStandardUrl(serverUrl: serverUrl, endpoint: endpoint, options: options) else { return options.queue.async { completion(nil, .urlError) } } diff --git a/Sources/NextcloudKit/NextcloudKit+NCText.swift b/Sources/NextcloudKit/NextcloudKit+NCText.swift index a2369971..2f8426fd 100644 --- a/Sources/NextcloudKit/NextcloudKit+NCText.swift +++ b/Sources/NextcloudKit/NextcloudKit+NCText.swift @@ -10,14 +10,12 @@ public extension NextcloudKit { func textObtainEditorDetails(account: String, options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ editors: [NKEditorDetailsEditors], _ creators: [NKEditorDetailsCreators], _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + completion: @escaping (_ account: String, _ editors: [NKEditorDetailsEditor]?, _ creators: [NKEditorDetailsCreator]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/files/api/v1/directEditing" - var editors: [NKEditorDetailsEditors] = [] - var creators: [NKEditorDetailsCreators] = [] - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { - return options.queue.async { completion(account, editors, creators, nil, .urlError) } + return options.queue.async { completion(account, nil, nil, nil, .urlError) } } nkSession.sessionData.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in @@ -27,49 +25,34 @@ public extension NextcloudKit { switch response.result { case .failure(let error): let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, editors, creators, response, error) } - case .success(let jsonData): - let json = JSON(jsonData) - let ocsdataeditors = json["ocs"]["data"]["editors"] - for (_, subJson): (String, JSON) in ocsdataeditors { - let editor = NKEditorDetailsEditors() - - if let mimetypes = subJson["mimetypes"].array { - for mimetype in mimetypes { - editor.mimetypes.append(mimetype.stringValue) + options.queue.async { completion(account, nil, nil, response, error) } + case .success(let responseData): + Task { + do { + let (editors, creators) = try NKEditorDetailsConverter.from(data: responseData) + let capabilities = await NKCapabilities.shared.getCapabilitiesAsync(for: account) + capabilities.directEditingEditors = editors + capabilities.directEditingCreators = creators + await NKCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) + + options.queue.async { + completion(account, editors, creators, response, .success) } - } - editor.name = subJson["name"].stringValue - if let optionalMimetypes = subJson["optionalMimetypes"].array { - for optionalMimetype in optionalMimetypes { - editor.optionalMimetypes.append(optionalMimetype.stringValue) + + } catch { + nkLog(error: "Parsing error in NKEditorDetailsConverter: \(error)") + options.queue.async { + completion(account, nil, nil, response, .invalidData) } } - editor.secure = subJson["secure"].intValue - editors.append(editor) - } - - let ocsdatacreators = json["ocs"]["data"]["creators"] - for (_, subJson): (String, JSON) in ocsdatacreators { - let creator = NKEditorDetailsCreators() - - creator.editor = subJson["editor"].stringValue - creator.ext = subJson["extension"].stringValue - creator.identifier = subJson["id"].stringValue - creator.mimetype = subJson["mimetype"].stringValue - creator.name = subJson["name"].stringValue - creator.templates = subJson["templates"].intValue - creators.append(creator) } - - options.queue.async { completion(account, editors, creators, response, .success) } } } } func textObtainEditorDetailsAsync(account: String, options: NKRequestOptions = NKRequestOptions(), - taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }) async -> (account: String, editors: [NKEditorDetailsEditors], creators: [NKEditorDetailsCreators], responseData: AFDataResponse?, error: NKError) { + taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }) async -> (account: String, editors: [NKEditorDetailsEditor]?, creators: [NKEditorDetailsCreator]?, responseData: AFDataResponse?, error: NKError) { await withUnsafeContinuation { continuation in textObtainEditorDetails(account: account, options: options, @@ -93,7 +76,7 @@ public extension NextcloudKit { if let fileId = fileId { endpoint = "ocs/v2.php/apps/files/api/v1/directEditing/open?path=/\(fileNamePath)&fileId=\(fileId)&editorId=\(editor)" } - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -118,10 +101,9 @@ public extension NextcloudKit { func textGetListOfTemplates(account: String, options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, - completion: @escaping (_ account: String, _ templates: [NKEditorTemplates]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { + completion: @escaping (_ account: String, _ templates: [NKEditorTemplate]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/files/api/v1/directEditing/templates/text/textdocumenttemplate" - var templates: [NKEditorTemplates] = [] - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -134,28 +116,29 @@ public extension NextcloudKit { switch response.result { case .failure(let error): let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, templates, response, error) } - case .success(let jsonData): - let json = JSON(jsonData) - let ocsdatatemplates = json["ocs"]["data"]["editors"] - - for (_, subJson): (String, JSON) in ocsdatatemplates { - let template = NKEditorTemplates() - - template.ext = subJson["extension"].stringValue - template.identifier = subJson["id"].stringValue - template.name = subJson["name"].stringValue - template.preview = subJson["preview"].stringValue - templates.append(template) + options.queue.async { completion(account, nil, response, error) } + case .success(let data): + Task { + do { + let decoded = try JSONDecoder().decode(NKEditorTemplateResponse.self, from: data) + let templates = decoded.ocs.data.editors + // Update capabilities + let capabilities = await NKCapabilities.shared.getCapabilitiesAsync(for: account) + capabilities.directEditingTemplates = templates + await NKCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) + + options.queue.async { completion(account, templates, response, .success) } + } catch { + nkLog(error: "Failed to decode template list: \(error)") + options.queue.async { completion(account, nil, response, .invalidData) } + } } - - options.queue.async { completion(account, templates, response, .success) } } } } func textGetListOfTemplatesAsync(account: String, - options: NKRequestOptions = NKRequestOptions()) async -> (account: String, templates: [NKEditorTemplates]?, responseData: AFDataResponse?, error: NKError) { + options: NKRequestOptions = NKRequestOptions()) async -> (account: String, templates: [NKEditorTemplate]?, responseData: AFDataResponse?, error: NKError) { await withUnsafeContinuation({ continuation in textGetListOfTemplates(account: account) { account, templates, responseData, error in continuation.resume(returning: (account: account, templates: templates, responseData: responseData, error: error)) @@ -180,7 +163,7 @@ public extension NextcloudKit { } else { endpoint = "ocs/v2.php/apps/files/api/v1/directEditing/create?path=/\(fileNamePath)&editorId=\(editorId)&creatorId=\(creatorId)&templateId=\(templateId)" } - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+PushNotification.swift b/Sources/NextcloudKit/NextcloudKit+PushNotification.swift index 02fcf2fb..b6b1b2d3 100644 --- a/Sources/NextcloudKit/NextcloudKit+PushNotification.swift +++ b/Sources/NextcloudKit/NextcloudKit+PushNotification.swift @@ -16,7 +16,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ deviceIdentifier: String?, _ signature: String?, _ publicKey: String?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/notifications/api/v2/push" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, nil, nil, .urlError) } @@ -56,7 +56,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/notifications/api/v2/push" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } @@ -86,7 +86,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "devices?format=json" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = self.nkCommonInstance.createStandardUrl(serverUrl: proxyServerUrl, endpoint: endpoint, options: options), let userAgent = options.customUserAgent else { return options.queue.async { completion(account, nil, .urlError) } @@ -122,7 +122,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "devices" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = self.nkCommonInstance.createStandardUrl(serverUrl: proxyServerUrl, endpoint: endpoint, options: options), let userAgent = options.customUserAgent else { return options.queue.async { completion(account, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+RecommendedFiles.swift b/Sources/NextcloudKit/NextcloudKit+RecommendedFiles.swift index 7f1235d7..1935cde8 100644 --- a/Sources/NextcloudKit/NextcloudKit+RecommendedFiles.swift +++ b/Sources/NextcloudKit/NextcloudKit+RecommendedFiles.swift @@ -16,7 +16,7 @@ public extension NextcloudKit { /// options.contentType = "application/xml" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+Richdocuments.swift b/Sources/NextcloudKit/NextcloudKit+Richdocuments.swift index b71fff46..58005003 100644 --- a/Sources/NextcloudKit/NextcloudKit+Richdocuments.swift +++ b/Sources/NextcloudKit/NextcloudKit+Richdocuments.swift @@ -13,7 +13,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ url: String?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/richdocuments/api/v1/document" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -46,7 +46,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ templates: [NKRichdocumentsTemplate]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/richdocuments/api/v1/templates/\(typeTemplate)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -101,7 +101,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ url: String?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/richdocuments/api/v1/templates/new" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -134,7 +134,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ url: String?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "index.php/apps/richdocuments/assets" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+Search.swift b/Sources/NextcloudKit/NextcloudKit+Search.swift index 55fecc86..13b7b05e 100644 --- a/Sources/NextcloudKit/NextcloudKit+Search.swift +++ b/Sources/NextcloudKit/NextcloudKit+Search.swift @@ -34,7 +34,7 @@ public extension NextcloudKit { update: @escaping (_ account: String, _ searchResult: NKSearchResult?, _ provider: NKSearchProvider, _ error: NKError) -> Void, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/search/providers" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } @@ -101,7 +101,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, NKSearchResult?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) -> DataRequest? { guard let term = term.urlEncoded, - let nkSession = nkCommonInstance.getSession(account: account), + let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { completion(account, nil, nil, .urlError) return nil diff --git a/Sources/NextcloudKit/NextcloudKit+Share.swift b/Sources/NextcloudKit/NextcloudKit+Share.swift index 3774cbbd..01ea7d74 100644 --- a/Sources/NextcloudKit/NextcloudKit+Share.swift +++ b/Sources/NextcloudKit/NextcloudKit+Share.swift @@ -61,7 +61,7 @@ public extension NextcloudKit { options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ shares: [NKShare]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: parameters.endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -114,7 +114,7 @@ public extension NextcloudKit { if lookup { lookupString = "true" } - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -265,7 +265,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ share: NKShare?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/files_sharing/api/v1/shares" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -348,7 +348,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ share: NKShare?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/files_sharing/api/v1/shares/\(idShare)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -407,7 +407,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/files_sharing/api/v1/shares/\(idShare)" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+ShareDownloadLimit.swift b/Sources/NextcloudKit/NextcloudKit+ShareDownloadLimit.swift index 4ac538ef..fb23d2ca 100644 --- a/Sources/NextcloudKit/NextcloudKit+ShareDownloadLimit.swift +++ b/Sources/NextcloudKit/NextcloudKit+ShareDownloadLimit.swift @@ -15,7 +15,7 @@ public extension NextcloudKit { let endpoint = makeEndpoint(with: token) let options = NKRequestOptions() - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { @@ -80,7 +80,7 @@ public extension NextcloudKit { let endpoint = makeEndpoint(with: token) let options = NKRequestOptions() - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { @@ -113,7 +113,7 @@ public extension NextcloudKit { let options = NKRequestOptions() options.contentType = "application/json" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options), var urlRequest = try? URLRequest(url: url, method: .put, headers: headers) else { diff --git a/Sources/NextcloudKit/NextcloudKit+TermsOfService.swift b/Sources/NextcloudKit/NextcloudKit+TermsOfService.swift index aa70eedc..7cd571b0 100644 --- a/Sources/NextcloudKit/NextcloudKit+TermsOfService.swift +++ b/Sources/NextcloudKit/NextcloudKit+TermsOfService.swift @@ -17,7 +17,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ tos: NKTermsOfService?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/terms_of_service/terms" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -85,7 +85,7 @@ public extension NextcloudKit { /// options.contentType = "application/json" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+Upload.swift b/Sources/NextcloudKit/NextcloudKit+Upload.swift index 38913f4b..05aebc41 100644 --- a/Sources/NextcloudKit/NextcloudKit+Upload.swift +++ b/Sources/NextcloudKit/NextcloudKit+Upload.swift @@ -17,7 +17,7 @@ public extension NextcloudKit { requestHandler: @escaping (_ request: UploadRequest) -> Void = { _ in }, taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, progressHandler: @escaping (_ progress: Progress) -> Void = { _ in }, - completionHandler: @escaping (_ account: String, _ ocId: String?, _ etag: String?, _ date: Date?, _ size: Int64, _ responseData: AFDataResponse?, _ nkError: NKError) -> Void) { + completionHandler: @escaping (_ account: String, _ ocId: String?, _ etag: String?, _ date: Date?, _ size: Int64, _ headers: [AnyHashable: Any]?, _ nkError: NKError) -> Void) { var convertible: URLConvertible? var uploadedSize: Int64 = 0 var uploadCompleted = false @@ -28,7 +28,7 @@ public extension NextcloudKit { convertible = (serverUrlFileName as? String)?.encodedToUrl } guard let url = convertible, - let nkSession = nkCommonInstance.getSession(account: account), + let nkSession = nkCommonInstance.nksessions.session(forAccount: account), var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completionHandler(account, nil, nil, nil, 0, nil, .urlError) } } @@ -81,7 +81,7 @@ public extension NextcloudKit { } options.queue.async { - completionHandler(account, ocId, etag, date, uploadedSize, response, result) + completionHandler(account, ocId, etag, date, uploadedSize, response.response?.allHeaderFields, result) } } @@ -120,7 +120,7 @@ public extension NextcloudKit { uploaded: @escaping (_ fileChunk: (fileName: String, size: Int64)) -> Void = { _ in }, completion: @escaping (_ account: String, _ filesChunk: [(fileName: String, size: Int64)]?, _ file: NKFile?, _ error: NKError) -> Void) { - guard let nkSession = nkCommonInstance.getSession(account: account) else { + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account) else { return completion(account, nil, nil, .urlError) } let fileNameLocalSize = self.nkCommonInstance.getFileSize(filePath: directory + "/" + fileName) diff --git a/Sources/NextcloudKit/NextcloudKit+UserStatus.swift b/Sources/NextcloudKit/NextcloudKit+UserStatus.swift index de772e5a..06465885 100644 --- a/Sources/NextcloudKit/NextcloudKit+UserStatus.swift +++ b/Sources/NextcloudKit/NextcloudKit+UserStatus.swift @@ -16,7 +16,7 @@ public extension NextcloudKit { if let userId = userId { endpoint = "ocs/v2.php/apps/user_status/api/v1/user_status/\(userId)" } - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, nil, nil, false, nil, false, nil, nil, .urlError) } @@ -75,7 +75,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/user_status/api/v1/user_status/status" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } @@ -111,7 +111,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/user_status/api/v1/user_status/message/predefined" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } @@ -152,7 +152,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/user_status/api/v1/user_status/message/custom" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } @@ -193,7 +193,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/user_status/api/v1/user_status/message" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } @@ -225,7 +225,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ userStatuses: [NKUserStatus]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/user_status/api/v1/predefined_statuses" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -275,7 +275,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ userStatuses: [NKUserStatus]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/user_status/api/v1/statuses" - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } diff --git a/Sources/NextcloudKit/NextcloudKit+WebDAV.swift b/Sources/NextcloudKit/NextcloudKit+WebDAV.swift index 45d0a349..3e8fef6b 100644 --- a/Sources/NextcloudKit/NextcloudKit+WebDAV.swift +++ b/Sources/NextcloudKit/NextcloudKit+WebDAV.swift @@ -13,7 +13,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ ocId: String?, _ date: Date?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { guard let url = serverUrlFileName.encodedToUrl, - let nkSession = nkCommonInstance.getSession(account: account), + let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, nil, .urlError) } } @@ -59,7 +59,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { guard let url = serverUrlFileName.encodedToUrl, - let nkSession = nkCommonInstance.getSession(account: account), + let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } @@ -101,7 +101,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { guard let url = serverUrlFileNameSource.encodedToUrl, - let nkSession = nkCommonInstance.getSession(account: account), + let nkSession = nkCommonInstance.nksessions.session(forAccount: account), var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } @@ -152,7 +152,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { guard let url = serverUrlFileNameSource.encodedToUrl, - let nkSession = nkCommonInstance.getSession(account: account), + let nkSession = nkCommonInstance.nksessions.session(forAccount: account), var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } @@ -205,12 +205,11 @@ public extension NextcloudKit { options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ files: [NKFile]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - var files: [NKFile] = [] var serverUrlFileName = serverUrlFileName /// options.contentType = "application/xml" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let url = serverUrlFileName.encodedToUrl, var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -233,7 +232,7 @@ public extension NextcloudKit { urlRequest.httpBody = NKDataFileXML(nkCommonInstance: self.nkCommonInstance).getRequestBodyFile(createProperties: options.createProperties, removeProperties: options.removeProperties).data(using: .utf8) } } catch { - return options.queue.async { completion(account, files, nil, NKError(error: error)) } + return options.queue.async { completion(account, nil, nil, NKError(error: error)) } } nkSession.sessionData.request(urlRequest, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in @@ -243,13 +242,15 @@ public extension NextcloudKit { switch response.result { case .failure(let error): let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, files, response, error) } + options.queue.async { completion(account, nil, response, error) } case .success: if let xmlData = response.data { - files = NKDataFileXML(nkCommonInstance: self.nkCommonInstance).convertDataFile(xmlData: xmlData, nkSession: nkSession, showHiddenFiles: showHiddenFiles, includeHiddenFiles: includeHiddenFiles) - options.queue.async { completion(account, files, response, .success) } + Task { + let files = await NKDataFileXML(nkCommonInstance: self.nkCommonInstance).convertDataFile(xmlData: xmlData, nkSession: nkSession, showHiddenFiles: showHiddenFiles, includeHiddenFiles: includeHiddenFiles) + options.queue.async { completion(account, files, response, .success) } + } } else { - options.queue.async { completion(account, files, response, .xmlError) } + options.queue.async { completion(account, nil, response, .xmlError) } } } } @@ -274,7 +275,7 @@ public extension NextcloudKit { options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ file: NKFile?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - guard let nkSession = nkCommonInstance.getSession(account: account) else { + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account) else { return options.queue.async { completion(account, nil, nil, .urlError) } } var httpBody: Data? @@ -324,7 +325,7 @@ public extension NextcloudKit { options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ files: [NKFile]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let href = ("/files/" + nkSession.userId).urlEncoded else { return options.queue.async { completion(account, nil, nil, .urlError) } } @@ -347,7 +348,7 @@ public extension NextcloudKit { options: NKRequestOptions = NKRequestOptions(), taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ files: [NKFile]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { - guard let nkSession = nkCommonInstance.getSession(account: account) else { + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account) else { return options.queue.async { completion(account, nil, nil, .urlError) } } let files: [NKFile] = [] @@ -389,13 +390,12 @@ public extension NextcloudKit { /// options.contentType = "application/xml" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } } - var files: [NKFile] = [] guard let url = (serverUrl + "/" + nkSession.dav).encodedToUrl else { - return options.queue.async { completion(account, files, nil, .urlError) } + return options.queue.async { completion(account, nil, nil, .urlError) } } let method = HTTPMethod(rawValue: "SEARCH") var urlRequest: URLRequest @@ -404,7 +404,7 @@ public extension NextcloudKit { urlRequest.httpBody = httpBody urlRequest.timeoutInterval = options.timeout } catch { - return options.queue.async { completion(account, files, nil, NKError(error: error)) } + return options.queue.async { completion(account, nil, nil, NKError(error: error)) } } nkSession.sessionData.request(urlRequest, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in @@ -414,13 +414,15 @@ public extension NextcloudKit { switch response.result { case .failure(let error): let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, files, response, error) } + options.queue.async { completion(account, nil, response, error) } case .success: if let xmlData = response.data { - files = NKDataFileXML(nkCommonInstance: self.nkCommonInstance).convertDataFile(xmlData: xmlData, nkSession: nkSession, showHiddenFiles: showHiddenFiles, includeHiddenFiles: includeHiddenFiles) - options.queue.async { completion(account, files, response, .success) } + Task { + let files = await NKDataFileXML(nkCommonInstance: self.nkCommonInstance).convertDataFile(xmlData: xmlData, nkSession: nkSession, showHiddenFiles: showHiddenFiles, includeHiddenFiles: includeHiddenFiles) + options.queue.async { completion(account, files, response, .success) } + } } else { - options.queue.async { completion(account, files, response, .xmlError) } + options.queue.async { completion(account, nil, response, .xmlError) } } } } @@ -435,7 +437,7 @@ public extension NextcloudKit { /// options.contentType = "application/xml" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, .urlError) } } @@ -487,14 +489,13 @@ public extension NextcloudKit { /// options.contentType = "application/xml" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } } let serverUrlFileName = nkSession.urlBase + "/" + nkSession.dav + "/files/" + nkSession.userId - var files: [NKFile] = [] guard let url = serverUrlFileName.encodedToUrl else { - return options.queue.async { completion(account, files, nil, .urlError) } + return options.queue.async { completion(account, nil, nil, .urlError) } } let method = HTTPMethod(rawValue: "REPORT") var urlRequest: URLRequest @@ -504,7 +505,7 @@ public extension NextcloudKit { urlRequest.httpBody = NKDataFileXML(nkCommonInstance: self.nkCommonInstance).getRequestBodyFileListingFavorites(createProperties: options.createProperties, removeProperties: options.removeProperties).data(using: .utf8) urlRequest.timeoutInterval = options.timeout } catch { - return options.queue.async { completion(account, files, nil, NKError(error: error)) } + return options.queue.async { completion(account, nil, nil, NKError(error: error)) } } nkSession.sessionData.request(urlRequest, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in @@ -514,13 +515,15 @@ public extension NextcloudKit { switch response.result { case .failure(let error): let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, files, response, error) } + options.queue.async { completion(account, nil, response, error) } case .success: if let xmlData = response.data { - files = NKDataFileXML(nkCommonInstance: self.nkCommonInstance).convertDataFile(xmlData: xmlData, nkSession: nkSession, showHiddenFiles: showHiddenFiles, includeHiddenFiles: includeHiddenFiles) - options.queue.async { completion(account, files, response, .success) } + Task { + let files = await NKDataFileXML(nkCommonInstance: self.nkCommonInstance).convertDataFile(xmlData: xmlData, nkSession: nkSession, showHiddenFiles: showHiddenFiles, includeHiddenFiles: includeHiddenFiles) + options.queue.async { completion(account, files, response, .success) } + } } else { - options.queue.async { completion(account, files, response, .xmlError) } + options.queue.async { completion(account, nil, response, .xmlError) } } } } @@ -546,7 +549,7 @@ public extension NextcloudKit { /// options.contentType = "application/xml" /// - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } } @@ -554,9 +557,8 @@ public extension NextcloudKit { if let filename { serverUrlFileName = serverUrlFileName + filename } - var items: [NKTrash] = [] guard let url = serverUrlFileName.encodedToUrl else { - return options.queue.async { completion(account, items, nil, .urlError) } + return options.queue.async { completion(account, nil, nil, .urlError) } } let method = HTTPMethod(rawValue: "PROPFIND") headers.update(name: "Depth", value: "1") @@ -567,7 +569,7 @@ public extension NextcloudKit { urlRequest.httpBody = NKDataFileXML(nkCommonInstance: self.nkCommonInstance).requestBodyTrash.data(using: .utf8) urlRequest.timeoutInterval = options.timeout } catch { - return options.queue.async { completion(account, items, nil, NKError(error: error)) } + return options.queue.async { completion(account, nil, nil, NKError(error: error)) } } nkSession.sessionData.request(urlRequest, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in @@ -577,13 +579,15 @@ public extension NextcloudKit { switch response.result { case .failure(let error): let error = NKError(error: error, afResponse: response, responseData: response.data) - options.queue.async { completion(account, items, response, error) } + options.queue.async { completion(account, nil, response, error) } case .success: if let xmlData = response.data { - items = NKDataFileXML(nkCommonInstance: self.nkCommonInstance).convertDataTrash(xmlData: xmlData, nkSession: nkSession, showHiddenFiles: showHiddenFiles) - options.queue.async { completion(account, items, response, .success) } + Task { + let items = await NKDataFileXML(nkCommonInstance: self.nkCommonInstance).convertDataTrash(xmlData: xmlData, nkSession: nkSession, showHiddenFiles: showHiddenFiles) + options.queue.async { completion(account, items, response, .success) } + } } else { - options.queue.async { completion(account, items, response, .xmlError) } + options.queue.async { completion(account, nil, response, .xmlError) } } } } diff --git a/Sources/NextcloudKit/NextcloudKit.swift b/Sources/NextcloudKit/NextcloudKit.swift index bb85c609..4d0337ee 100644 --- a/Sources/NextcloudKit/NextcloudKit.swift +++ b/Sources/NextcloudKit/NextcloudKit.swift @@ -87,10 +87,10 @@ open class NextcloudKit { httpMaximumConnectionsPerHostInDownload: Int = 6, httpMaximumConnectionsPerHostInUpload: Int = 6, groupIdentifier: String) { - if nkCommonInstance.nksessions.filter({ $0.account == account }).first != nil { + if nkCommonInstance.nksessions.contains(account: account) { return updateSession(account: account, urlBase: urlBase, userId: userId, password: password, userAgent: userAgent) } - + let nkSession = NKSession( nkCommonInstance: nkCommonInstance, urlBase: urlBase, @@ -115,7 +115,10 @@ open class NextcloudKit { password: String? = nil, userAgent: String? = nil, replaceWithAccount: String? = nil) { - guard var nkSession = nkCommonInstance.nksessions.filter({ $0.account == account }).first else { return } + guard var nkSession = nkCommonInstance.nksessions.session(forAccount: account) else { + return + } + if let urlBase { nkSession.urlBase = urlBase } @@ -136,18 +139,10 @@ open class NextcloudKit { } } - public func removeSession(account: String) { - if let index = nkCommonInstance.nksessions.index(where: { $0.account == account}) { - nkCommonInstance.nksessions.remove(at: index) - } - } - - public func getSession(account: String) -> NKSession? { - return nkCommonInstance.nksessions.filter({ $0.account == account }).first - } - public func deleteCookieStorageForAccount(_ account: String) { - guard let nkSession = nkCommonInstance.nksessions.filter({ $0.account == account }).first else { return } + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account) else { + return + } if let cookieStore = nkSession.sessionData.session.configuration.httpCookieStorage { for cookie in cookieStore.cookies ?? [] { @@ -167,13 +162,13 @@ open class NextcloudKit { reachabilityManager?.startListening(onUpdatePerforming: { status in switch status { case .unknown: - self.nkCommonInstance.delegate?.networkReachabilityObserver(NKCommon.TypeReachability.unknown) + self.nkCommonInstance.delegate?.networkReachabilityObserver(.unknown) case .notReachable: - self.nkCommonInstance.delegate?.networkReachabilityObserver(NKCommon.TypeReachability.notReachable) + self.nkCommonInstance.delegate?.networkReachabilityObserver(.notReachable) case .reachable(.ethernetOrWiFi): - self.nkCommonInstance.delegate?.networkReachabilityObserver(NKCommon.TypeReachability.reachableEthernetOrWiFi) + self.nkCommonInstance.delegate?.networkReachabilityObserver(.reachableEthernetOrWiFi) case .reachable(.cellular): - self.nkCommonInstance.delegate?.networkReachabilityObserver(NKCommon.TypeReachability.reachableCellular) + self.nkCommonInstance.delegate?.networkReachabilityObserver(.reachableCellular) } }) } diff --git a/Sources/NextcloudKit/NextcloudKitBackground.swift b/Sources/NextcloudKit/NextcloudKitBackground.swift index 5ab72d2b..76810f40 100644 --- a/Sources/NextcloudKit/NextcloudKitBackground.swift +++ b/Sources/NextcloudKit/NextcloudKitBackground.swift @@ -39,7 +39,7 @@ public final class NKBackground: NSObject, URLSessionTaskDelegate, URLSessionDel url = (serverUrlFileName as? String)?.encodedToUrl as? URL } - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let urlForRequest = url else { return (nil, .urlError) @@ -95,7 +95,7 @@ public final class NKBackground: NSObject, URLSessionTaskDelegate, URLSessionDel url = (serverUrlFileName as? String)?.encodedToUrl as? URL } - guard let nkSession = nkCommonInstance.getSession(account: account), + guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account), let urlForRequest = url else { return (nil, .urlError) diff --git a/Sources/NextcloudKit/NextcloudKitSessionDelegate.swift b/Sources/NextcloudKit/NextcloudKitSessionDelegate.swift index 789d00a1..69b9f05c 100644 --- a/Sources/NextcloudKit/NextcloudKitSessionDelegate.swift +++ b/Sources/NextcloudKit/NextcloudKitSessionDelegate.swift @@ -13,7 +13,7 @@ import UIKit import Alamofire import SwiftyJSON -final class NextcloudKitSessionDelegate: SessionDelegate { +final class NextcloudKitSessionDelegate: SessionDelegate, @unchecked Sendable { public let nkCommonInstance: NKCommon? public init(fileManager: FileManager = .default, nkCommonInstance: NKCommon? = nil) { diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift new file mode 100644 index 00000000..d4304476 --- /dev/null +++ b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import UniformTypeIdentifiers + +public class NKFileProperty: NSObject { + public var classFile: NKTypeClassFile = .unknow + public var iconName: NKTypeIconFile = .unknow + public var name: String = "" + public var ext: String = "" +} + +public enum NKTypeClassFile: String { + case audio = "audio" + case compress = "compress" + case directory = "directory" + case document = "document" + case image = "image" + case unknow = "unknow" + case url = "url" + case video = "video" +} + +public enum NKTypeIconFile: String { + case audio = "audio" + case code = "code" + case compress = "compress" + case directory = "directory" + case document = "document" + case image = "image" + case video = "video" + case pdf = "pdf" + case ppt = "ppt" + case txt = "txt" + case unknow = "file" + case url = "url" + case xls = "xls" +} + +/// Class responsible for resolving NKFileProperty information from a given UTI. +public final class NKFilePropertyResolver { + + public init() {} + + public func resolve(inUTI: String, account: String) -> NKFileProperty { + let fileProperty = NKFileProperty() + let typeIdentifier = inUTI as String + let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: account) + let utiString = inUTI as String + + // Preferred extension + if let type = UTType(utiString), + let ext = type.preferredFilenameExtension { + fileProperty.ext = ext + } + + // Collabora Nextcloud Text Office + if capabilities.richDocumentsMimetypes.contains(typeIdentifier) { + fileProperty.classFile = .document + fileProperty.iconName = .document + fileProperty.name = "document" + + return fileProperty + } + + // Special-case identifiers + switch typeIdentifier { + case "text/plain", "text/html", "net.daringfireball.markdown", "text/x-markdown": + fileProperty.classFile = .document + fileProperty.iconName = .document + fileProperty.name = "markdown" + return fileProperty + case "com.microsoft.word.doc": + fileProperty.classFile = .document + fileProperty.iconName = .document + fileProperty.name = "document" + return fileProperty + case "com.apple.iwork.keynote.key": + fileProperty.classFile = .document + fileProperty.iconName = .ppt + fileProperty.name = "keynote" + return fileProperty + case "com.microsoft.excel.xls": + fileProperty.classFile = .document + fileProperty.iconName = .xls + fileProperty.name = "sheet" + return fileProperty + case "com.apple.iwork.numbers.numbers": + fileProperty.classFile = .document + fileProperty.iconName = .xls + fileProperty.name = "numbers" + return fileProperty + case "com.microsoft.powerpoint.ppt": + fileProperty.classFile = .document + fileProperty.iconName = .ppt + fileProperty.name = "presentation" + default: + break + } + + // Well-known UTI type classifications + if let type = UTType(utiString) { + if type.conforms(to: .image) { + fileProperty.classFile = .image + fileProperty.iconName = .image + fileProperty.name = "image" + + } else if type.conforms(to: .movie) { + fileProperty.classFile = .video + fileProperty.iconName = .video + fileProperty.name = "movie" + + } else if type.conforms(to: .audio) { + fileProperty.classFile = .audio + fileProperty.iconName = .audio + fileProperty.name = "audio" + + } else if type.conforms(to: .zip) { + fileProperty.classFile = .compress + fileProperty.iconName = .compress + fileProperty.name = "archive" + + } else if type.conforms(to: .html) { + fileProperty.classFile = .document + fileProperty.iconName = .code + fileProperty.name = "code" + + } else if type.conforms(to: .pdf) { + fileProperty.classFile = .document + fileProperty.iconName = .pdf + fileProperty.name = "document" + + } else if type.conforms(to: .rtf) { + fileProperty.classFile = .document + fileProperty.iconName = .txt + fileProperty.name = "document" + + } else if type.conforms(to: .text) { + // Default to .txt if extension is empty + if fileProperty.ext.isEmpty { + fileProperty.ext = "txt" + } + fileProperty.classFile = .document + fileProperty.iconName = .txt + fileProperty.name = "text" + + } else if type.conforms(to: .content) { + fileProperty.classFile = .document + fileProperty.iconName = .document + fileProperty.name = "document" + + } else { + fileProperty.classFile = .unknow + fileProperty.iconName = .unknow + fileProperty.name = "file" + } + } else { + // tipo UTI non valido + fileProperty.classFile = .unknow + fileProperty.iconName = .unknow + fileProperty.name = "file" + } + + return fileProperty + } +} diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift new file mode 100644 index 00000000..edf08cb0 --- /dev/null +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import UniformTypeIdentifiers + +/// Resolved file type metadata, used for cache and classification +public struct NKTypeIdentifierCache: Sendable { + public let mimeType: String + public let classFile: String + public let iconName: String + public let typeIdentifier: String + public let fileNameWithoutExt: String + public let ext: String +} + +/// Actor responsible for resolving file type metadata (UTI, MIME type, icon, class file, etc.) +public actor NKTypeIdentifiers { + public static let shared = NKTypeIdentifiers() + // Cache: extension → resolved type info + private var filePropertyCache: [String: NKTypeIdentifierCache] = [:] + // Internal resolver + private let resolver = NKFilePropertyResolver() + + private init() {} + + // Resolves type info from file name and optional MIME type + public func getInternalType(fileName: String, mimeType inputMimeType: String, directory: Bool, account: String) -> NKTypeIdentifierCache { + + var ext = (fileName as NSString).pathExtension.lowercased() + var mimeType = inputMimeType + var classFile = "" + var iconName = "" + var typeIdentifier = "" + var fileNameWithoutExt = (fileName as NSString).deletingPathExtension + + // Use full name if no extension + if ext.isEmpty { + fileNameWithoutExt = fileName + } + + // Check cache first + if let cached = filePropertyCache[ext] { + return cached + } + + // Resolve UTType + let type = UTType(filenameExtension: ext) ?? .data + typeIdentifier = type.identifier + + // Resolve MIME type + if mimeType.isEmpty { + mimeType = type.preferredMIMEType ?? "application/octet-stream" + } + + // Handle folder case + if directory { + mimeType = "httpd/unix-directory" + classFile = NKTypeClassFile.directory.rawValue + iconName = NKTypeIconFile.directory.rawValue + typeIdentifier = UTType.folder.identifier + fileNameWithoutExt = fileName + ext = "" + } else { + let props = resolver.resolve(inUTI: typeIdentifier, account: account) + classFile = props.classFile.rawValue + iconName = props.iconName.rawValue + } + + // Construct result + let result = NKTypeIdentifierCache( + mimeType: mimeType, + classFile: classFile, + iconName: iconName, + typeIdentifier: typeIdentifier, + fileNameWithoutExt: fileNameWithoutExt, + ext: ext + ) + + // Cache it + if !ext.isEmpty { + filePropertyCache[ext] = result + } + + return result + } + + // Clears the internal cache (used for testing or reset) + public func clearCache() { + filePropertyCache.removeAll() + } +} + +/// Helper class to access NKTypeIdentifiers from sync contexts (e.g. in legacy code or libraries). +public final class NKTypeIdentifiersHelper { + public static let shared = NKTypeIdentifiersHelper() + + // Internal actor reference (uses NKTypeIdentifiers.shared by default) + private let actor: NKTypeIdentifiers + + private init() { + self.actor = .shared + } + + // Init with optional custom actor (useful for testing) + public init(actor: NKTypeIdentifiers = .shared) { + self.actor = actor + } + + // Synchronously resolves file type info by calling the async actor inside a semaphore block. + public func getInternalTypeSync(fileName: String, mimeType: String, directory: Bool, account: String) -> NKTypeIdentifierCache { + var result: NKTypeIdentifierCache? + let semaphore = DispatchSemaphore(value: 0) + + Task { + result = await actor.getInternalType( + fileName: fileName, + mimeType: mimeType, + directory: directory, + account: account + ) + semaphore.signal() + } + + semaphore.wait() + return result! + } +} diff --git a/Sources/NextcloudKit/Utils/SynchronizedNKSessionArray.swift b/Sources/NextcloudKit/Utils/SynchronizedNKSessionArray.swift new file mode 100644 index 00000000..a2d84055 --- /dev/null +++ b/Sources/NextcloudKit/Utils/SynchronizedNKSessionArray.swift @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation + +/// A thread-safe container for managing an array of `NKSession` instances. +/// +/// Internally uses a concurrent `DispatchQueue` with barrier writes to ensure safe concurrent access and mutation. +/// Conforms to `@unchecked Sendable` for Swift 6 compatibility. +public final class SynchronizedNKSessionArray: @unchecked Sendable { + + // MARK: - Internal Storage + + /// Internal storage for the session array. + private var array: [NKSession] + + /// Dispatch queue used for synchronizing access to the array. + private let queue: DispatchQueue + + // MARK: - Initialization + + /// Initializes a new synchronized array with optional initial content. + /// - Parameter initial: An initial array of `NKSession` to populate the container with. + public init(_ initial: [NKSession] = []) { + self.array = initial + self.queue = DispatchQueue(label: "com.nextcloud.SynchronizedNKSessionArray", attributes: .concurrent) + } + + // MARK: - Read Operations + + /// Returns the number of sessions currently stored. + public var count: Int { + queue.sync { array.count } + } + + /// Returns a Boolean value indicating whether the array is empty. + public var isEmpty: Bool { + queue.sync { array.isEmpty } + } + + /// Returns a snapshot of all stored sessions. + public var all: [NKSession] { + queue.sync { array } + } + + /// Calls the given closure on each session in the array, in order. + /// - Parameter body: A closure that takes a `NKSession` as a parameter. + public func forEach(_ body: (NKSession) -> Void) { + queue.sync { + array.forEach(body) + } + } + + /// Returns the first session matching a given account string. + /// - Parameter account: The account identifier string to match. + /// - Returns: A `NKSession` instance if found, otherwise `nil`. + public func session(forAccount account: String) -> NKSession? { + queue.sync { + for session in array { + if session.account == account { + return session + } + } + return nil + } + } + + /// Checks whether a session for a given account exists. + /// - Parameter account: The account identifier string to check. + /// - Returns: `true` if a matching session exists, `false` otherwise. + public func contains(account: String) -> Bool { + queue.sync { + array.contains(where: { $0.account == account }) + } + } + + // MARK: - Write Operations + + /// Appends a new session to the array. + /// - Parameter element: The `NKSession` to append. + public func append(_ element: NKSession) { + queue.async(flags: .barrier) { + self.array.append(element) + } + } + + /// Removes all sessions associated with the given account. + /// - Parameter account: The account identifier string to remove sessions for. + public func remove(account: String) { + queue.async(flags: .barrier) { + self.array.removeAll { $0.account == account } + } + } + + /// Removes all sessions from the array. + public func removeAll() { + queue.async(flags: .barrier) { + self.array.removeAll() + } + } + + // MARK: - Subscript + + /// Accesses the session at a given index. + /// - Parameter index: The index of the desired session. + /// - Returns: A `NKSession` if the index is valid, otherwise `nil`. + public subscript(index: Int) -> NKSession? { + queue.sync { + guard array.indices.contains(index) else { return nil } + return array[index] + } + } +} diff --git a/Sources/NextcloudKit/Utils/ThreadSafeArray.swift b/Sources/NextcloudKit/Utils/ThreadSafeArray.swift deleted file mode 100644 index fae38414..00000000 --- a/Sources/NextcloudKit/Utils/ThreadSafeArray.swift +++ /dev/null @@ -1,276 +0,0 @@ -// SPDX-FileCopyrightText: Nextcloud GmbH -// SPDX-FileCopyrightText: 2024 Marino Faggiana -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This code derived from: http://basememara.com/creating-thread-safe-arrays-in-swift/ - -import Foundation - -/// A thread-safe array. -public struct ThreadSafeArray: Sendable { - - private var array = [Element]() - - public init() { } - - public init(_ array: [Element]) { - self.init() - self.array = array - } -} - -// MARK: - Properties - -public extension ThreadSafeArray { - - /// The first element of the collection. - var first: Element? { - NSLock().perform { self.array.first } - } - - /// The last element of the collection. - var last: Element? { - NSLock().perform { self.array.last } - } - - /// The number of elements in the array. - var count: Int { - NSLock().perform { self.array.count } - } - - /// A Boolean value indicating whether the collection is empty. - var isEmpty: Bool { - NSLock().perform { self.array.isEmpty } - } - - /// A textual representation of the array and its elements. - var description: String { - NSLock().perform { self.array.description } - } -} - -// MARK: - Immutable - -public extension ThreadSafeArray { - - /// Returns the first element of the sequence that satisfies the given predicate. - /// - /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match. - /// - Returns: The first element of the sequence that satisfies predicate, or nil if there is no element that satisfies predicate. - func first(where predicate: (Element) -> Bool) -> Element? { - NSLock().perform { self.array.first(where: predicate) } - } - - /// Returns the last element of the sequence that satisfies the given predicate. - /// - /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match. - /// - Returns: The last element of the sequence that satisfies predicate, or nil if there is no element that satisfies predicate. - func last(where predicate: (Element) -> Bool) -> Element? { - NSLock().perform { self.array.last(where: predicate) } - } - - /// Returns an array containing, in order, the elements of the sequence that satisfy the given predicate. - /// - /// - Parameter isIncluded: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the returned array. - /// - Returns: An array of the elements that includeElement allowed. - func filter(_ isIncluded: @escaping (Element) -> Bool) -> ThreadSafeArray { - NSLock().perform { ThreadSafeArray(self.array.filter(isIncluded)) } - } - - /// Returns the first index in which an element of the collection satisfies the given predicate. - /// - /// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match. - /// - Returns: The index of the first element for which predicate returns true. If no elements in the collection satisfy the given predicate, returns nil. - func index(where predicate: (Element) -> Bool) -> Int? { - NSLock().perform { self.array.firstIndex(where: predicate) } - } - - /// Returns the elements of the collection, sorted using the given predicate as the comparison between elements. - /// - /// - Parameter areInIncreasingOrder: A predicate that returns true if its first argument should be ordered before its second argument; otherwise, false. - /// - Returns: A sorted array of the collection’s elements. - func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> ThreadSafeArray { - NSLock().perform { ThreadSafeArray(self.array.sorted(by: areInIncreasingOrder)) } - } - - /// Returns an array containing the results of mapping the given closure over the sequence’s elements. - /// - /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value. - /// - Returns: An array of the non-nil results of calling transform with each element of the sequence. - func map(_ transform: @escaping (Element) -> ElementOfResult) -> [ElementOfResult] { - NSLock().perform { self.array.map(transform) } - } - - /// Returns an array containing the non-nil results of calling the given transformation with each element of this sequence. - /// - /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value. - /// - Returns: An array of the non-nil results of calling transform with each element of the sequence. - func compactMap(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] { - NSLock().perform { self.array.compactMap(transform) } - } - - /// Returns the result of combining the elements of the sequence using the given closure. - /// - /// - Parameters: - /// - initialResult: The value to use as the initial accumulating value. initialResult is passed to nextPartialResult the first time the closure is executed. - /// - nextPartialResult: A closure that combines an accumulating value and an element of the sequence into a new accumulating value, to be used in the next call of the nextPartialResult closure or returned to the caller. - /// - Returns: The final accumulated value. If the sequence has no elements, the result is initialResult. - func reduce(_ initialResult: ElementOfResult, _ nextPartialResult: @escaping (ElementOfResult, Element) -> ElementOfResult) -> ElementOfResult { - NSLock().perform { self.array.reduce(initialResult, nextPartialResult) } - } - - /// Returns the result of combining the elements of the sequence using the given closure. - /// - /// - Parameters: - /// - initialResult: The value to use as the initial accumulating value. - /// - updateAccumulatingResult: A closure that updates the accumulating value with an element of the sequence. - /// - Returns: The final accumulated value. If the sequence has no elements, the result is initialResult. - func reduce(into initialResult: ElementOfResult, _ updateAccumulatingResult: @escaping (inout ElementOfResult, Element) -> Void) -> ElementOfResult { - NSLock().perform { self.array.reduce(into: initialResult, updateAccumulatingResult) } - } - - /// Calls the given closure on each element in the sequence in the same order as a for-in loop. - /// - /// - Parameter body: A closure that takes an element of the sequence as a parameter. - func forEach(_ body: (Element) -> Void) { - NSLock().perform { self.array.forEach(body) } - } - - /// Returns a Boolean value indicating whether the sequence contains an element that satisfies the given predicate. - /// - /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element represents a match. - /// - Returns: true if the sequence contains an element that satisfies predicate; otherwise, false. - func contains(where predicate: (Element) -> Bool) -> Bool { - NSLock().perform { self.array.contains(where: predicate) } - } - - /// Returns a Boolean value indicating whether every element of a sequence satisfies a given predicate. - /// - /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element satisfies a condition. - /// - Returns: true if the sequence contains only elements that satisfy predicate; otherwise, false. - func allSatisfy(_ predicate: (Element) -> Bool) -> Bool { - NSLock().perform { self.array.allSatisfy(predicate) } - } - - /// Returns the array - /// - /// - Returns: the array part. - func getArray() -> [Element]? { - NSLock().perform { self.array } - } -} - -// MARK: - Mutable - -public extension ThreadSafeArray { - - /// Adds a new element at the end of the array. - /// - /// - Parameter element: The element to append to the array. - mutating func append(_ element: Element) { - NSLock().perform { self.array.append(element) } - } - - /// Adds new elements at the end of the array. - /// - /// - Parameter element: The elements to append to the array. - mutating func append(_ elements: [Element]) { - NSLock().perform { self.array += elements } - } - - /// Inserts a new element at the specified position. - /// - /// - Parameters: - /// - element: The new element to insert into the array. - /// - index: The position at which to insert the new element. - mutating func insert(_ element: Element, at index: Int) { - NSLock().perform { self.array.insert(element, at: index) } - } - - /// Removes and returns the element at the specified position. - /// - /// - Parameters: - /// - index: The position of the element to remove. - /// - completion: The handler with the removed element. - mutating func remove(at index: Int, completion: ((Element) -> Void)? = nil) { - NSLock().perform { self.array.remove(at: index) } - } - - /// Removes and returns the elements that meet the criteria. - /// - /// - Parameters: - /// - predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match. - /// - completion: The handler with the removed elements. - mutating func remove(where predicate: @escaping (Element) -> Bool) -> [Element] { - return NSLock().perform { - var elements = [Element]() - while let index = self.array.firstIndex(where: predicate) { - elements.append(self.array.remove(at: index)) - } - return elements - } - } - - /// Removes all elements from the array. - /// - /// - Parameter keepingCapacity: Pass true to keep the existing capacity of the array after removing its elements. The default value is false. - mutating func removeAll(keepingCapacity: Bool = false) { - NSLock().perform { self.array.removeAll(keepingCapacity: keepingCapacity) } - } -} - -public extension ThreadSafeArray { - - /// Accesses the element at the specified position if it exists. - /// - /// - Parameter index: The position of the element to access. - /// - Returns: optional element if it exists. - subscript(index: Int) -> Element? { - get { - NSLock().perform { - guard self.array.startIndex.. Bool { - NSLock().perform { self.array.contains(element) } - } -} - -// MARK: - Infix operators - -public extension ThreadSafeArray { - - /// Adds a new element at the end of the array. - /// - /// - Parameters: - /// - left: The collection to append to. - /// - right: The element to append to the array. - static func += (left: inout ThreadSafeArray, right: Element) { - left.append(right) - } - - /// Adds new elements at the end of the array. - /// - /// - Parameters: - /// - left: The collection to append to. - /// - right: The elements to append to the array. - static func += (left: inout ThreadSafeArray, right: [Element]) { - left.append(right) - } -}