@@ -195,42 +195,139 @@ export class Storage extends Service {
195195 } catch ( e ) {
196196 }
197197
198- let timestamp = new Date ( ) . getTime ( ) ;
199- while ( offset < size ) {
200- let end = Math . min ( offset + Service . CHUNK_SIZE - 1 , size - 1 ) ;
198+ const totalChunks = Math . ceil ( size / Service . CHUNK_SIZE ) ;
201199
202- apiHeaders [ 'content-range' ] = 'bytes ' + offset + '-' + end + '/' + size ;
203- if ( response && response . $id ) {
204- apiHeaders [ 'x-appwrite-id' ] = response . $id ;
205- }
200+ // Upload first chunk alone to get the upload ID
201+ if ( offset === 0 ) {
202+ const firstChunkEnd = Math . min ( Service . CHUNK_SIZE , size ) ;
203+ const firstChunkHeaders = { ... apiHeaders , 'content-range' : 'bytes 0-' + ( firstChunkEnd - 1 ) + '/' + size } ;
206204
207- let chunk = await FileSystem . readAsStringAsync ( file . uri , {
205+ let firstChunk = await FileSystem . readAsStringAsync ( file . uri , {
208206 encoding : FileSystem . EncodingType . Base64 ,
209- position : offset ,
207+ position : 0 ,
210208 length : Service . CHUNK_SIZE
211209 } ) ;
212- var path = `data:${ file . type } ;base64,${ chunk } ` ;
210+ var firstPath = `data:${ file . type } ;base64,${ firstChunk } ` ;
213211 if ( RNPlatform . OS . toLowerCase ( ) === 'android' ) {
214- path = FileSystem . cacheDirectory + '/tmp_chunk_' + timestamp ;
215- await FileSystem . writeAsStringAsync ( path , chunk , { encoding : FileSystem . EncodingType . Base64 } ) ;
212+ firstPath = FileSystem . cacheDirectory + '/tmp_chunk_' + new Date ( ) . getTime ( ) ;
213+ await FileSystem . writeAsStringAsync ( firstPath , firstChunk , { encoding : FileSystem . EncodingType . Base64 } ) ;
216214 }
217215
218- payload [ 'file' ] = { uri : path , name : file . name , type : file . type } ;
216+ payload [ 'file' ] = { uri : firstPath , name : file . name , type : file . type } ;
219217
220- response = await this . client . call ( 'post' , uri , apiHeaders , payload ) ;
218+ response = await this . client . call ( 'post' , uri , firstChunkHeaders , payload ) ;
219+ offset = firstChunkEnd ;
221220
222221 if ( onProgress ) {
223222 onProgress ( {
224223 $id : response . $id ,
225224 progress : ( offset / size ) * 100 ,
226225 sizeUploaded : offset ,
227- chunksTotal : response . chunksTotal ,
228- chunksUploaded : response . chunksUploaded
226+ chunksTotal : totalChunks ,
227+ chunksUploaded : 1
229228 } ) ;
230229 }
231- offset += Service . CHUNK_SIZE ;
232230 }
233- return response ;
231+
232+ if ( offset >= size ) {
233+ return response ;
234+ }
235+
236+ const uploadId = response ?. $id ;
237+ const chunks : { index : number ; start : number ; end : number } [ ] = [ ] ;
238+ const startChunkIndex = Math . ceil ( offset / Service . CHUNK_SIZE ) ;
239+ for ( let i = startChunkIndex ; i < totalChunks ; i ++ ) {
240+ const start = i * Service . CHUNK_SIZE ;
241+ const end = Math . min ( start + Service . CHUNK_SIZE , size ) ;
242+ chunks . push ( { index : i , start, end } ) ;
243+ }
244+
245+ // Upload remaining chunks with max concurrency of 8
246+ const CONCURRENCY = 8 ;
247+ let completedCount = startChunkIndex ;
248+ let uploadedBytes = offset ;
249+ let finalResponse = null ;
250+ let failed = false ;
251+
252+ const isUploadComplete = ( chunkResponse : any ) => {
253+ const chunksUploaded = chunkResponse ?. chunksUploaded ;
254+ const chunksTotal = chunkResponse ?. chunksTotal ?? totalChunks ;
255+ return typeof chunksUploaded === 'number' && typeof chunksTotal === 'number' && chunksUploaded >= chunksTotal ;
256+ } ;
257+
258+ const uploadChunk = async ( chunk : typeof chunks [ 0 ] ) => {
259+ const chunkHeaders = { ...apiHeaders } ;
260+ if ( uploadId ) {
261+ chunkHeaders [ 'x-appwrite-id' ] = uploadId ;
262+ }
263+ chunkHeaders [ 'content-range' ] = 'bytes ' + chunk . start + '-' + ( chunk . end - 1 ) + '/' + size ;
264+
265+ const chunkData = await FileSystem . readAsStringAsync ( file . uri , {
266+ encoding : FileSystem . EncodingType . Base64 ,
267+ position : chunk . start ,
268+ length : chunk . end - chunk . start
269+ } ) ;
270+
271+ let chunkPath = `data:${ file . type } ;base64,${ chunkData } ` ;
272+ if ( RNPlatform . OS . toLowerCase ( ) === 'android' ) {
273+ chunkPath = FileSystem . cacheDirectory + '/tmp_chunk_' + new Date ( ) . getTime ( ) + '_' + chunk . index ;
274+ await FileSystem . writeAsStringAsync ( chunkPath , chunkData , { encoding : FileSystem . EncodingType . Base64 } ) ;
275+ }
276+
277+ const chunkPayload = { ...payload } ;
278+ chunkPayload [ 'file' ] = { uri : chunkPath , name : file . name , type : file . type } ;
279+
280+ const chunkResponse = await this . client . call ( 'post' , uri , chunkHeaders , chunkPayload ) ;
281+
282+ if ( failed ) {
283+ return chunkResponse ;
284+ }
285+
286+ completedCount ++ ;
287+ uploadedBytes += ( chunk . end - chunk . start ) ;
288+
289+ response = chunkResponse ;
290+ if ( isUploadComplete ( chunkResponse ) ) {
291+ finalResponse = chunkResponse ;
292+ }
293+
294+ if ( onProgress ) {
295+ onProgress ( {
296+ $id : uploadId ,
297+ progress : ( uploadedBytes / size ) * 100 ,
298+ sizeUploaded : uploadedBytes ,
299+ chunksTotal : totalChunks ,
300+ chunksUploaded : completedCount
301+ } ) ;
302+ }
303+
304+ return chunkResponse ;
305+ } ;
306+
307+ // Process with limited concurrency using a worker pool
308+ const queue = [ ...chunks ] ;
309+ const workers : Promise < void > [ ] = [ ] ;
310+ const workerCount = Math . min ( CONCURRENCY , queue . length ) ;
311+
312+ for ( let i = 0 ; i < workerCount ; i ++ ) {
313+ workers . push (
314+ ( async ( ) => {
315+ while ( ! failed && queue . length > 0 ) {
316+ const chunk = queue . shift ( ) ! ;
317+ try {
318+ await uploadChunk ( chunk ) ;
319+ } catch ( error ) {
320+ failed = true ;
321+ throw error ;
322+ }
323+ }
324+ } ) ( )
325+ ) ;
326+ }
327+
328+ await Promise . all ( workers ) ;
329+
330+ return finalResponse ?? response ;
234331 }
235332
236333 /**
0 commit comments