Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Sources/NextcloudKit/NKError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: ""))
Expand Down
26 changes: 9 additions & 17 deletions Sources/NextcloudKit/NextcloudKit+Upload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Data>?, _ nkError: NKError) -> Void) {
var convertible: URLConvertible?
var uploadedSize: Int64 = 0
var uploadCompleted = false

if serverUrlFileName is URL {
convertible = serverUrlFileName as? URLConvertible
Expand Down Expand Up @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -148,7 +140,7 @@ public extension NextcloudKit {
etag: String?,
date: Date?,
size: Int64,
headers: [AnyHashable: Any]?,
response: AFDataResponse<Data>?,
error: NKError
) {
await withCheckedContinuation { continuation in
Expand All @@ -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
))
}
Expand Down Expand Up @@ -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
Expand Down
36 changes: 23 additions & 13 deletions Sources/NextcloudKit/NextcloudKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Data>(_ response: AFDataResponse<Data>) -> 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)
}
}
}
Loading