From c96cee29181fd7b6aef65c5813d63f9e6e0755fa Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 15 Aug 2025 19:22:23 +0200 Subject: [PATCH 1/4] fix upload --- Sources/NextcloudKit/NKError.swift | 3 --- Sources/NextcloudKit/NextcloudKit+Upload.swift | 14 ++------------ 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/Sources/NextcloudKit/NKError.swift b/Sources/NextcloudKit/NKError.swift index a70a7ed2..64c17758 100644 --- a/Sources/NextcloudKit/NKError.swift +++ b/Sources/NextcloudKit/NKError.swift @@ -49,9 +49,6 @@ public struct NKError: Error, Equatable { public static let forbiddenError = NKError(errorCode: 403, errorDescription: NSLocalizedString("_forbidden_", value: "Forbidden", comment: "")) public static let cancelled = NKError(errorCode: -999, errorDescription: NSLocalizedString("_cancelled_", value: "Cancelled", comment: "")) - public static let uploadIncomplete = NKError(errorCode: -9992, errorDescription: NSLocalizedString("_upload_incomplete_", value: "Upload incomplete", comment: "")) - - public static let errorChunkFileUpload = NKError(errorCode: -9993, errorDescription: NSLocalizedString("_upload_incomplete_", value: "Upload incomplete", comment: "")) public static let errorChunkFileNull = NKError(errorCode: -9994, errorDescription: NSLocalizedString("_error_file_null_", value: "File not found", comment: "")) public static let errorChunkFilesEmpty = NKError(errorCode: -9995, errorDescription: NSLocalizedString("_chunk_files_empty_", value: "Files not found", comment: "")) public static let errorChunkCreateFolder = NKError(errorCode: -9996, errorDescription: NSLocalizedString("_error_create_folder_", value: "Create folder error", comment: "")) diff --git a/Sources/NextcloudKit/NextcloudKit+Upload.swift b/Sources/NextcloudKit/NextcloudKit+Upload.swift index 13c2c77a..ddc94491 100644 --- a/Sources/NextcloudKit/NextcloudKit+Upload.swift +++ b/Sources/NextcloudKit/NextcloudKit+Upload.swift @@ -41,7 +41,6 @@ public extension NextcloudKit { 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 if serverUrlFileName is URL { convertible = serverUrlFileName as? URLConvertible @@ -72,11 +71,9 @@ public extension NextcloudKit { options.queue.async { taskHandler(task) } }) .uploadProgress { progress in uploadedSize = progress.totalUnitCount - uploadCompleted = progress.fractionCompleted == 1.0 options.queue.async { progressHandler(progress) } } .responseData(queue: self.nkCommonInstance.backgroundQueue) { response in var ocId: String?, etag: String?, date: Date? - var result: NKError if self.nkCommonInstance.findHeader("oc-fileid", allHeaderFields: response.response?.allHeaderFields) != nil { ocId = self.nkCommonInstance.findHeader("oc-fileid", allHeaderFields: response.response?.allHeaderFields) @@ -95,15 +92,8 @@ public extension NextcloudKit { date = dateRaw.parsedDate(using: "EEE, dd MMM y HH:mm:ss zzz") } - if !uploadCompleted { - nkLog(error: "Upload incomplete: only \(uploadedSize) bytes sent.") - result = .uploadIncomplete - } else { - result = self.evaluateResponse(response) - } - options.queue.async { - completionHandler(account, ocId, etag, date, uploadedSize, response.response?.allHeaderFields, result) + completionHandler(account, ocId, etag, date, uploadedSize, response.response?.allHeaderFields, self.evaluateResponse(response)) } } @@ -355,7 +345,7 @@ public extension NextcloudKit { } guard uploadNKError == .success else { - return completion(account, filesChunkOutput, nil, .errorChunkFileUpload) + return completion(account, filesChunkOutput, nil, uploadNKError) } // Assemble the chunks From 3895f4d6f8a7e0d2711b2218781bac7d600f3eb1 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 15 Aug 2025 19:27:51 +0200 Subject: [PATCH 2/4] Update NextcloudKit+Upload.swift --- Sources/NextcloudKit/NextcloudKit+Upload.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/NextcloudKit/NextcloudKit+Upload.swift b/Sources/NextcloudKit/NextcloudKit+Upload.swift index ddc94491..483e8487 100644 --- a/Sources/NextcloudKit/NextcloudKit+Upload.swift +++ b/Sources/NextcloudKit/NextcloudKit+Upload.swift @@ -38,7 +38,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, _ headers: [AnyHashable: Any]?, _ nkError: NKError) -> Void) { + completionHandler: @escaping (_ account: String, _ ocId: String?, _ etag: String?, _ date: Date?, _ size: Int64, _ response: AFDataResponse?, _ nkError: NKError) -> Void) { var convertible: URLConvertible? var uploadedSize: Int64 = 0 @@ -93,7 +93,7 @@ public extension NextcloudKit { } options.queue.async { - completionHandler(account, ocId, etag, date, uploadedSize, response.response?.allHeaderFields, self.evaluateResponse(response)) + completionHandler(account, ocId, etag, date, uploadedSize, response, self.evaluateResponse(response)) } } @@ -138,7 +138,7 @@ public extension NextcloudKit { etag: String?, date: Date?, size: Int64, - headers: [AnyHashable: Any]?, + response: AFDataResponse?, error: NKError ) { await withCheckedContinuation { continuation in @@ -151,14 +151,14 @@ public extension NextcloudKit { options: options, requestHandler: requestHandler, taskHandler: taskHandler, - progressHandler: progressHandler) { account, ocId, etag, date, size, headers, error in + progressHandler: progressHandler) { account, ocId, etag, date, size, response, error in continuation.resume(returning: ( account: account, ocId: ocId, etag: etag, date: date, size: size, - headers: headers, + response: response, error: error )) } From 4f4d8fd33671f6dab8edc9ee210f537fa6a4ab3b Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Sat, 16 Aug 2025 07:18:09 +0200 Subject: [PATCH 3/4] improvement - evaluateResponse --- .../NextcloudKit/NextcloudKit+Upload.swift | 4 +- Sources/NextcloudKit/NextcloudKit.swift | 48 +++++++++++++++++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/Sources/NextcloudKit/NextcloudKit+Upload.swift b/Sources/NextcloudKit/NextcloudKit+Upload.swift index 483e8487..613b9564 100644 --- a/Sources/NextcloudKit/NextcloudKit+Upload.swift +++ b/Sources/NextcloudKit/NextcloudKit+Upload.swift @@ -97,7 +97,9 @@ public extension NextcloudKit { } } - options.queue.async { requestHandler(request) } + options.queue.async { + requestHandler(request) + } } /// Asynchronously uploads a file to the Nextcloud server. diff --git a/Sources/NextcloudKit/NextcloudKit.swift b/Sources/NextcloudKit/NextcloudKit.swift index 8b06882e..61bdf9a2 100644 --- a/Sources/NextcloudKit/NextcloudKit.swift +++ b/Sources/NextcloudKit/NextcloudKit.swift @@ -178,13 +178,13 @@ open class NextcloudKit { } #endif + /* /// Evaluates an Alamofire response and returns the appropriate NKError. - /// Treats `inputDataNilOrZeroLength` as `.success`. func evaluateResponse(_ response: AFDataResponse) -> NKError { - if let afError = response.error?.asAFError { - if afError.isExplicitlyCancelledError { - return .cancelled - } + // Treat explicit cancellations as a first-class outcome + if let afError = response.error?.asAFError, + afError.isExplicitlyCancelledError { + return .cancelled } switch response.result { @@ -200,4 +200,42 @@ open class NextcloudKit { return .success } } + */ + + /// Evaluates an Alamofire response and returns the appropriate NKError. + func evaluateResponse(_ response: AFDataResponse) -> NKError { + // Treat explicit cancellations as a first-class outcome + if let afError = response.error?.asAFError, + afError.isExplicitlyCancelledError { + return .cancelled + } + + // Prefer HTTP status code over serializer outcome for uploads + let statusCode = response.response?.statusCode + if let code = statusCode { + // Success on any 2xx; explicitly include 204/205 which carry no body by definition + if (200...299).contains(code) || code == 204 || code == 205 { + return .success + } + } + + // Fall back to Alamofire's result only if HTTP status wasn't clearly successful + switch response.result { + case .success: + return .success + + case .failure(let error): + // If the only failure reason is "no data" but status is actually OK, still succeed + if let afError = error.asAFError, + case .responseSerializationFailed(let reason) = afError, + case .inputDataNilOrZeroLength = reason, + let code = statusCode, + (200...299).contains(code) || code == 204 || code == 205 { + return .success + } + + // Everything else is a real error: keep the payload for diagnostics + return NKError(error: error, afResponse: response, responseData: response.data) + } + } } From 48bc168ab84b44c4038c7df90ce6f4547be9c2cf Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Sat, 16 Aug 2025 07:26:07 +0200 Subject: [PATCH 4/4] Update NextcloudKit.swift --- Sources/NextcloudKit/NextcloudKit.swift | 58 +++++++------------------ 1 file changed, 15 insertions(+), 43 deletions(-) diff --git a/Sources/NextcloudKit/NextcloudKit.swift b/Sources/NextcloudKit/NextcloudKit.swift index 61bdf9a2..bc260496 100644 --- a/Sources/NextcloudKit/NextcloudKit.swift +++ b/Sources/NextcloudKit/NextcloudKit.swift @@ -178,63 +178,35 @@ open class NextcloudKit { } #endif - /* - /// Evaluates an Alamofire response and returns the appropriate NKError. + /// Evaluates a generic Alamofire response into NKError with simple HTTP-aware rules. + /// - Note: + /// - Explicit cancellations return `.cancelled`. + /// - Any HTTP 2xx is considered success, regardless of body presence. + /// - If no HTTP status is available, fall back to Alamofire's `Result`. func evaluateResponse(_ response: AFDataResponse) -> NKError { - // Treat explicit cancellations as a first-class outcome + // 1) Cancellations take precedence if let afError = response.error?.asAFError, - afError.isExplicitlyCancelledError { + afError.isExplicitlyCancelledError { return .cancelled } - switch response.result { - case .failure(let error): - if let afError = error.asAFError, - case .responseSerializationFailed(let reason) = afError, - case .inputDataNilOrZeroLength = reason { - return .success - } else { - return NKError(error: error, afResponse: response, responseData: response.data) - } - case .success: - return .success - } - } - */ - - /// Evaluates an Alamofire response and returns the appropriate NKError. - func evaluateResponse(_ response: AFDataResponse) -> NKError { - // Treat explicit cancellations as a first-class outcome - if let afError = response.error?.asAFError, - afError.isExplicitlyCancelledError { - return .cancelled - } - - // Prefer HTTP status code over serializer outcome for uploads - let statusCode = response.response?.statusCode - if let code = statusCode { - // Success on any 2xx; explicitly include 204/205 which carry no body by definition - if (200...299).contains(code) || code == 204 || code == 205 { + // 2) Prefer HTTP status code when available + if let code = response.response?.statusCode { + if (200...299).contains(code) { return .success } + // Non-2xx: let the error flow below (even if serializer said "success") } - // Fall back to Alamofire's result only if HTTP status wasn't clearly successful + // 3) Fall back to Alamofire's result (covers transport errors and missing status) switch response.result { case .success: return .success case .failure(let error): - // If the only failure reason is "no data" but status is actually OK, still succeed - if let afError = error.asAFError, - case .responseSerializationFailed(let reason) = afError, - case .inputDataNilOrZeroLength = reason, - let code = statusCode, - (200...299).contains(code) || code == 204 || code == 205 { - return .success - } - - // Everything else is a real error: keep the payload for diagnostics + // No need to special-case inputDataNilOrZeroLength here: + // - If it was a 2xx, we already returned above. + // - If it's not 2xx or no status code, it's a real failure for our purposes. return NKError(error: error, afResponse: response, responseData: response.data) } }