-
Notifications
You must be signed in to change notification settings - Fork 148
feat: support concurrent chunk uploads #313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,7 +40,7 @@ class ClientBrowser extends ClientBase with ClientMixin { | |
| 'x-sdk-name': 'Flutter', | ||
| 'x-sdk-platform': 'client', | ||
| 'x-sdk-language': 'flutter', | ||
| 'x-sdk-version': '24.1.1', | ||
| 'x-sdk-version': '24.2.0', | ||
| 'X-Appwrite-Response-Format': '1.9.5', | ||
| }; | ||
|
|
||
|
|
@@ -207,6 +207,7 @@ class ClientBrowser extends ClientBase with ClientMixin { | |
| } | ||
|
|
||
| var offset = 0; | ||
| String? uploadId; | ||
| if (idParamName.isNotEmpty) { | ||
| //make a request to check if a file already exists | ||
| try { | ||
|
|
@@ -217,40 +218,109 @@ class ClientBrowser extends ClientBase with ClientMixin { | |
| ); | ||
| final int chunksUploaded = res.data['chunksUploaded'] as int; | ||
| offset = chunksUploaded * chunkSize; | ||
| uploadId = res.data['\$id'] ?? params[idParamName]?.toString(); | ||
| } on AppwriteException catch (_) {} | ||
| } | ||
|
|
||
| while (offset < size) { | ||
| if (offset >= size) { | ||
| return res; | ||
| } | ||
|
|
||
| final totalChunks = (size / chunkSize).ceil(); | ||
|
|
||
| Future<Response> uploadChunk( | ||
| int index, int start, int end, String? id) async { | ||
| List<int> chunk = []; | ||
| final end = min(offset + chunkSize, size); | ||
| chunk = file.bytes!.getRange(offset, end).toList(); | ||
| params[paramName] = http.MultipartFile.fromBytes( | ||
| chunk = file.bytes!.getRange(start, end).toList(); | ||
|
|
||
| final chunkParams = Map<String, dynamic>.from(params); | ||
| chunkParams[paramName] = http.MultipartFile.fromBytes( | ||
| paramName, | ||
| chunk, | ||
| filename: file.filename, | ||
| ); | ||
| headers['content-range'] = | ||
| 'bytes $offset-${min<int>((offset + chunkSize - 1), size - 1)}/$size'; | ||
| res = await call( | ||
| final chunkHeaders = Map<String, String>.from(headers); | ||
| if (id != null && id.isNotEmpty) { | ||
| chunkHeaders['x-appwrite-id'] = id; | ||
| } | ||
| chunkHeaders['content-range'] = 'bytes $start-${end - 1}/$size'; | ||
|
|
||
| return call( | ||
| HttpMethod.post, | ||
| path: path, | ||
| headers: headers, | ||
| params: params, | ||
| headers: chunkHeaders, | ||
| params: chunkParams, | ||
| ); | ||
| offset += chunkSize; | ||
| if (offset < size) { | ||
| headers['x-appwrite-id'] = res.data['\$id']; | ||
| } | ||
|
|
||
| final firstStart = offset; | ||
| final firstEnd = min(firstStart + chunkSize, size); | ||
| final firstIndex = firstStart ~/ chunkSize; | ||
| res = await uploadChunk(firstIndex, firstStart, firstEnd, uploadId); | ||
| uploadId = res.data['\$id'] ?? uploadId; | ||
|
|
||
| var completedChunks = firstIndex + 1; | ||
| var uploadedBytes = firstEnd; | ||
| var lastResponse = res; | ||
|
|
||
| bool isUploadComplete(Response response) { | ||
| final chunksUploaded = response.data['chunksUploaded']; | ||
| final chunksTotal = response.data['chunksTotal'] ?? totalChunks; | ||
| return chunksUploaded is num && | ||
| chunksTotal is num && | ||
| chunksUploaded >= chunksTotal; | ||
| } | ||
|
|
||
| final progress = UploadProgress( | ||
| $id: uploadId ?? '', | ||
| progress: min(uploadedBytes, size) / size * 100, | ||
| sizeUploaded: min(uploadedBytes, size), | ||
| chunksTotal: totalChunks, | ||
| chunksUploaded: completedChunks, | ||
| ); | ||
| onProgress?.call(progress); | ||
|
|
||
| final chunks = <Map<String, int>>[]; | ||
| for (var start = firstEnd; start < size; start += chunkSize) { | ||
| final end = min(start + chunkSize, size); | ||
| chunks.add({ | ||
| 'index': start ~/ chunkSize, | ||
| 'start': start, | ||
| 'end': end, | ||
| }); | ||
| } | ||
|
|
||
| var nextChunk = 0; | ||
| Future<void> uploadNext() async { | ||
| while (nextChunk < chunks.length) { | ||
| final chunk = chunks[nextChunk++]; | ||
| final chunkResponse = await uploadChunk( | ||
| chunk['index']!, | ||
| chunk['start']!, | ||
| chunk['end']!, | ||
| uploadId, | ||
| ); | ||
| completedChunks++; | ||
| uploadedBytes += chunk['end']! - chunk['start']!; | ||
| if (isUploadComplete(chunkResponse)) { | ||
| lastResponse = chunkResponse; | ||
| } | ||
|
Comment on lines
+305
to
+307
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| final progress = UploadProgress( | ||
| $id: uploadId ?? '', | ||
| progress: min(uploadedBytes, size) / size * 100, | ||
| sizeUploaded: min(uploadedBytes, size), | ||
| chunksTotal: totalChunks, | ||
| chunksUploaded: completedChunks, | ||
| ); | ||
| onProgress?.call(progress); | ||
| } | ||
| final progress = UploadProgress( | ||
| $id: res.data['\$id'] ?? '', | ||
| progress: min(offset, size) / size * 100, | ||
| sizeUploaded: min(offset, size), | ||
| chunksTotal: res.data['chunksTotal'] ?? 0, | ||
| chunksUploaded: res.data['chunksUploaded'] ?? 0, | ||
| ); | ||
| onProgress?.call(progress); | ||
| } | ||
| return res; | ||
|
|
||
| final concurrency = min(8, chunks.length); | ||
| await Future.wait(List.generate(concurrency, (_) => uploadNext())); | ||
|
|
||
| return lastResponse; | ||
| } | ||
|
|
||
| @override | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indexparameter inuploadChunkThe
indexparameter is accepted byuploadChunkin bothclient_browser.dartandclient_io.dartbut is never referenced in either function body. All positioning is done viastart/end, so the parameter appears to be dead code. Remove it or document its intended use to avoid confusion.