@@ -16,6 +16,7 @@ open class Client {
1616
1717 // MARK: Properties
1818 public static var chunkSize = 5 * 1024 * 1024 // 5MB
19+ public static var maxConcurrentUploads = 8
1920
2021 open var endPoint = " https://cloud.appwrite.io/v1 "
2122
@@ -583,6 +584,7 @@ open class Client {
583584
584585 var offset = 0
585586 var result = [ String: Any] ( )
587+ var uploadId = idParamName != nil ? params [ idParamName!] as? String : nil
586588
587589 if idParamName != nil {
588590 // Make a request to check if a file already exists
@@ -596,37 +598,113 @@ open class Client {
596598 )
597599 let chunksUploaded = map [ " chunksUploaded " ] as! Int
598600 offset = chunksUploaded * Client. chunkSize
601+ result = map
599602 } catch {
600603 // File does not exist yet, swallow exception
601604 }
602605 }
603606
604- while offset < size {
605- let slice = ( input. data as! ByteBuffer ) . getSlice ( at: offset, length: Client . chunkSize)
606- ?? ( input. data as! ByteBuffer ) . getSlice ( at: offset, length: Int ( size - offset) )
607+ let totalChunks = Int ( ceil ( Double ( size) / Double( Client . chunkSize) ) )
608+ var nextChunk = offset / Client. chunkSize
609+ var completedChunks = nextChunk
610+ var uploadedBytes = min ( offset, size)
611+ var completedResponse : [ String : Any ] ? = nil
612+ var lastChunkResponse : [ String : Any ] ? = nil
613+ let baseParams = params
614+ let baseHeaders = headers
615+
616+ func isUploadComplete( _ response: [ String : Any ] ) -> Bool {
617+ guard let chunksUploaded = response [ " chunksUploaded " ] as? Int else {
618+ return false
619+ }
620+
621+ let chunksTotal = response [ " chunksTotal " ] as? Int ?? totalChunks
622+ return chunksUploaded >= chunksTotal
623+ }
607624
608- params [ paramName] = InputFile . fromBuffer ( slice!, filename: input. filename, mimeType: input. mimeType)
609- headers [ " content-range " ] = " bytes \( offset) - \( min ( ( offset + Client. chunkSize) - 1 , size - 1 ) ) / \( size) "
625+ func uploadChunk( index: Int , uploadId: String ? ) async throws -> ( Int , Int , [ String : Any ] ) {
626+ let chunkOffset = index * Client. chunkSize
627+ let chunkLength = min ( Client . chunkSize, size - chunkOffset)
628+ guard let slice = ( input. data as! ByteBuffer ) . getSlice ( at: chunkOffset, length: chunkLength) else {
629+ throw AppwriteError ( message: " Failed to read upload chunk " )
630+ }
631+ var chunkParams = baseParams
632+ var chunkHeaders = baseHeaders
633+ chunkParams [ paramName] = InputFile . fromBuffer ( slice, filename: input. filename, mimeType: input. mimeType)
634+ chunkHeaders [ " content-range " ] = " bytes \( chunkOffset) - \( chunkOffset + chunkLength - 1 ) / \( size) "
635+ if let uploadId = uploadId {
636+ chunkHeaders [ " x-appwrite-id " ] = uploadId
637+ }
610638
611- result = try await call (
639+ let chunkResult = try await call (
612640 method: " POST " ,
613641 path: path,
614- headers: headers ,
615- params: params ,
642+ headers: chunkHeaders ,
643+ params: chunkParams ,
616644 converter: { return $0 as! [ String : Any ] }
617645 )
618646
619- offset += Client . chunkSize
620- headers [ " x-appwrite-id " ] = result [ " $id " ] as? String
647+ return ( index, chunkLength, chunkResult)
648+ }
649+
650+ if nextChunk == 0 {
651+ let first = try await uploadChunk ( index: 0 , uploadId: uploadId)
652+ result = first. 2
653+ uploadId = result [ " $id " ] as? String
654+ nextChunk = 1
655+ completedChunks = 1
656+ uploadedBytes = first. 1
621657 onProgress ? ( UploadProgress (
622- id: result [ " $id " ] as? String ?? " " ,
623- progress: Double ( min ( offset , size ) ) / Double( size) * 100.0 ,
624- sizeUploaded: min ( offset , size ) ,
625- chunksTotal: result [ " chunksTotal " ] as? Int ?? - 1 ,
626- chunksUploaded: result [ " chunksUploaded " ] as? Int ?? - 1
658+ id: uploadId ?? " " ,
659+ progress: Double ( uploadedBytes ) / Double( size) * 100.0 ,
660+ sizeUploaded: uploadedBytes ,
661+ chunksTotal: result [ " chunksTotal " ] as? Int ?? totalChunks ,
662+ chunksUploaded: result [ " chunksUploaded " ] as? Int ?? completedChunks
627663 ) )
628664 }
629665
666+ let maxConcurrency = Client . maxConcurrentUploads
667+
668+ try await withThrowingTaskGroup ( of: ( Int, Int, [ String: Any] ) . self) { group in
669+ var inFlight = 0
670+
671+ while inFlight < maxConcurrency && nextChunk < totalChunks {
672+ let index = nextChunk
673+ let currentUploadId = uploadId
674+ group. addTask { try await uploadChunk ( index: index, uploadId: currentUploadId) }
675+ nextChunk += 1
676+ inFlight += 1
677+ }
678+
679+ while let chunk = try await group. next ( ) {
680+ inFlight -= 1
681+ completedChunks += 1
682+ uploadedBytes += chunk. 1
683+ lastChunkResponse = chunk. 2
684+ if isUploadComplete ( chunk. 2 ) {
685+ completedResponse = chunk. 2
686+ }
687+
688+ onProgress ? ( UploadProgress (
689+ id: uploadId ?? " " ,
690+ progress: Double ( min ( uploadedBytes, size) ) / Double( size) * 100.0 ,
691+ sizeUploaded: min ( uploadedBytes, size) ,
692+ chunksTotal: chunk. 2 [ " chunksTotal " ] as? Int ?? totalChunks,
693+ chunksUploaded: chunk. 2 [ " chunksUploaded " ] as? Int ?? completedChunks
694+ ) )
695+
696+ while inFlight < maxConcurrency && nextChunk < totalChunks {
697+ let index = nextChunk
698+ let currentUploadId = uploadId
699+ group. addTask { try await uploadChunk ( index: index, uploadId: currentUploadId) }
700+ nextChunk += 1
701+ inFlight += 1
702+ }
703+ }
704+ }
705+
706+ result = completedResponse ?? lastChunkResponse ?? result
707+
630708 return try converter!( result)
631709 }
632710
0 commit comments