From 5de17719ab6c6e3bd661b02aa2500c3f6c327f60 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 18 Jun 2025 18:40:50 +0200 Subject: [PATCH 01/41] internal Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/NKCommon.swift | 100 ++++++++++++++++++++------- Sources/NextcloudKit/NKSession.swift | 1 - 2 files changed, 75 insertions(+), 26 deletions(-) diff --git a/Sources/NextcloudKit/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 598e8d2e..5b64ea1e 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -108,14 +108,7 @@ public struct NKCommon: Sendable { 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() @@ -126,7 +119,6 @@ public struct NKCommon: Sendable { internal var mimeTypeCache = [String: String]() internal var filePropertiesCache = [String: NKFileProperty]() #endif - internal var internalTypeIdentifiers = ThreadSafeArray() // MARK: - Init @@ -136,18 +128,16 @@ public struct NKCommon: Sendable { // 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) { + 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 = "" @@ -280,10 +270,70 @@ public struct NKCommon: Sendable { 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 + if typeIdentifier == "text/plain" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "markdown" + } else if typeIdentifier == "text/html" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "markdown" + } else if typeIdentifier == "net.daringfireball.markdown" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "markdown" + } else if typeIdentifier == "text/x-markdown" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "markdown" + } else if typeIdentifier == "org.oasis-open.opendocument.text" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "document" + } else if typeIdentifier == "org.openxmlformats.wordprocessingml.document" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "document" + } else if typeIdentifier == "com.microsoft.word.doc" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "document" + } else if typeIdentifier == "com.apple.iwork.keynote.key" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "pages" + } else if typeIdentifier == "org.oasis-open.opendocument.spreadsheet" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.name = "sheet" + } else if typeIdentifier == "org.openxmlformats.spreadsheetml.sheet" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.name = "sheet" + } else if typeIdentifier == "com.microsoft.excel.xls" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.name = "sheet" + } else if typeIdentifier == "com.apple.iwork.numbers.numbers" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.name = "numbers" + } else if typeIdentifier == "org.oasis-open.opendocument.presentation" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.name = "presentation" + } else if typeIdentifier == "org.openxmlformats.presentationml.presentation" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.name = "presentation" + } else if typeIdentifier == "com.microsoft.powerpoint.ppt" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.name = "presentation" + } else if typeIdentifier == "com.apple.iwork.keynote.key" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.name = "keynote" } else { if UTTypeConformsTo(inUTI, kUTTypeContent) { fileProperty.classFile = TypeClassFile.document.rawValue 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 From fa3e231ebed8ea4eeeb941af81e0542dffe7e07f Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 07:16:15 +0200 Subject: [PATCH 02/41] cod Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/NKCommon.swift | 156 +++++++++++++++------------- 1 file changed, 84 insertions(+), 72 deletions(-) diff --git a/Sources/NextcloudKit/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 5b64ea1e..3ea763f9 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -209,7 +209,7 @@ public struct NKCommon: Sendable { if let cachedFileProperties = filePropertiesCache.object(forKey: inUTI) { fileProperties = cachedFileProperties } else { - fileProperties = getFileProperties(inUTI: inUTI) + fileProperties = getFileProperties(inUTI: inUTI, account: account) filePropertiesCache.setObject(fileProperties, forKey: inUTI) } #else @@ -228,9 +228,10 @@ public struct NKCommon: Sendable { return(mimeType: mimeType, classFile: classFile, iconName: iconName, typeIdentifier: typeIdentifier, fileNameWithoutExt: fileNameWithoutExt, ext: ext) } - public func getFileProperties(inUTI: CFString) -> NKFileProperty { + public func getFileProperties(inUTI: CFString, account: String) -> NKFileProperty { let fileProperty = NKFileProperty() let typeIdentifier: String = inUTI as String + let capabilities = NCCapabilities.shared.getCapabilitiesBlocking(for: account) if let fileExtension = UTTypeCopyPreferredTagWithClass(inUTI as CFString, kUTTagClassFilenameExtension) { fileProperty.ext = String(fileExtension.takeRetainedValue()) @@ -269,82 +270,93 @@ public struct NKCommon: Sendable { fileProperty.classFile = TypeClassFile.document.rawValue fileProperty.iconName = TypeIconFile.txt.rawValue fileProperty.name = "text" + } else if typeIdentifier == "text/plain" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "markdown" + } else if typeIdentifier == "text/html" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "markdown" + } else if typeIdentifier == "net.daringfireball.markdown" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "markdown" + } else if typeIdentifier == "text/x-markdown" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "markdown" + } else if typeIdentifier == "org.oasis-open.opendocument.text" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "document" + } else if typeIdentifier == "org.openxmlformats.wordprocessingml.document" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "document" + } else if typeIdentifier == "com.microsoft.word.doc" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "document" + } else if typeIdentifier == "com.apple.iwork.keynote.key" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "pages" + } else if typeIdentifier == "org.oasis-open.opendocument.spreadsheet" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.name = "sheet" + } else if typeIdentifier == "org.openxmlformats.spreadsheetml.sheet" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.name = "sheet" + } else if typeIdentifier == "com.microsoft.excel.xls" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.name = "sheet" + } else if typeIdentifier == "com.apple.iwork.numbers.numbers" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.name = "numbers" + } else if typeIdentifier == "org.oasis-open.opendocument.presentation" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.name = "presentation" + } else if typeIdentifier == "org.openxmlformats.presentationml.presentation" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.name = "presentation" + } else if typeIdentifier == "com.microsoft.powerpoint.ppt" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.name = "presentation" + } else if typeIdentifier == "com.apple.iwork.keynote.key" { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.name = "keynote" } else { - if typeIdentifier == "text/plain" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "markdown" - } else if typeIdentifier == "text/html" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "markdown" - } else if typeIdentifier == "net.daringfireball.markdown" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "markdown" - } else if typeIdentifier == "text/x-markdown" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "markdown" - } else if typeIdentifier == "org.oasis-open.opendocument.text" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "document" - } else if typeIdentifier == "org.openxmlformats.wordprocessingml.document" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "document" - } else if typeIdentifier == "com.microsoft.word.doc" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "document" - } else if typeIdentifier == "com.apple.iwork.keynote.key" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "pages" - } else if typeIdentifier == "org.oasis-open.opendocument.spreadsheet" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.xls.rawValue - fileProperty.name = "sheet" - } else if typeIdentifier == "org.openxmlformats.spreadsheetml.sheet" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.xls.rawValue - fileProperty.name = "sheet" - } else if typeIdentifier == "com.microsoft.excel.xls" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.xls.rawValue - fileProperty.name = "sheet" - } else if typeIdentifier == "com.apple.iwork.numbers.numbers" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.xls.rawValue - fileProperty.name = "numbers" - } else if typeIdentifier == "org.oasis-open.opendocument.presentation" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.ppt.rawValue - fileProperty.name = "presentation" - } else if typeIdentifier == "org.openxmlformats.presentationml.presentation" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.ppt.rawValue - fileProperty.name = "presentation" - } else if typeIdentifier == "com.microsoft.powerpoint.ppt" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.ppt.rawValue - fileProperty.name = "presentation" - } else if typeIdentifier == "com.apple.iwork.keynote.key" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.ppt.rawValue - fileProperty.name = "keynote" - } else { - if UTTypeConformsTo(inUTI, kUTTypeContent) { + // Added UTI for Collabora + for mimeType in capabilities.richDocumentsMimetypes { + if typeIdentifier == mimeType { 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 } } + + + + 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 } From 2388b977801224fe5c291ba98e87a86dfce5e603 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 07:24:33 +0200 Subject: [PATCH 03/41] cod Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/NKCommon.swift | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/Sources/NextcloudKit/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 3ea763f9..944c6463 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -286,14 +286,6 @@ public struct NKCommon: Sendable { fileProperty.classFile = TypeClassFile.document.rawValue fileProperty.iconName = TypeIconFile.document.rawValue fileProperty.name = "markdown" - } else if typeIdentifier == "org.oasis-open.opendocument.text" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "document" - } else if typeIdentifier == "org.openxmlformats.wordprocessingml.document" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "document" } else if typeIdentifier == "com.microsoft.word.doc" { fileProperty.classFile = TypeClassFile.document.rawValue fileProperty.iconName = TypeIconFile.document.rawValue @@ -302,14 +294,6 @@ public struct NKCommon: Sendable { fileProperty.classFile = TypeClassFile.document.rawValue fileProperty.iconName = TypeIconFile.document.rawValue fileProperty.name = "pages" - } else if typeIdentifier == "org.oasis-open.opendocument.spreadsheet" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.xls.rawValue - fileProperty.name = "sheet" - } else if typeIdentifier == "org.openxmlformats.spreadsheetml.sheet" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.xls.rawValue - fileProperty.name = "sheet" } else if typeIdentifier == "com.microsoft.excel.xls" { fileProperty.classFile = TypeClassFile.document.rawValue fileProperty.iconName = TypeIconFile.xls.rawValue @@ -318,14 +302,6 @@ public struct NKCommon: Sendable { fileProperty.classFile = TypeClassFile.document.rawValue fileProperty.iconName = TypeIconFile.xls.rawValue fileProperty.name = "numbers" - } else if typeIdentifier == "org.oasis-open.opendocument.presentation" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.ppt.rawValue - fileProperty.name = "presentation" - } else if typeIdentifier == "org.openxmlformats.presentationml.presentation" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.ppt.rawValue - fileProperty.name = "presentation" } else if typeIdentifier == "com.microsoft.powerpoint.ppt" { fileProperty.classFile = TypeClassFile.document.rawValue fileProperty.iconName = TypeIconFile.ppt.rawValue From e0c71e3204eee97e6345e67cc862d5f855c7aabc Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 08:13:53 +0200 Subject: [PATCH 04/41] improved directEditing Signed-off-by: Marino Faggiana --- .../NKEditorDetailsConverter.swift | 16 +++ ...NKEditorDetailsResponse+NKConversion.swift | 13 +++ .../NKEditorDetailsResponse.swift | 50 +++++++++ .../Models/NKEditorDetailsCreators.swift | 15 --- .../Models/NKEditorDetailsEditors.swift | 13 --- .../Models/NKEditorTemplates.swift | 15 --- .../NextcloudKit+Capabilities.swift | 86 ++++++++------- .../NextcloudKit/NextcloudKit+NCText.swift | 101 ++++++++---------- .../NextcloudKitSessionDelegate.swift | 2 +- 9 files changed, 167 insertions(+), 144 deletions(-) create mode 100644 Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift create mode 100644 Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse+NKConversion.swift create mode 100644 Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift delete mode 100644 Sources/NextcloudKit/Models/NKEditorDetailsCreators.swift delete mode 100644 Sources/NextcloudKit/Models/NKEditorDetailsEditors.swift delete mode 100644 Sources/NextcloudKit/Models/NKEditorTemplates.swift diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift new file mode 100644 index 00000000..33f84587 --- /dev/null +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift @@ -0,0 +1,16 @@ +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() + + 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..1f19b311 --- /dev/null +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse+NKConversion.swift @@ -0,0 +1,13 @@ +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..9d6bbba0 --- /dev/null +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift @@ -0,0 +1,50 @@ + +import Foundation + +public struct NKEditorDetailsResponse: Codable { + public let ocs: OCS + + public struct OCS: Codable { + public let data: DataClass + + public struct DataClass: Codable { + 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 name: String + public let mimetypes: [String] + public let optionalMimetypes: [String]? + public let secure: Int +} + +public struct NKEditorDetailsCreator: Codable, Sendable { + public let editor: String + public let `extension`: String + public let id: String + public let mimetype: String + public let name: String + public let templates: Int +} + +public struct NKEditorTemplate: Codable, Sendable { + public let `extension`: String + public let id: String + public let name: String + public let preview: String +} 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/NextcloudKit+Capabilities.swift b/Sources/NextcloudKit/NextcloudKit+Capabilities.swift index 3d5f7a6a..a4b91d62 100644 --- a/Sources/NextcloudKit/NextcloudKit+Capabilities.swift +++ b/Sources/NextcloudKit/NextcloudKit+Capabilities.swift @@ -439,47 +439,51 @@ final public class NCCapabilities: Sendable { 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 editors: [NKEditorDetailsEditor] = [] + public var creators: [NKEditorDetailsCreator] = [] + public var templates: [NKEditorTemplate] = [] public init() {} } diff --git a/Sources/NextcloudKit/NextcloudKit+NCText.swift b/Sources/NextcloudKit/NextcloudKit+NCText.swift index a2369971..70fc9870 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), 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) - } - } - editor.name = subJson["name"].stringValue - if let optionalMimetypes = subJson["optionalMimetypes"].array { - for optionalMimetype in optionalMimetypes { - editor.optionalMimetypes.append(optionalMimetype.stringValue) - } - } - 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) } + 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 NCCapabilities.shared.getCapabilitiesAsync(for: account) + capabilities.editors = editors + capabilities.creators = creators + await NCCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) + + options.queue.async { + completion(account, editors, creators, response, .success) + } + + } catch { + nkLog(error: "Parsing error in NKEditorDetailsConverter: \(error)") + options.queue.async { + completion(account, nil, nil, response, .invalidData) + } + } + } } } } 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, @@ -118,9 +101,8 @@ 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), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { @@ -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 NCCapabilities.shared.getCapabilitiesAsync(for: account) + capabilities.templates = templates + await NCCapabilities.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)) 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) { From 5d6cf44e7f6fabd291baa15a9315cf07346d060c Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 09:01:29 +0200 Subject: [PATCH 05/41] NKCapabilities Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/NKCommon.swift | 4 ++-- .../NextcloudKit+Capabilities.swift | 20 +++++++++---------- .../NextcloudKit/NextcloudKit+NCText.swift | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/NextcloudKit/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 944c6463..5b635332 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -231,7 +231,7 @@ public struct NKCommon: Sendable { public func getFileProperties(inUTI: CFString, account: String) -> NKFileProperty { let fileProperty = NKFileProperty() let typeIdentifier: String = inUTI as String - let capabilities = NCCapabilities.shared.getCapabilitiesBlocking(for: account) + let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: account) if let fileExtension = UTTypeCopyPreferredTagWithClass(inUTI as CFString, kUTTagClassFilenameExtension) { fileProperty.ext = String(fileExtension.takeRetainedValue()) @@ -449,7 +449,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 { diff --git a/Sources/NextcloudKit/NextcloudKit+Capabilities.swift b/Sources/NextcloudKit/NextcloudKit+Capabilities.swift index a4b91d62..7da3797a 100644 --- a/Sources/NextcloudKit/NextcloudKit+Capabilities.swift +++ b/Sources/NextcloudKit/NextcloudKit+Capabilities.swift @@ -25,7 +25,7 @@ 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), let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint, options: options), @@ -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,20 +421,20 @@ 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() diff --git a/Sources/NextcloudKit/NextcloudKit+NCText.swift b/Sources/NextcloudKit/NextcloudKit+NCText.swift index 70fc9870..3ab3c8b2 100644 --- a/Sources/NextcloudKit/NextcloudKit+NCText.swift +++ b/Sources/NextcloudKit/NextcloudKit+NCText.swift @@ -30,10 +30,10 @@ public extension NextcloudKit { Task { do { let (editors, creators) = try NKEditorDetailsConverter.from(data: responseData) - let capabilities = await NCCapabilities.shared.getCapabilitiesAsync(for: account) + let capabilities = await NKCapabilities.shared.getCapabilitiesAsync(for: account) capabilities.editors = editors capabilities.creators = creators - await NCCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) + await NKCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) options.queue.async { completion(account, editors, creators, response, .success) @@ -123,9 +123,9 @@ public extension NextcloudKit { let decoded = try JSONDecoder().decode(NKEditorTemplateResponse.self, from: data) let templates = decoded.ocs.data.editors // Update capabilities - let capabilities = await NCCapabilities.shared.getCapabilitiesAsync(for: account) + let capabilities = await NKCapabilities.shared.getCapabilitiesAsync(for: account) capabilities.templates = templates - await NCCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) + await NKCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) options.queue.async { completion(account, templates, response, .success) } } catch { From c5f9347e665b748a2f5a12bf2aad3ee38a45762d Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 09:54:16 +0200 Subject: [PATCH 06/41] cod Signed-off-by: Marino Faggiana --- .../Models/EditorDetails/NKEditorDetailsConverter.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift index 33f84587..9b4671a2 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift @@ -7,6 +7,8 @@ public enum NKEditorDetailsConverter { /// - Returns: A tuple with editors and creators. /// - Throws: Decoding error if parsing fails. public static func from(data: Data) throws -> (editors: [NKEditorDetailsEditor], creators: [NKEditorDetailsCreator]) { + data.printJson() + let decoded = try JSONDecoder().decode(NKEditorDetailsResponse.self, from: data) let editors = decoded.ocs.data.editorsArray() let creators = decoded.ocs.data.creatorsArray() From 65592228a5339fdd9b4dc4fd435d1c177a1940c6 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 09:54:53 +0200 Subject: [PATCH 07/41] cleaning Signed-off-by: Marino Faggiana --- .../NextcloudKit/NextcloudKit+NCText.swift | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/NextcloudKit/NextcloudKit+NCText.swift b/Sources/NextcloudKit/NextcloudKit+NCText.swift index 3ab3c8b2..a38c283b 100644 --- a/Sources/NextcloudKit/NextcloudKit+NCText.swift +++ b/Sources/NextcloudKit/NextcloudKit+NCText.swift @@ -28,24 +28,24 @@ public extension NextcloudKit { 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.editors = editors - capabilities.creators = creators - await NKCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) + do { + let (editors, creators) = try NKEditorDetailsConverter.from(data: responseData) + let capabilities = await NKCapabilities.shared.getCapabilitiesAsync(for: account) + capabilities.editors = editors + capabilities.creators = creators + await NKCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) - options.queue.async { - completion(account, editors, creators, response, .success) - } + options.queue.async { + completion(account, editors, creators, response, .success) + } - } catch { - nkLog(error: "Parsing error in NKEditorDetailsConverter: \(error)") - options.queue.async { - completion(account, nil, nil, response, .invalidData) - } - } - } + } catch { + nkLog(error: "Parsing error in NKEditorDetailsConverter: \(error)") + options.queue.async { + completion(account, nil, nil, response, .invalidData) + } + } + } } } } From 09c33cd088ded42732d695b85e866dc822373432 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 09:58:17 +0200 Subject: [PATCH 08/41] fix parser Signed-off-by: Marino Faggiana --- .../NKEditorDetailsResponse.swift | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift index 9d6bbba0..9239aa00 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift @@ -1,13 +1,13 @@ import Foundation -public struct NKEditorDetailsResponse: Codable { +public struct NKEditorDetailsResponse: Codable, Sendable { public let ocs: OCS - public struct OCS: Codable { + public struct OCS: Codable, Sendable { public let data: DataClass - public struct DataClass: Codable { + public struct DataClass: Codable, Sendable { public let editors: [String: NKEditorDetailsEditor] public let creators: [String: NKEditorDetailsCreator] } @@ -27,19 +27,20 @@ public struct NKEditorTemplateResponse: Codable, Sendable { } public struct NKEditorDetailsEditor: Codable, Sendable { - public let name: String + public let id: String public let mimetypes: [String] - public let optionalMimetypes: [String]? - public let secure: Int + public let name: String + public let optionalMimetypes: [String] + public let secure: Bool } public struct NKEditorDetailsCreator: Codable, Sendable { - public let editor: String - public let `extension`: String public let id: String + public let templates: Bool public let mimetype: String public let name: String - public let templates: Int + public let editor: String + public let `extension`: String } public struct NKEditorTemplate: Codable, Sendable { From 95b7572c26d529f598af3ce66586d5653191ab09 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 10:17:55 +0200 Subject: [PATCH 09/41] rename Signed-off-by: Marino Faggiana --- .../NextcloudKit+Capabilities.swift | 90 +++++++++---------- .../NextcloudKit/NextcloudKit+NCText.swift | 6 +- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/Sources/NextcloudKit/NextcloudKit+Capabilities.swift b/Sources/NextcloudKit/NextcloudKit+Capabilities.swift index 7da3797a..df5c393f 100644 --- a/Sources/NextcloudKit/NextcloudKit+Capabilities.swift +++ b/Sources/NextcloudKit/NextcloudKit+Capabilities.swift @@ -439,51 +439,51 @@ final public class NKCapabilities: Sendable { 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 editors: [NKEditorDetailsEditor] = [] - public var creators: [NKEditorDetailsCreator] = [] - public var templates: [NKEditorTemplate] = [] + 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+NCText.swift b/Sources/NextcloudKit/NextcloudKit+NCText.swift index a38c283b..a5b5697b 100644 --- a/Sources/NextcloudKit/NextcloudKit+NCText.swift +++ b/Sources/NextcloudKit/NextcloudKit+NCText.swift @@ -31,8 +31,8 @@ public extension NextcloudKit { do { let (editors, creators) = try NKEditorDetailsConverter.from(data: responseData) let capabilities = await NKCapabilities.shared.getCapabilitiesAsync(for: account) - capabilities.editors = editors - capabilities.creators = creators + capabilities.directEditingEditors = editors + capabilities.directEditingCreators = creators await NKCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) options.queue.async { @@ -124,7 +124,7 @@ public extension NextcloudKit { let templates = decoded.ocs.data.editors // Update capabilities let capabilities = await NKCapabilities.shared.getCapabilitiesAsync(for: account) - capabilities.templates = templates + capabilities.directEditingTemplates = templates await NKCapabilities.shared.appendCapabilitiesAsync(for: account, capabilities: capabilities) options.queue.async { completion(account, templates, response, .success) } From 3a9e89c98b59017bf5d196f6bc966a95a37340d6 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 10:26:55 +0200 Subject: [PATCH 10/41] code Signed-off-by: Marino Faggiana --- .../NKEditorDetailsResponse.swift | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift index 9239aa00..cb21912a 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift @@ -27,25 +27,49 @@ public struct NKEditorTemplateResponse: Codable, Sendable { } public struct NKEditorDetailsEditor: Codable, Sendable { - public let id: String + 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 id: String + public let identifier: String public let templates: Bool public let mimetype: String public let name: String public let editor: String - public let `extension`: 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 let `extension`: String - public let id: String + public let ext: String + public let identifier: String public let name: String public let preview: String + + enum CodingKeys: String, CodingKey { + case ext = "extension" + case identifier = "id" + case name + case preview + } } From 00000cf158bdaec1b2dab658dcb60c15b8dd708e Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 10:35:39 +0200 Subject: [PATCH 11/41] added init Signed-off-by: Marino Faggiana --- .../Models/EditorDetails/NKEditorDetailsResponse.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift index cb21912a..03a007bb 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift @@ -72,4 +72,11 @@ public struct NKEditorTemplate: Codable, Sendable { 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 + } } From 3be9a486ae4618d7f742deba02ecae408e8c71fc Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 10:37:03 +0200 Subject: [PATCH 12/41] fix Signed-off-by: Marino Faggiana --- .../Models/EditorDetails/NKEditorDetailsResponse.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift index 03a007bb..ef5d28cb 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift @@ -61,10 +61,10 @@ public struct NKEditorDetailsCreator: Codable, Sendable { } public struct NKEditorTemplate: Codable, Sendable { - public let ext: String - public let identifier: String - public let name: String - public let preview: String + public var ext: String + public var identifier: String + public var name: String + public var preview: String enum CodingKeys: String, CodingKey { case ext = "extension" From 5e6c4bcd82a85576d750a796be51949dc20aca68 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 10:39:37 +0200 Subject: [PATCH 13/41] fix Signed-off-by: Marino Faggiana --- .../Models/EditorDetails/NKEditorDetailsResponse.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift index ef5d28cb..03a007bb 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift @@ -61,10 +61,10 @@ public struct NKEditorDetailsCreator: Codable, Sendable { } public struct NKEditorTemplate: Codable, Sendable { - public var ext: String - public var identifier: String - public var name: String - public var preview: String + public let ext: String + public let identifier: String + public let name: String + public let preview: String enum CodingKeys: String, CodingKey { case ext = "extension" From 2db62819f43cdde7ebfa5792fb54b4f5b76f5cf8 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 10:44:41 +0200 Subject: [PATCH 14/41] argh Signed-off-by: Marino Faggiana --- .../Models/EditorDetails/NKEditorDetailsResponse.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift index 03a007bb..ef5d28cb 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift @@ -61,10 +61,10 @@ public struct NKEditorDetailsCreator: Codable, Sendable { } public struct NKEditorTemplate: Codable, Sendable { - public let ext: String - public let identifier: String - public let name: String - public let preview: String + public var ext: String + public var identifier: String + public var name: String + public var preview: String enum CodingKeys: String, CodingKey { case ext = "extension" From 0914963e59f462a6820505bc48266442ee0718e8 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 15:53:03 +0200 Subject: [PATCH 15/41] new class Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/Models/NKTrash.swift | 2 +- Sources/NextcloudKit/NKCommon.swift | 272 +----------------- Sources/NextcloudKit/NKDataFileXML.swift | 14 +- .../NextcloudKit/NextcloudKit+WebDAV.swift | 58 ++-- Sources/NextcloudKit/NextcloudKit.swift | 8 +- .../NKFilePropertyResolver.swift | 133 +++++++++ .../TypeIdentifiers/NKTypeIdentifiers.swift | 92 ++++++ 7 files changed, 280 insertions(+), 299 deletions(-) create mode 100644 Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift create mode 100644 Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift diff --git a/Sources/NextcloudKit/Models/NKTrash.swift b/Sources/NextcloudKit/Models/NKTrash.swift index 7e554652..33b46b6d 100644 --- a/Sources/NextcloudKit/Models/NKTrash.swift +++ b/Sources/NextcloudKit/Models/NKTrash.swift @@ -5,7 +5,7 @@ import Foundation -public class NKTrash: NSObject { +public struct NKTrash: Sendable { public var ocId = "" public var contentType = "" public var date = Date() diff --git a/Sources/NextcloudKit/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 5b635332..7a668d6e 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -12,11 +12,18 @@ import MobileCoreServices import CoreServices #endif +public enum TypeReachability: 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: TypeReachability) func request(_ request: DataRequest, didParseResponse response: AFDataResponse) @@ -33,7 +40,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: TypeReachability) { } func request(_ request: DataRequest, didParseResponse response: AFDataResponse) { } @@ -51,6 +58,8 @@ public struct NKCommon: Sendable { public var delegate: NextcloudKitDelegate? public var groupIdentifier: String? + public let typeIdentifiers = NKTypeIdentifiers() + // Foreground public let identifierSessionDownload: String = "com.nextcloud.nextcloudkit.session.download" public let identifierSessionUpload: String = "com.nextcloud.nextcloudkit.session.upload" @@ -74,268 +83,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" - } - - -#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 // MARK: - Init - init() { - - } - - // MARK: - Type Identifier - - 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, account: account) - 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, account: String) -> NKFileProperty { - let fileProperty = NKFileProperty() - let typeIdentifier: String = inUTI as String - let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: account) - - 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 typeIdentifier == "text/plain" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "markdown" - } else if typeIdentifier == "text/html" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "markdown" - } else if typeIdentifier == "net.daringfireball.markdown" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "markdown" - } else if typeIdentifier == "text/x-markdown" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "markdown" - } else if typeIdentifier == "com.microsoft.word.doc" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "document" - } else if typeIdentifier == "com.apple.iwork.keynote.key" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "pages" - } else if typeIdentifier == "com.microsoft.excel.xls" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.xls.rawValue - fileProperty.name = "sheet" - } else if typeIdentifier == "com.apple.iwork.numbers.numbers" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.xls.rawValue - fileProperty.name = "numbers" - } else if typeIdentifier == "com.microsoft.powerpoint.ppt" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.ppt.rawValue - fileProperty.name = "presentation" - } else if typeIdentifier == "com.apple.iwork.keynote.key" { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.ppt.rawValue - fileProperty.name = "keynote" - } else { - // Added UTI for Collabora - for mimeType in capabilities.richDocumentsMimetypes { - if typeIdentifier == mimeType { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue - fileProperty.name = "document" - - return fileProperty - } - } - - - - 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 diff --git a/Sources/NextcloudKit/NKDataFileXML.swift b/Sources/NextcloudKit/NKDataFileXML.swift index d079d50f..ecad5ad2 100644 --- a/Sources/NextcloudKit/NKDataFileXML.swift +++ b/Sources/NextcloudKit/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,7 +534,7 @@ 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 @@ -557,8 +557,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 == TypeClassFile.image.rawValue, + files[index + 1].classFile == TypeClassFile.video.rawValue { files[index].livePhotoFile = files[index + 1].fileId files[index + 1].livePhotoFile = files[index].fileId } @@ -567,7 +567,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 +581,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,7 +644,7 @@ 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 diff --git a/Sources/NextcloudKit/NextcloudKit+WebDAV.swift b/Sources/NextcloudKit/NextcloudKit+WebDAV.swift index 45d0a349..50457824 100644 --- a/Sources/NextcloudKit/NextcloudKit+WebDAV.swift +++ b/Sources/NextcloudKit/NextcloudKit+WebDAV.swift @@ -205,7 +205,6 @@ 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" @@ -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) } } } } @@ -393,9 +394,8 @@ public extension NextcloudKit { 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) } } } } @@ -492,9 +494,8 @@ public extension NextcloudKit { 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) } } } } @@ -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..1c98a82c 100644 --- a/Sources/NextcloudKit/NextcloudKit.swift +++ b/Sources/NextcloudKit/NextcloudKit.swift @@ -167,13 +167,13 @@ open class NextcloudKit { reachabilityManager?.startListening(onUpdatePerforming: { status in switch status { case .unknown: - self.nkCommonInstance.delegate?.networkReachabilityObserver(NKCommon.TypeReachability.unknown) + self.nkCommonInstance.delegate?.networkReachabilityObserver(TypeReachability.unknown) case .notReachable: - self.nkCommonInstance.delegate?.networkReachabilityObserver(NKCommon.TypeReachability.notReachable) + self.nkCommonInstance.delegate?.networkReachabilityObserver(TypeReachability.notReachable) case .reachable(.ethernetOrWiFi): - self.nkCommonInstance.delegate?.networkReachabilityObserver(NKCommon.TypeReachability.reachableEthernetOrWiFi) + self.nkCommonInstance.delegate?.networkReachabilityObserver(TypeReachability.reachableEthernetOrWiFi) case .reachable(.cellular): - self.nkCommonInstance.delegate?.networkReachabilityObserver(NKCommon.TypeReachability.reachableCellular) + self.nkCommonInstance.delegate?.networkReachabilityObserver(TypeReachability.reachableCellular) } }) } diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift new file mode 100644 index 00000000..9af736bd --- /dev/null +++ b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift @@ -0,0 +1,133 @@ +import Foundation +import MobileCoreServices + +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" +} + +/// Class responsible for resolving NKFileProperty information from a given UTI. +public final class NKFilePropertyResolver { + + public init() {} + + public func resolve(inUTI: CFString, account: String) -> NKFileProperty { + let fileProperty = NKFileProperty() + let typeIdentifier = inUTI as String + let capabilities = NKCapabilities.shared.getCapabilitiesBlocking(for: account) + + // Preferred extension + if let fileExtension = UTTypeCopyPreferredTagWithClass(inUTI, kUTTagClassFilenameExtension) { + fileProperty.ext = fileExtension.takeRetainedValue() as String + } + + // Well-known UTI type classifications + 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 { + // Special-case identifiers + switch typeIdentifier { + case "text/plain", "text/html", "net.daringfireball.markdown", "text/x-markdown": + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "markdown" + + case "com.microsoft.word.doc": + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "document" + + case "com.apple.iwork.keynote.key": + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.name = "keynote" + + case "com.microsoft.excel.xls": + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.name = "sheet" + + case "com.apple.iwork.numbers.numbers": + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.name = "numbers" + + case "com.microsoft.powerpoint.ppt": + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.name = "presentation" + + default: + // Check against Collabora mimetypes + if capabilities.richDocumentsMimetypes.contains(typeIdentifier) { + fileProperty.classFile = TypeClassFile.document.rawValue + fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.name = "document" + } 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 + } +} diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift new file mode 100644 index 00000000..e6f1548a --- /dev/null +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -0,0 +1,92 @@ + +import Foundation +import MobileCoreServices + +/// Actor responsible for resolving file type metadata (UTI, MIME type, icon, class file, etc.) in a thread-safe manner. +public actor NKTypeIdentifiers { + + private var utiCache: [String: String] = [:] + private var mimeTypeCache: [String: String] = [:] + private var filePropertiesCache: [String: NKFileProperty] = [:] + private let resolver = NKFilePropertyResolver() + + public init() {} + + /// Resolves internal type metadata for a given file. + public func getInternalType(fileName: String, + mimeType inputMimeType: 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 = inputMimeType + var classFile = "" + var iconName = "" + var typeIdentifier = "" + var fileNameWithoutExt = "" + + var uti: CFString? + + // UTI cache + if let cachedUTI = utiCache[ext] { + uti = cachedUTI as CFString + } else if let unmanagedUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil) { + let resolvedUTI = unmanagedUTI.takeRetainedValue() + uti = resolvedUTI + utiCache[ext] = resolvedUTI as String + } + + if let uti { + typeIdentifier = uti as String + fileNameWithoutExt = (fileName as NSString).deletingPathExtension + + // MIME type detection + if mimeType.isEmpty { + if let cachedMime = mimeTypeCache[typeIdentifier] { + mimeType = cachedMime + } else if let mime = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType) { + let mimeStr = mime.takeRetainedValue() as String + mimeType = mimeStr + mimeTypeCache[typeIdentifier] = mimeStr + } + } + + // Directory override + if directory { + mimeType = "httpd/unix-directory" + classFile = TypeClassFile.directory.rawValue + iconName = TypeIconFile.directory.rawValue + typeIdentifier = kUTTypeFolder as String + fileNameWithoutExt = fileName + ext = "" + } else { + let fileProps: NKFileProperty + + if let cached = filePropertiesCache[typeIdentifier] { + fileProps = cached + } else { + fileProps = resolver.resolve(inUTI: uti, account: account) + filePropertiesCache[typeIdentifier] = fileProps + } + + classFile = fileProps.classFile + iconName = fileProps.iconName + } + } + + return ( + mimeType: mimeType, + classFile: classFile, + iconName: iconName, + typeIdentifier: typeIdentifier, + fileNameWithoutExt: fileNameWithoutExt, + ext: ext + ) + } +} From d82e7ac168c9b9efcef6836bafdbda9c3c40ab74 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 16:26:00 +0200 Subject: [PATCH 16/41] cod Signed-off-by: Marino Faggiana --- .../NextcloudKit/Models/NKFileProperty.swift | 22 ----- Sources/NextcloudKit/NKCommon.swift | 6 +- Sources/NextcloudKit/NKDataFileXML.swift | 4 +- Sources/NextcloudKit/NextcloudKit.swift | 8 +- .../NKFilePropertyResolver.swift | 85 +++++++++++-------- .../TypeIdentifiers/NKTypeIdentifiers.swift | 37 +++----- 6 files changed, 71 insertions(+), 91 deletions(-) delete mode 100644 Sources/NextcloudKit/Models/NKFileProperty.swift 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/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 7a668d6e..2c01b2a3 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -12,7 +12,7 @@ import MobileCoreServices import CoreServices #endif -public enum TypeReachability: Int { +public enum NKTypeReachability: Int { case unknown = 0 case notReachable = 1 case reachableEthernetOrWiFi = 2 @@ -23,7 +23,7 @@ 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: TypeReachability) + func networkReachabilityObserver(_ typeReachability: NKTypeReachability) func request(_ request: DataRequest, didParseResponse response: AFDataResponse) @@ -40,7 +40,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: TypeReachability) { } + func networkReachabilityObserver(_ typeReachability: NKTypeReachability) { } func request(_ request: DataRequest, didParseResponse response: AFDataResponse) { } diff --git a/Sources/NextcloudKit/NKDataFileXML.swift b/Sources/NextcloudKit/NKDataFileXML.swift index ecad5ad2..8123b844 100644 --- a/Sources/NextcloudKit/NKDataFileXML.swift +++ b/Sources/NextcloudKit/NKDataFileXML.swift @@ -557,8 +557,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 == TypeClassFile.image.rawValue, - files[index + 1].classFile == 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 } diff --git a/Sources/NextcloudKit/NextcloudKit.swift b/Sources/NextcloudKit/NextcloudKit.swift index 1c98a82c..5a728efa 100644 --- a/Sources/NextcloudKit/NextcloudKit.swift +++ b/Sources/NextcloudKit/NextcloudKit.swift @@ -167,13 +167,13 @@ open class NextcloudKit { reachabilityManager?.startListening(onUpdatePerforming: { status in switch status { case .unknown: - self.nkCommonInstance.delegate?.networkReachabilityObserver(TypeReachability.unknown) + self.nkCommonInstance.delegate?.networkReachabilityObserver(.unknown) case .notReachable: - self.nkCommonInstance.delegate?.networkReachabilityObserver(TypeReachability.notReachable) + self.nkCommonInstance.delegate?.networkReachabilityObserver(.notReachable) case .reachable(.ethernetOrWiFi): - self.nkCommonInstance.delegate?.networkReachabilityObserver(TypeReachability.reachableEthernetOrWiFi) + self.nkCommonInstance.delegate?.networkReachabilityObserver(.reachableEthernetOrWiFi) case .reachable(.cellular): - self.nkCommonInstance.delegate?.networkReachabilityObserver(TypeReachability.reachableCellular) + self.nkCommonInstance.delegate?.networkReachabilityObserver(.reachableCellular) } }) } diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift index 9af736bd..ec3a77bc 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift @@ -1,7 +1,18 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + import Foundation import MobileCoreServices -public enum TypeClassFile: String { +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" @@ -12,14 +23,14 @@ public enum TypeClassFile: String { case video = "video" } -public enum TypeIconFile: String { +public enum NKTypeIconFile: String { case audio = "audio" case code = "code" case compress = "compress" case directory = "directory" case document = "document" case image = "image" - case movie = "movie" + case video = "video" case pdf = "pdf" case ppt = "ppt" case txt = "txt" @@ -45,84 +56,84 @@ public final class NKFilePropertyResolver { // Well-known UTI type classifications if UTTypeConformsTo(inUTI, kUTTypeImage) { - fileProperty.classFile = TypeClassFile.image.rawValue - fileProperty.iconName = TypeIconFile.image.rawValue + fileProperty.classFile = .image + fileProperty.iconName = .image fileProperty.name = "image" } else if UTTypeConformsTo(inUTI, kUTTypeMovie) { - fileProperty.classFile = TypeClassFile.video.rawValue - fileProperty.iconName = TypeIconFile.movie.rawValue + fileProperty.classFile = .video + fileProperty.iconName = .video fileProperty.name = "movie" } else if UTTypeConformsTo(inUTI, kUTTypeAudio) { - fileProperty.classFile = TypeClassFile.audio.rawValue - fileProperty.iconName = TypeIconFile.audio.rawValue + fileProperty.classFile = .audio + fileProperty.iconName = .audio fileProperty.name = "audio" } else if UTTypeConformsTo(inUTI, kUTTypeZipArchive) { - fileProperty.classFile = TypeClassFile.compress.rawValue - fileProperty.iconName = TypeIconFile.compress.rawValue + fileProperty.classFile = .compress + fileProperty.iconName = .compress fileProperty.name = "archive" } else if UTTypeConformsTo(inUTI, kUTTypeHTML) { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.code.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .code fileProperty.name = "code" } else if UTTypeConformsTo(inUTI, kUTTypePDF) { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.pdf.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .pdf fileProperty.name = "document" } else if UTTypeConformsTo(inUTI, kUTTypeRTF) { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.txt.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .txt 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.classFile = .document + fileProperty.iconName = .txt fileProperty.name = "text" } else { // Special-case identifiers switch typeIdentifier { case "text/plain", "text/html", "net.daringfireball.markdown", "text/x-markdown": - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .document fileProperty.name = "markdown" case "com.microsoft.word.doc": - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .document fileProperty.name = "document" case "com.apple.iwork.keynote.key": - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .ppt fileProperty.name = "keynote" case "com.microsoft.excel.xls": - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .xls fileProperty.name = "sheet" case "com.apple.iwork.numbers.numbers": - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.xls.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .xls fileProperty.name = "numbers" case "com.microsoft.powerpoint.ppt": - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.ppt.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .ppt fileProperty.name = "presentation" default: // Check against Collabora mimetypes if capabilities.richDocumentsMimetypes.contains(typeIdentifier) { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .document fileProperty.name = "document" } else if UTTypeConformsTo(inUTI, kUTTypeContent) { - fileProperty.classFile = TypeClassFile.document.rawValue - fileProperty.iconName = TypeIconFile.document.rawValue + fileProperty.classFile = .document + fileProperty.iconName = .document fileProperty.name = "document" } else { - fileProperty.classFile = TypeClassFile.unknow.rawValue - fileProperty.iconName = TypeIconFile.unknow.rawValue + fileProperty.classFile = .unknow + fileProperty.iconName = .unknow fileProperty.name = "file" } } diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index e6f1548a..81a8951a 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import Foundation import MobileCoreServices @@ -13,17 +16,12 @@ public actor NKTypeIdentifiers { public init() {} /// Resolves internal type metadata for a given file. - public func getInternalType(fileName: String, - mimeType inputMimeType: String, - directory: Bool, - account: String) -> ( - mimeType: String, - classFile: String, - iconName: String, - typeIdentifier: String, - fileNameWithoutExt: String, - ext: String - ) { + public func getInternalType(fileName: String, mimeType inputMimeType: 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 = inputMimeType var classFile = "" @@ -60,8 +58,8 @@ public actor NKTypeIdentifiers { // Directory override if directory { mimeType = "httpd/unix-directory" - classFile = TypeClassFile.directory.rawValue - iconName = TypeIconFile.directory.rawValue + classFile = NKTypeClassFile.directory.rawValue + iconName = NKTypeIconFile.directory.rawValue typeIdentifier = kUTTypeFolder as String fileNameWithoutExt = fileName ext = "" @@ -75,18 +73,11 @@ public actor NKTypeIdentifiers { filePropertiesCache[typeIdentifier] = fileProps } - classFile = fileProps.classFile - iconName = fileProps.iconName + classFile = fileProps.classFile.rawValue + iconName = fileProps.iconName.rawValue } } - return ( - mimeType: mimeType, - classFile: classFile, - iconName: iconName, - typeIdentifier: typeIdentifier, - fileNameWithoutExt: fileNameWithoutExt, - ext: ext - ) + return (mimeType: mimeType, classFile: classFile, iconName: iconName, typeIdentifier: typeIdentifier, fileNameWithoutExt: fileNameWithoutExt, ext: ext) } } From b4c1c129b35f269c91972235c4d8746381034c1e Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Thu, 19 Jun 2025 17:57:52 +0200 Subject: [PATCH 17/41] NKTypeIdentifiersHelper Signed-off-by: Marino Faggiana --- .../TypeIdentifiers/NKTypeIdentifiers.swift | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index 81a8951a..66cc0499 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -81,3 +81,42 @@ public actor NKTypeIdentifiers { return (mimeType: mimeType, classFile: classFile, iconName: iconName, typeIdentifier: typeIdentifier, fileNameWithoutExt: fileNameWithoutExt, ext: ext) } } + +public final class NKTypeIdentifiersHelper { + private let actor: NKTypeIdentifiers + + public init(actor: NKTypeIdentifiers) { + self.actor = actor + } + + public func getInternalTypeSync(fileName: String, mimeType: String, directory: Bool, account: String) -> (mimeType: String, + classFile: String, + iconName: String, + typeIdentifier: String, + fileNameWithoutExt: String, + ext: String) { + var result: ( + mimeType: String, + classFile: String, + iconName: String, + typeIdentifier: String, + fileNameWithoutExt: String, + ext: String + )! + + let semaphore = DispatchSemaphore(value: 0) + + Task { + result = await actor.getInternalType( + fileName: fileName, + mimeType: mimeType, + directory: directory, + account: account + ) + semaphore.signal() + } + + semaphore.wait() + return result + } +} From 9c233112e7a69548a83e678e2af61ad93e94c2cd Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 11:12:01 +0200 Subject: [PATCH 18/41] improvements Signed-off-by: Marino Faggiana --- .../NKFilePropertyResolver.swift | 93 ++++++++++--------- .../TypeIdentifiers/NKTypeIdentifiers.swift | 14 ++- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift index ec3a77bc..1ec56443 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift @@ -54,6 +54,50 @@ public final class NKFilePropertyResolver { fileProperty.ext = fileExtension.takeRetainedValue() as String } + // 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 UTTypeConformsTo(inUTI, kUTTypeImage) { fileProperty.classFile = .image @@ -89,53 +133,14 @@ public final class NKFilePropertyResolver { fileProperty.iconName = .txt fileProperty.name = "text" } else { - // 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" - - case "com.microsoft.word.doc": + if UTTypeConformsTo(inUTI, kUTTypeContent) { fileProperty.classFile = .document fileProperty.iconName = .document fileProperty.name = "document" - - case "com.apple.iwork.keynote.key": - fileProperty.classFile = .document - fileProperty.iconName = .ppt - fileProperty.name = "keynote" - - case "com.microsoft.excel.xls": - fileProperty.classFile = .document - fileProperty.iconName = .xls - fileProperty.name = "sheet" - - case "com.apple.iwork.numbers.numbers": - fileProperty.classFile = .document - fileProperty.iconName = .xls - fileProperty.name = "numbers" - - case "com.microsoft.powerpoint.ppt": - fileProperty.classFile = .document - fileProperty.iconName = .ppt - fileProperty.name = "presentation" - - default: - // Check against Collabora mimetypes - if capabilities.richDocumentsMimetypes.contains(typeIdentifier) { - fileProperty.classFile = .document - fileProperty.iconName = .document - fileProperty.name = "document" - } else if UTTypeConformsTo(inUTI, kUTTypeContent) { - fileProperty.classFile = .document - fileProperty.iconName = .document - fileProperty.name = "document" - } else { - fileProperty.classFile = .unknow - fileProperty.iconName = .unknow - fileProperty.name = "file" - } + } else { + fileProperty.classFile = .unknow + fileProperty.iconName = .unknow + fileProperty.name = "file" } } diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index 66cc0499..ec03c057 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -95,14 +95,12 @@ public final class NKTypeIdentifiersHelper { typeIdentifier: String, fileNameWithoutExt: String, ext: String) { - var result: ( - mimeType: String, - classFile: String, - iconName: String, - typeIdentifier: String, - fileNameWithoutExt: String, - ext: String - )! + var result: (mimeType: String, + classFile: String, + iconName: String, + typeIdentifier: String, + fileNameWithoutExt: String, + ext: String)! let semaphore = DispatchSemaphore(value: 0) From b318a327146e136ceee9d2448aceab7cfa451e2f Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 12:07:04 +0200 Subject: [PATCH 19/41] license Signed-off-by: Marino Faggiana --- .../Models/EditorDetails/NKEditorDetailsConverter.swift | 4 ++++ .../NKEditorDetailsResponse+NKConversion.swift | 6 ++++-- .../Models/EditorDetails/NKEditorDetailsResponse.swift | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift index 9b4671a2..18bf6787 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + import Foundation public enum NKEditorDetailsConverter { diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse+NKConversion.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse+NKConversion.swift index 1f19b311..7d4a1953 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse+NKConversion.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse+NKConversion.swift @@ -1,8 +1,10 @@ -import Foundation +// 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) } diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift index ef5d28cb..72055382 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsResponse.swift @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import Foundation From 1940d0d85a8aad2d68bbf3ea0e0fc066070c0450 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 12:16:11 +0200 Subject: [PATCH 20/41] cleaning Signed-off-by: Marino Faggiana --- .../Models/EditorDetails/NKEditorDetailsConverter.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift index 18bf6787..51d2aec6 100644 --- a/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift +++ b/Sources/NextcloudKit/Models/EditorDetails/NKEditorDetailsConverter.swift @@ -11,12 +11,14 @@ public enum NKEditorDetailsConverter { /// - Returns: A tuple with editors and creators. /// - Throws: Decoding error if parsing fails. public static func from(data: Data) throws -> (editors: [NKEditorDetailsEditor], creators: [NKEditorDetailsCreator]) { - data.printJson() - 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) } } From 88612392d116d6a53d805fbd3ad1c650c588a501 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 17:44:52 +0200 Subject: [PATCH 21/41] SynchronizedNKSessionArray Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/NKCommon.swift | 16 +- Sources/NextcloudKit/NKMonitor.swift | 2 +- Sources/NextcloudKit/NextcloudKit+API.swift | 28 +- .../NextcloudKit/NextcloudKit+Assistant.swift | 10 +- .../NextcloudKit+AssistantV2.swift | 8 +- .../NextcloudKit+Capabilities.swift | 2 +- .../NextcloudKit/NextcloudKit+Comments.swift | 10 +- .../NextcloudKit/NextcloudKit+Dashboard.swift | 4 +- .../NextcloudKit/NextcloudKit+Download.swift | 2 +- Sources/NextcloudKit/NextcloudKit+E2EE.swift | 22 +- .../NextcloudKit/NextcloudKit+FilesLock.swift | 2 +- .../NextcloudKit+Groupfolders.swift | 2 +- .../NextcloudKit/NextcloudKit+Hovercard.swift | 2 +- .../NextcloudKit/NextcloudKit+Livephoto.swift | 2 +- Sources/NextcloudKit/NextcloudKit+Login.swift | 2 +- .../NextcloudKit/NextcloudKit+NCText.swift | 8 +- .../NextcloudKit+PushNotification.swift | 8 +- .../NextcloudKit+RecommendedFiles.swift | 2 +- .../NextcloudKit+Richdocuments.swift | 8 +- .../NextcloudKit/NextcloudKit+Search.swift | 4 +- Sources/NextcloudKit/NextcloudKit+Share.swift | 10 +- .../NextcloudKit+ShareDownloadLimit.swift | 6 +- .../NextcloudKit+TermsOfService.swift | 4 +- .../NextcloudKit/NextcloudKit+Upload.swift | 4 +- .../NextcloudKit+UserStatus.swift | 14 +- .../NextcloudKit/NextcloudKit+WebDAV.swift | 24 +- Sources/NextcloudKit/NextcloudKit.swift | 23 +- .../NextcloudKit/NextcloudKitBackground.swift | 4 +- .../Utils/SynchronizedNKSessionArray.swift | 106 +++++++ .../NextcloudKit/Utils/ThreadSafeArray.swift | 276 ------------------ 30 files changed, 217 insertions(+), 398 deletions(-) create mode 100644 Sources/NextcloudKit/Utils/SynchronizedNKSessionArray.swift delete mode 100644 Sources/NextcloudKit/Utils/ThreadSafeArray.swift diff --git a/Sources/NextcloudKit/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 2c01b2a3..5ee6ed4d 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -54,7 +54,7 @@ public extension NextcloudKitDelegate { } public struct NKCommon: Sendable { - public var nksessions = ThreadSafeArray() + public var nksessions = SynchronizedNKSessionArray() public var delegate: NextcloudKitDelegate? public var groupIdentifier: String? @@ -236,18 +236,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/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 df5c393f..8c116459 100644 --- a/Sources/NextcloudKit/NextcloudKit+Capabilities.swift +++ b/Sources/NextcloudKit/NextcloudKit+Capabilities.swift @@ -27,7 +27,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, 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) } 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..190e7732 100644 --- a/Sources/NextcloudKit/NextcloudKit+Download.swift +++ b/Sources/NextcloudKit/NextcloudKit+Download.swift @@ -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) } } 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 a5b5697b..2f8426fd 100644 --- a/Sources/NextcloudKit/NextcloudKit+NCText.swift +++ b/Sources/NextcloudKit/NextcloudKit+NCText.swift @@ -12,7 +12,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ editors: [NKEditorDetailsEditor]?, _ creators: [NKEditorDetailsCreator]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/files/api/v1/directEditing" - 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) } @@ -76,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) } @@ -103,7 +103,7 @@ public extension NextcloudKit { taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, completion: @escaping (_ account: String, _ templates: [NKEditorTemplate]?, _ responseData: AFDataResponse?, _ error: NKError) -> Void) { let endpoint = "ocs/v2.php/apps/files/api/v1/directEditing/templates/text/textdocumenttemplate" - 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) } @@ -163,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..b266baf6 100644 --- a/Sources/NextcloudKit/NextcloudKit+Upload.swift +++ b/Sources/NextcloudKit/NextcloudKit+Upload.swift @@ -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) } } @@ -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 50457824..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) } } @@ -209,7 +209,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 = serverUrlFileName.encodedToUrl, var headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else { return options.queue.async { completion(account, nil, nil, .urlError) } @@ -275,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? @@ -325,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) } } @@ -348,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] = [] @@ -390,7 +390,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) } } @@ -437,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) } } @@ -489,7 +489,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) } } @@ -549,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) } } diff --git a/Sources/NextcloudKit/NextcloudKit.swift b/Sources/NextcloudKit/NextcloudKit.swift index 5a728efa..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 ?? [] { 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/Utils/SynchronizedNKSessionArray.swift b/Sources/NextcloudKit/Utils/SynchronizedNKSessionArray.swift new file mode 100644 index 00000000..fe140250 --- /dev/null +++ b/Sources/NextcloudKit/Utils/SynchronizedNKSessionArray.swift @@ -0,0 +1,106 @@ +// 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 } + } + + /// 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) - } -} From 8f8ba703b4cd4fa834b1e7c52d7e86e6c69d48c9 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 17:54:06 +0200 Subject: [PATCH 22/41] added foreach Signed-off-by: Marino Faggiana --- .../NextcloudKit/Utils/SynchronizedNKSessionArray.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/NextcloudKit/Utils/SynchronizedNKSessionArray.swift b/Sources/NextcloudKit/Utils/SynchronizedNKSessionArray.swift index fe140250..a2d84055 100644 --- a/Sources/NextcloudKit/Utils/SynchronizedNKSessionArray.swift +++ b/Sources/NextcloudKit/Utils/SynchronizedNKSessionArray.swift @@ -44,6 +44,14 @@ public final class SynchronizedNKSessionArray: @unchecked Sendable { 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`. From 2972bc70179e020c6ff0d8f898f48318ff95d35c Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 18:44:33 +0200 Subject: [PATCH 23/41] UniformTypeIdentifiers Signed-off-by: Marino Faggiana --- Package.swift | 32 ++++++++++++++++--- .../NextcloudKit/Log/NKLogFileManager.swift | 8 ++--- Sources/NextcloudKit/NKCommon.swift | 6 ---- .../TypeIdentifiers/NKTypeIdentifiers.swift | 23 ++++++------- 4 files changed, 42 insertions(+), 27 deletions(-) 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/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index 5ee6ed4d..d25388e9 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -6,12 +6,6 @@ import Foundation import Alamofire -#if os(iOS) -import MobileCoreServices -#else -import CoreServices -#endif - public enum NKTypeReachability: Int { case unknown = 0 case notReachable = 1 diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index ec03c057..d11dbdf2 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later import Foundation -import MobileCoreServices +import UniformTypeIdentifiers /// Actor responsible for resolving file type metadata (UTI, MIME type, icon, class file, etc.) in a thread-safe manner. public actor NKTypeIdentifiers { @@ -28,16 +28,13 @@ public actor NKTypeIdentifiers { var iconName = "" var typeIdentifier = "" var fileNameWithoutExt = "" - - var uti: CFString? + var uti: String? // UTI cache if let cachedUTI = utiCache[ext] { - uti = cachedUTI as CFString - } else if let unmanagedUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil) { - let resolvedUTI = unmanagedUTI.takeRetainedValue() - uti = resolvedUTI - utiCache[ext] = resolvedUTI as String + uti = cachedUTI + } else if let type = UTType(filenameExtension: ext) { + utiCache[ext] = type.identifier } if let uti { @@ -48,10 +45,10 @@ public actor NKTypeIdentifiers { if mimeType.isEmpty { if let cachedMime = mimeTypeCache[typeIdentifier] { mimeType = cachedMime - } else if let mime = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType) { - let mimeStr = mime.takeRetainedValue() as String - mimeType = mimeStr - mimeTypeCache[typeIdentifier] = mimeStr + } else if let type = UTType(typeIdentifier), + let resolvedMime = type.preferredMIMEType { + mimeType = resolvedMime + mimeTypeCache[typeIdentifier] = resolvedMime } } @@ -60,7 +57,7 @@ public actor NKTypeIdentifiers { mimeType = "httpd/unix-directory" classFile = NKTypeClassFile.directory.rawValue iconName = NKTypeIconFile.directory.rawValue - typeIdentifier = kUTTypeFolder as String + typeIdentifier = UTType.folder.identifier fileNameWithoutExt = fileName ext = "" } else { From 5edeeb1336a9a112534b884b2f53a8c7768dd769 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 18:44:41 +0200 Subject: [PATCH 24/41] UniformTypeIdentifiers Signed-off-by: Marino Faggiana --- .../NKFilePropertyResolver.swift | 112 ++++++++++++------ 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift index 1ec56443..0973e7da 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift @@ -3,7 +3,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later import Foundation -import MobileCoreServices +import UniformTypeIdentifiers public class NKFileProperty: NSObject { public var classFile: NKTypeClassFile = .unknow @@ -39,19 +39,36 @@ public enum NKTypeIconFile: String { case xls = "xls" } +public enum NKTypeIdentifierResolver { + + public static func preferredFileExtension(for uti: String) -> String? { + UTType(uti)?.preferredFilenameExtension + } + + public static func typeIdentifier(forExtension ext: String) -> String? { + UTType(filenameExtension: ext)?.identifier + } + + public static func typeIdentifier(forMIME mime: String) -> String? { + UTType(mimeType: mime)?.identifier + } +} + /// Class responsible for resolving NKFileProperty information from a given UTI. public final class NKFilePropertyResolver { public init() {} - public func resolve(inUTI: CFString, account: String) -> NKFileProperty { + 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 fileExtension = UTTypeCopyPreferredTagWithClass(inUTI, kUTTagClassFilenameExtension) { - fileProperty.ext = fileExtension.takeRetainedValue() as String + if let type = UTType(utiString), + let ext = type.preferredFilenameExtension { + fileProperty.ext = ext } // Collabora Nextcloud Text Office @@ -99,49 +116,66 @@ public final class NKFilePropertyResolver { } // Well-known UTI type classifications - if UTTypeConformsTo(inUTI, kUTTypeImage) { - fileProperty.classFile = .image - fileProperty.iconName = .image - fileProperty.name = "image" - } else if UTTypeConformsTo(inUTI, kUTTypeMovie) { - fileProperty.classFile = .video - fileProperty.iconName = .video - fileProperty.name = "movie" - } else if UTTypeConformsTo(inUTI, kUTTypeAudio) { - fileProperty.classFile = .audio - fileProperty.iconName = .audio - fileProperty.name = "audio" - } else if UTTypeConformsTo(inUTI, kUTTypeZipArchive) { - fileProperty.classFile = .compress - fileProperty.iconName = .compress - fileProperty.name = "archive" - } else if UTTypeConformsTo(inUTI, kUTTypeHTML) { - fileProperty.classFile = .document - fileProperty.iconName = .code - fileProperty.name = "code" - } else if UTTypeConformsTo(inUTI, kUTTypePDF) { - fileProperty.classFile = .document - fileProperty.iconName = .pdf - fileProperty.name = "document" - } else if UTTypeConformsTo(inUTI, kUTTypeRTF) { - fileProperty.classFile = .document - fileProperty.iconName = .txt - fileProperty.name = "document" - } else if UTTypeConformsTo(inUTI, kUTTypeText) { - if fileProperty.ext.isEmpty { fileProperty.ext = "txt" } - fileProperty.classFile = .document - fileProperty.iconName = .txt - fileProperty.name = "text" - } else { - if UTTypeConformsTo(inUTI, kUTTypeContent) { + 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 From ec8e4365b4015d73b36d90d51cf52410d38fa38f Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 18:44:53 +0200 Subject: [PATCH 25/41] cod Signed-off-by: Marino Faggiana --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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 From 3037d444e294b45af37c4e588801e00f4948077e Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 19:11:16 +0200 Subject: [PATCH 26/41] fix Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index d11dbdf2..d8d6454f 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -35,6 +35,7 @@ public actor NKTypeIdentifiers { uti = cachedUTI } else if let type = UTType(filenameExtension: ext) { utiCache[ext] = type.identifier + uti = type.identifier } if let uti { From b3f8f0190851e4db393beba4e096c3c02a48724e Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 20:29:53 +0200 Subject: [PATCH 27/41] code improved Signed-off-by: Marino Faggiana --- .../TypeIdentifiers/NKTypeIdentifiers.swift | 138 ++++++++++-------- 1 file changed, 74 insertions(+), 64 deletions(-) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index d8d6454f..f4cd87a3 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -5,10 +5,18 @@ import Foundation import UniformTypeIdentifiers +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.) in a thread-safe manner. public actor NKTypeIdentifiers { - - private var utiCache: [String: String] = [:] + private var filePropertyCache: [String: NKTypeIdentifierCache] = [:] private var mimeTypeCache: [String: String] = [:] private var filePropertiesCache: [String: NKFileProperty] = [:] private let resolver = NKFilePropertyResolver() @@ -16,67 +24,80 @@ public actor NKTypeIdentifiers { public init() {} /// Resolves internal type metadata for a given file. - public func getInternalType(fileName: String, mimeType inputMimeType: String, directory: Bool, account: String) -> (mimeType: String, - classFile: String, - iconName: String, - typeIdentifier: String, - fileNameWithoutExt: String, - ext: String) { + public func getInternalType(fileName: String, mimeType inputMimeType: String, directory: Bool, account: String) -> NKTypeIdentifierCache { + // Extract file extension var ext = (fileName as NSString).pathExtension.lowercased() var mimeType = inputMimeType var classFile = "" var iconName = "" var typeIdentifier = "" - var fileNameWithoutExt = "" - var uti: String? - - // UTI cache - if let cachedUTI = utiCache[ext] { - uti = cachedUTI - } else if let type = UTType(filenameExtension: ext) { - utiCache[ext] = type.identifier - uti = type.identifier + var fileNameWithoutExt = (fileName as NSString).deletingPathExtension + + // Try cached result + if let cached = filePropertyCache[ext] { + return cached } - if let uti { - typeIdentifier = uti as String - fileNameWithoutExt = (fileName as NSString).deletingPathExtension - - // MIME type detection - if mimeType.isEmpty { - if let cachedMime = mimeTypeCache[typeIdentifier] { - mimeType = cachedMime - } else if let type = UTType(typeIdentifier), - let resolvedMime = type.preferredMIMEType { - mimeType = resolvedMime - mimeTypeCache[typeIdentifier] = resolvedMime - } - } + // Resolve UTI from extension + guard let type = UTType(filenameExtension: ext) else { + return NKTypeIdentifierCache( + mimeType: mimeType, + classFile: classFile, + iconName: iconName, + typeIdentifier: typeIdentifier, + fileNameWithoutExt: fileNameWithoutExt, + ext: ext + ) + } - // Directory override - if directory { - mimeType = "httpd/unix-directory" - classFile = NKTypeClassFile.directory.rawValue - iconName = NKTypeIconFile.directory.rawValue - typeIdentifier = UTType.folder.identifier - fileNameWithoutExt = fileName - ext = "" - } else { - let fileProps: NKFileProperty + let uti = type.identifier + typeIdentifier = uti - if let cached = filePropertiesCache[typeIdentifier] { - fileProps = cached - } else { - fileProps = resolver.resolve(inUTI: uti, account: account) - filePropertiesCache[typeIdentifier] = fileProps - } + // Detect MIME type from UTI + if mimeType.isEmpty { + if let cachedMime = mimeTypeCache[typeIdentifier] { + mimeType = cachedMime + } else if let mime = UTType(typeIdentifier)?.preferredMIMEType { + mimeType = mime + mimeTypeCache[typeIdentifier] = mime + } + } - classFile = fileProps.classFile.rawValue - iconName = fileProps.iconName.rawValue + // Special case for folders + if directory { + mimeType = "httpd/unix-directory" + classFile = NKTypeClassFile.directory.rawValue + iconName = NKTypeIconFile.directory.rawValue + typeIdentifier = UTType.folder.identifier + fileNameWithoutExt = fileName + ext = "" + } else { + // Lookup file classification and icon + let fileProps: NKFileProperty + if let cachedProps = filePropertiesCache[typeIdentifier] { + fileProps = cachedProps + } else { + fileProps = resolver.resolve(inUTI: typeIdentifier, account: account) + filePropertiesCache[typeIdentifier] = fileProps } + + classFile = fileProps.classFile.rawValue + iconName = fileProps.iconName.rawValue } - return (mimeType: mimeType, classFile: classFile, iconName: iconName, typeIdentifier: typeIdentifier, fileNameWithoutExt: fileNameWithoutExt, ext: ext) + // Step 7: Assemble cache object + let result = NKTypeIdentifierCache( + mimeType: mimeType, + classFile: classFile, + iconName: iconName, + typeIdentifier: typeIdentifier, + fileNameWithoutExt: fileNameWithoutExt, + ext: ext + ) + + // Step 8: Cache result for reuse + filePropertyCache[ext] = result + return result } } @@ -87,19 +108,8 @@ public final class NKTypeIdentifiersHelper { self.actor = actor } - public func getInternalTypeSync(fileName: String, mimeType: String, directory: Bool, account: String) -> (mimeType: String, - classFile: String, - iconName: String, - typeIdentifier: String, - fileNameWithoutExt: String, - ext: String) { - var result: (mimeType: String, - classFile: String, - iconName: String, - typeIdentifier: String, - fileNameWithoutExt: String, - ext: String)! - + public func getInternalTypeSync(fileName: String, mimeType: String, directory: Bool, account: String) -> NKTypeIdentifierCache { + var result: NKTypeIdentifierCache? let semaphore = DispatchSemaphore(value: 0) Task { @@ -113,6 +123,6 @@ public final class NKTypeIdentifiersHelper { } semaphore.wait() - return result + return result! } } From 2c2b3624b92bfbbe072f0a484408aa40dd7e3ef5 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 20:36:31 +0200 Subject: [PATCH 28/41] improved Signed-off-by: Marino Faggiana --- .../TypeIdentifiers/NKTypeIdentifiers.swift | 55 ++++++------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index f4cd87a3..2ca5f468 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -15,17 +15,17 @@ public struct NKTypeIdentifierCache: Sendable { } /// Actor responsible for resolving file type metadata (UTI, MIME type, icon, class file, etc.) in a thread-safe manner. +/// Actor responsible for resolving file type metadata (UTI, MIME type, icon, etc.) in a thread-safe manner. public actor NKTypeIdentifiers { + /// Cache by file extension private var filePropertyCache: [String: NKTypeIdentifierCache] = [:] - private var mimeTypeCache: [String: String] = [:] - private var filePropertiesCache: [String: NKFileProperty] = [:] + /// Internal file type resolver private let resolver = NKFilePropertyResolver() public init() {} /// Resolves internal type metadata for a given file. public func getInternalType(fileName: String, mimeType inputMimeType: String, directory: Bool, account: String) -> NKTypeIdentifierCache { - // Extract file extension var ext = (fileName as NSString).pathExtension.lowercased() var mimeType = inputMimeType var classFile = "" @@ -33,59 +33,39 @@ public actor NKTypeIdentifiers { var typeIdentifier = "" var fileNameWithoutExt = (fileName as NSString).deletingPathExtension - // Try cached result + // Check cache if let cached = filePropertyCache[ext] { return cached } - // Resolve UTI from extension - guard let type = UTType(filenameExtension: ext) else { - return NKTypeIdentifierCache( - mimeType: mimeType, - classFile: classFile, - iconName: iconName, - typeIdentifier: typeIdentifier, - fileNameWithoutExt: fileNameWithoutExt, - ext: ext - ) + // Fallback if no extension (e.g. ".bashrc" or folder) + if ext.isEmpty { + fileNameWithoutExt = fileName } - let uti = type.identifier - typeIdentifier = uti + // Resolve UTType from extension or fallback to .data + let type = UTType(filenameExtension: ext) ?? .data + typeIdentifier = type.identifier - // Detect MIME type from UTI + // Resolve MIME type if not provided if mimeType.isEmpty { - if let cachedMime = mimeTypeCache[typeIdentifier] { - mimeType = cachedMime - } else if let mime = UTType(typeIdentifier)?.preferredMIMEType { - mimeType = mime - mimeTypeCache[typeIdentifier] = mime - } + mimeType = type.preferredMIMEType ?? "application/octet-stream" } - // Special case for folders + // Special case: folders if directory { mimeType = "httpd/unix-directory" classFile = NKTypeClassFile.directory.rawValue iconName = NKTypeIconFile.directory.rawValue typeIdentifier = UTType.folder.identifier - fileNameWithoutExt = fileName ext = "" + fileNameWithoutExt = fileName } else { - // Lookup file classification and icon - let fileProps: NKFileProperty - if let cachedProps = filePropertiesCache[typeIdentifier] { - fileProps = cachedProps - } else { - fileProps = resolver.resolve(inUTI: typeIdentifier, account: account) - filePropertiesCache[typeIdentifier] = fileProps - } - - classFile = fileProps.classFile.rawValue - iconName = fileProps.iconName.rawValue + let props = resolver.resolve(inUTI: typeIdentifier, account: account) + classFile = props.classFile.rawValue + iconName = props.iconName.rawValue } - // Step 7: Assemble cache object let result = NKTypeIdentifierCache( mimeType: mimeType, classFile: classFile, @@ -95,7 +75,6 @@ public actor NKTypeIdentifiers { ext: ext ) - // Step 8: Cache result for reuse filePropertyCache[ext] = result return result } From 249a2a24c6ae316343f4092a413959784a731536 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 20:37:17 +0200 Subject: [PATCH 29/41] cleaning Signed-off-by: Marino Faggiana --- .../NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index 2ca5f468..01b27914 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -18,7 +18,7 @@ public struct NKTypeIdentifierCache: Sendable { /// Actor responsible for resolving file type metadata (UTI, MIME type, icon, etc.) in a thread-safe manner. public actor NKTypeIdentifiers { /// Cache by file extension - private var filePropertyCache: [String: NKTypeIdentifierCache] = [:] + private var typeIdentifierCache: [String: NKTypeIdentifierCache] = [:] /// Internal file type resolver private let resolver = NKFilePropertyResolver() @@ -34,7 +34,7 @@ public actor NKTypeIdentifiers { var fileNameWithoutExt = (fileName as NSString).deletingPathExtension // Check cache - if let cached = filePropertyCache[ext] { + if let cached = typeIdentifierCache[ext] { return cached } @@ -75,7 +75,7 @@ public actor NKTypeIdentifiers { ext: ext ) - filePropertyCache[ext] = result + typeIdentifierCache[ext] = result return result } } From d21f06e1057d163bb1039f001c29d128b77e10b7 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 20:53:04 +0200 Subject: [PATCH 30/41] fix Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/NKCommon.swift | 3 +- .../TypeIdentifiers/NKTypeIdentifiers.swift | 55 +++++++++++-------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/Sources/NextcloudKit/NKCommon.swift b/Sources/NextcloudKit/NKCommon.swift index d25388e9..3a062209 100644 --- a/Sources/NextcloudKit/NKCommon.swift +++ b/Sources/NextcloudKit/NKCommon.swift @@ -51,8 +51,7 @@ public struct NKCommon: Sendable { public var nksessions = SynchronizedNKSessionArray() public var delegate: NextcloudKitDelegate? public var groupIdentifier: String? - - public let typeIdentifiers = NKTypeIdentifiers() + public let typeIdentifiers: NKTypeIdentifiers = .shared // Foreground public let identifierSessionDownload: String = "com.nextcloud.nextcloudkit.session.download" diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index 01b27914..e7d4ecfc 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -1,10 +1,7 @@ -// 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 @@ -14,18 +11,19 @@ public struct NKTypeIdentifierCache: Sendable { public let ext: String } -/// Actor responsible for resolving file type metadata (UTI, MIME type, icon, class file, etc.) in a thread-safe manner. -/// Actor responsible for resolving file type metadata (UTI, MIME type, icon, etc.) in a thread-safe manner. +/// Actor responsible for resolving file type metadata (UTI, MIME type, icon, class file, etc.) public actor NKTypeIdentifiers { - /// Cache by file extension - private var typeIdentifierCache: [String: NKTypeIdentifierCache] = [:] - /// Internal file type resolver + public static let shared = NKTypeIdentifiers() + // Cache: extension → resolved type info + private var filePropertyCache: [String: NKTypeIdentifierCache] = [:] + // Internal resolver private let resolver = NKFilePropertyResolver() public init() {} - /// Resolves internal type metadata for a given file. + /// 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 = "" @@ -33,39 +31,40 @@ public actor NKTypeIdentifiers { var typeIdentifier = "" var fileNameWithoutExt = (fileName as NSString).deletingPathExtension - // Check cache - if let cached = typeIdentifierCache[ext] { - return cached - } - - // Fallback if no extension (e.g. ".bashrc" or folder) + // Use full name if no extension if ext.isEmpty { fileNameWithoutExt = fileName } - // Resolve UTType from extension or fallback to .data + // 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 not provided + // Resolve MIME type if mimeType.isEmpty { mimeType = type.preferredMIMEType ?? "application/octet-stream" } - // Special case: folders + // Handle folder case if directory { mimeType = "httpd/unix-directory" classFile = NKTypeClassFile.directory.rawValue iconName = NKTypeIconFile.directory.rawValue typeIdentifier = UTType.folder.identifier - ext = "" 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, @@ -75,18 +74,30 @@ public actor NKTypeIdentifiers { ext: ext ) - typeIdentifierCache[ext] = result + // Cache it + 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 - public init(actor: NKTypeIdentifiers) { + /// 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) From 4bfc6d72da9d34ec53a4097e8023eae9af06964b Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 21:06:32 +0200 Subject: [PATCH 31/41] fix Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index e7d4ecfc..4a56483a 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -75,7 +75,10 @@ public actor NKTypeIdentifiers { ) // Cache it - filePropertyCache[ext] = result + if !ext.isEmpty { + filePropertyCache[ext] = result + } + return result } From e04c2925d490a929bf9dd5ff7c8a9767c6649d54 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 21:10:46 +0200 Subject: [PATCH 32/41] cleaning Signed-off-by: Marino Faggiana --- .../NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index 4a56483a..37eb8fc7 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -19,7 +19,7 @@ public actor NKTypeIdentifiers { // Internal resolver private let resolver = NKFilePropertyResolver() - public init() {} + 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 { @@ -95,6 +95,10 @@ public final class 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 From 465cf9533e784d3576d0843c919b3117eaeeb454 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 21:11:40 +0200 Subject: [PATCH 33/41] remove old code Signed-off-by: Marino Faggiana --- .../TypeIdentifiers/NKFilePropertyResolver.swift | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift index 0973e7da..d4304476 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKFilePropertyResolver.swift @@ -39,21 +39,6 @@ public enum NKTypeIconFile: String { case xls = "xls" } -public enum NKTypeIdentifierResolver { - - public static func preferredFileExtension(for uti: String) -> String? { - UTType(uti)?.preferredFilenameExtension - } - - public static func typeIdentifier(forExtension ext: String) -> String? { - UTType(filenameExtension: ext)?.identifier - } - - public static func typeIdentifier(forMIME mime: String) -> String? { - UTType(mimeType: mime)?.identifier - } -} - /// Class responsible for resolving NKFileProperty information from a given UTI. public final class NKFilePropertyResolver { From 2c92998a596f52314059807206267126235f5c37 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Jun 2025 21:13:37 +0200 Subject: [PATCH 34/41] cleaning Signed-off-by: Marino Faggiana --- .../TypeIdentifiers/NKTypeIdentifiers.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index 37eb8fc7..3a79d647 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -21,7 +21,7 @@ public actor NKTypeIdentifiers { private init() {} - /// Resolves type info from file name and optional MIME type + // 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() @@ -82,7 +82,7 @@ public actor NKTypeIdentifiers { return result } - /// Clears the internal cache (used for testing or reset) + // Clears the internal cache (used for testing or reset) public func clearCache() { filePropertyCache.removeAll() } @@ -92,19 +92,19 @@ public actor NKTypeIdentifiers { public final class NKTypeIdentifiersHelper { public static let shared = NKTypeIdentifiersHelper() - /// Internal actor reference (uses NKTypeIdentifiers.shared by default) + // 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) + // 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. + // 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) From 2555fd41f1e7680caab591b73901e9f292bb3115 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Sun, 22 Jun 2025 10:30:06 +0200 Subject: [PATCH 35/41] added typeidentifier Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/Models/NKFile.swift | 127 +++++++++++----------- Sources/NextcloudKit/Models/NKTrash.swift | 1 + Sources/NextcloudKit/NKDataFileXML.swift | 3 + 3 files changed, 69 insertions(+), 62 deletions(-) diff --git a/Sources/NextcloudKit/Models/NKFile.swift b/Sources/NextcloudKit/Models/NKFile.swift index 719d3b0a..50af10a5 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/NKTrash.swift b/Sources/NextcloudKit/Models/NKTrash.swift index 33b46b6d..182820b0 100644 --- a/Sources/NextcloudKit/Models/NKTrash.swift +++ b/Sources/NextcloudKit/Models/NKTrash.swift @@ -8,6 +8,7 @@ import Foundation 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/NKDataFileXML.swift b/Sources/NextcloudKit/NKDataFileXML.swift index 8123b844..386c994f 100644 --- a/Sources/NextcloudKit/NKDataFileXML.swift +++ b/Sources/NextcloudKit/NKDataFileXML.swift @@ -540,6 +540,8 @@ public class NKDataFileXML: NSObject { 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 @@ -649,6 +651,7 @@ public class NKDataFileXML: NSObject { file.contentType = results.mimeType file.classFile = results.classFile file.iconName = results.iconName + file.typeIdentifier = results.typeIdentifier files.append(file) } From 9f3e0c8447111b51231fbe389e4b482d5b478296 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Sun, 22 Jun 2025 10:34:41 +0200 Subject: [PATCH 36/41] fix Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/Models/NKFile.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/NextcloudKit/Models/NKFile.swift b/Sources/NextcloudKit/Models/NKFile.swift index 50af10a5..6e457157 100644 --- a/Sources/NextcloudKit/Models/NKFile.swift +++ b/Sources/NextcloudKit/Models/NKFile.swift @@ -80,7 +80,7 @@ public struct NKFile: Sendable { } public var exifPhotos: [[String: String?]] public var placePhotos: String? - public var typeIdentifier: String? + public var typeIdentifier: String public init( account: String = "", From 95def4039463bc1873c58d35974388008bd01b9b Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 23 Jun 2025 14:56:37 +0200 Subject: [PATCH 37/41] memory issue remove Data Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/NextcloudKit+Download.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/NextcloudKit/NextcloudKit+Download.swift b/Sources/NextcloudKit/NextcloudKit+Download.swift index 190e7732..75b52cce 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, _ afError: AFError?, _ nKError: NKError) -> Void) { var convertible: URLConvertible? if serverUrlFileName is URL { convertible = serverUrlFileName as? URLConvertible @@ -24,7 +24,7 @@ public extension NextcloudKit { guard let url = convertible, 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) } + return options.queue.async { completionHandler(account, nil, nil, 0, nil, .urlError) } } var destination: Alamofire.DownloadRequest.Destination? let fileNamePathLocalDestinationURL = NSURL.fileURL(withPath: fileNameLocalPath) @@ -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, 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, nil, .success) } } } From 9b1c141bb28a39170a1dd8f6f8c69f58d0dac080 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 23 Jun 2025 15:02:43 +0200 Subject: [PATCH 38/41] replace with headers Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/NextcloudKit+Download.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/NextcloudKit/NextcloudKit+Download.swift b/Sources/NextcloudKit/NextcloudKit+Download.swift index 75b52cce..313b6987 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, _ 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 @@ -24,7 +24,7 @@ public extension NextcloudKit { guard let url = convertible, 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, .urlError) } + return options.queue.async { completionHandler(account, nil, nil, 0, nil, nil, .urlError) } } var destination: Alamofire.DownloadRequest.Destination? let fileNamePathLocalDestinationURL = NSURL.fileURL(withPath: fileNameLocalPath) @@ -39,10 +39,11 @@ public extension NextcloudKit { } .downloadProgress { progress in options.queue.async { progressHandler(progress) } } .responseData(queue: self.nkCommonInstance.backgroundQueue) { response in + let headers = response.response?.allHeaderFields switch response.result { case .failure(let error): let resultError = NKError(error: error, afResponse: response, responseData: nil) - options.queue.async { completionHandler(account, nil, nil, 0, error, resultError) } + options.queue.async { completionHandler(account, nil, nil, 0, headers, error, resultError) } case .success: var date: Date? var etag: String? @@ -63,7 +64,7 @@ public extension NextcloudKit { date = dateRaw.parsedDate(using: "yyyy-MM-dd HH:mm:ss") } - options.queue.async { completionHandler(account, etag, date, length, nil, .success) } + options.queue.async { completionHandler(account, etag, date, length, headers, nil, .success) } } } From 3fc8cc907bb4d28d0d641cfc978633949e407346 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 23 Jun 2025 15:04:16 +0200 Subject: [PATCH 39/41] response.response?.allHeaderFields Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/NextcloudKit+Download.swift | 5 ++--- Sources/NextcloudKit/NextcloudKit+Upload.swift | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Sources/NextcloudKit/NextcloudKit+Download.swift b/Sources/NextcloudKit/NextcloudKit+Download.swift index 313b6987..c6159139 100644 --- a/Sources/NextcloudKit/NextcloudKit+Download.swift +++ b/Sources/NextcloudKit/NextcloudKit+Download.swift @@ -39,11 +39,10 @@ public extension NextcloudKit { } .downloadProgress { progress in options.queue.async { progressHandler(progress) } } .responseData(queue: self.nkCommonInstance.backgroundQueue) { response in - let headers = response.response?.allHeaderFields switch response.result { case .failure(let error): let resultError = NKError(error: error, afResponse: response, responseData: nil) - options.queue.async { completionHandler(account, nil, nil, 0, headers, error, resultError) } + options.queue.async { completionHandler(account, nil, nil, 0, response.response?.allHeaderFields, error, resultError) } case .success: var date: Date? var etag: String? @@ -64,7 +63,7 @@ public extension NextcloudKit { date = dateRaw.parsedDate(using: "yyyy-MM-dd HH:mm:ss") } - options.queue.async { completionHandler(account, etag, date, length, headers, nil, .success) } + options.queue.async { completionHandler(account, etag, date, length, response.response?.allHeaderFields, nil, .success) } } } diff --git a/Sources/NextcloudKit/NextcloudKit+Upload.swift b/Sources/NextcloudKit/NextcloudKit+Upload.swift index b266baf6..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 @@ -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) } } From 9c5cb8cbc09d51ece56a7be0d555389ea52f9b6e Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 24 Jun 2025 09:55:23 +0200 Subject: [PATCH 40/41] licence Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift index 3a79d647..edf08cb0 100644 --- a/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift +++ b/Sources/NextcloudKit/TypeIdentifiers/NKTypeIdentifiers.swift @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + import Foundation import UniformTypeIdentifiers From 2ab001225a06105ce16993e6be502a857f879b6f Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 24 Jun 2025 09:57:56 +0200 Subject: [PATCH 41/41] clean Signed-off-by: Marino Faggiana --- Sources/NextcloudKit/{ => Models}/NKDataFileXML.swift | 0 Sources/NextcloudKit/{ => Models}/NKShareAccounts.swift | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Sources/NextcloudKit/{ => Models}/NKDataFileXML.swift (100%) rename Sources/NextcloudKit/{ => Models}/NKShareAccounts.swift (100%) diff --git a/Sources/NextcloudKit/NKDataFileXML.swift b/Sources/NextcloudKit/Models/NKDataFileXML.swift similarity index 100% rename from Sources/NextcloudKit/NKDataFileXML.swift rename to Sources/NextcloudKit/Models/NKDataFileXML.swift 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