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..613b9564 100644 --- a/Sources/NextcloudKit/NextcloudKit+Upload.swift +++ b/Sources/NextcloudKit/NextcloudKit+Upload.swift @@ -38,10 +38,9 @@ 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 - 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,19 +92,14 @@ 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, self.evaluateResponse(response)) } } - options.queue.async { requestHandler(request) } + options.queue.async { + requestHandler(request) + } } /// Asynchronously uploads a file to the Nextcloud server. @@ -148,7 +140,7 @@ public extension NextcloudKit { etag: String?, date: Date?, size: Int64, - headers: [AnyHashable: Any]?, + response: AFDataResponse?, error: NKError ) { await withCheckedContinuation { continuation in @@ -161,14 +153,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 )) } @@ -355,7 +347,7 @@ public extension NextcloudKit { } guard uploadNKError == .success else { - return completion(account, filesChunkOutput, nil, .errorChunkFileUpload) + return completion(account, filesChunkOutput, nil, uploadNKError) } // Assemble the chunks diff --git a/Sources/NextcloudKit/NextcloudKit.swift b/Sources/NextcloudKit/NextcloudKit.swift index 8b06882e..bc260496 100644 --- a/Sources/NextcloudKit/NextcloudKit.swift +++ b/Sources/NextcloudKit/NextcloudKit.swift @@ -178,26 +178,36 @@ open class NextcloudKit { } #endif - /// Evaluates an Alamofire response and returns the appropriate NKError. - /// Treats `inputDataNilOrZeroLength` as `.success`. + /// 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 { - if let afError = response.error?.asAFError { - if afError.isExplicitlyCancelledError { - return .cancelled - } + // 1) Cancellations take precedence + if let afError = response.error?.asAFError, + afError.isExplicitlyCancelledError { + return .cancelled } - switch response.result { - case .failure(let error): - if let afError = error.asAFError, - case .responseSerializationFailed(let reason) = afError, - case .inputDataNilOrZeroLength = reason { + // 2) Prefer HTTP status code when available + if let code = response.response?.statusCode { + if (200...299).contains(code) { return .success - } else { - return NKError(error: error, afResponse: response, responseData: response.data) } + // Non-2xx: let the error flow below (even if serializer said "success") + } + + // 3) Fall back to Alamofire's result (covers transport errors and missing status) + switch response.result { case .success: return .success + + case .failure(let error): + // 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) } } }