From 00ece65c6662860fc46d4b123dc5c1757ca5ea2b Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 21 May 2026 17:19:17 +0400 Subject: [PATCH 01/10] feat: support concurrent chunk uploads --- lib/src/client_browser.dart | 119 +++++++++++++++++++++++--------- lib/src/client_io.dart | 131 ++++++++++++++++++++++++++---------- pubspec.yaml | 2 +- 3 files changed, 184 insertions(+), 68 deletions(-) diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 98c71f28..2d65cb2e 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -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', }; @@ -63,7 +63,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } - /// Your secret JSON Web Token @override ClientBrowser setJWT(value) { @@ -71,14 +70,12 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } - @override ClientBrowser setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } - /// The user session to authenticate with @override ClientBrowser setSession(value) { @@ -86,7 +83,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } - /// Your secret dev API key @override ClientBrowser setDevKey(value) { @@ -94,7 +90,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } - /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientBrowser setCookie(value) { @@ -102,7 +97,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } - /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserId(value) { @@ -110,7 +104,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } - /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserEmail(value) { @@ -118,7 +111,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } - /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserPhone(value) { @@ -207,6 +199,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 +210,106 @@ 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 uploadChunk(int index, int start, int end, String? id) async { List 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.from(params); + chunkParams[paramName] = http.MultipartFile.fromBytes( paramName, chunk, filename: file.filename, ); - headers['content-range'] = - 'bytes $offset-${min((offset + chunkSize - 1), size - 1)}/$size'; - res = await call( + final chunkHeaders = Map.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 = >[]; + 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 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; + } + + 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 diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index ff6501c8..a5b538d8 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -58,7 +58,7 @@ class ClientIO 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', }; @@ -89,7 +89,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } - /// Your secret JSON Web Token @override ClientIO setJWT(value) { @@ -97,14 +96,12 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } - @override ClientIO setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } - /// The user session to authenticate with @override ClientIO setSession(value) { @@ -112,7 +109,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } - /// Your secret dev API key @override ClientIO setDevKey(value) { @@ -120,7 +116,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } - /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientIO setCookie(value) { @@ -128,7 +123,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } - /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserId(value) { @@ -136,7 +130,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } - /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserEmail(value) { @@ -144,7 +137,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } - /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserPhone(value) { @@ -328,6 +320,7 @@ class ClientIO extends ClientBase with ClientMixin { } var offset = 0; + String? uploadId; if (idParamName.isNotEmpty) { //make a request to check if a file already exists try { @@ -338,52 +331,116 @@ class ClientIO 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 (_) {} } - RandomAccessFile? raf; - // read chunk and upload each chunk - if (iofile != null) { - raf = await iofile.open(mode: FileMode.read); + if (offset >= size) { + return res; } - while (offset < size) { + final totalChunks = (size / chunkSize).ceil(); + + Future uploadChunk(int index, int start, int end, String? id) async { List chunk = []; if (file.bytes != null) { - final end = min(offset + chunkSize, size); - chunk = file.bytes!.getRange(offset, end).toList(); + chunk = file.bytes!.getRange(start, end).toList(); } else { - raf!.setPositionSync(offset); - chunk = raf.readSync(chunkSize); + final raf = await iofile!.open(mode: FileMode.read); + try { + await raf.setPosition(start); + chunk = await raf.read(end - start); + } finally { + await raf.close(); + } } - params[paramName] = http.MultipartFile.fromBytes( + + final chunkParams = Map.from(params); + chunkParams[paramName] = http.MultipartFile.fromBytes( paramName, chunk, filename: file.filename, ); - headers['content-range'] = - 'bytes $offset-${min((offset + chunkSize - 1), size - 1)}/$size'; - res = await call( + final chunkHeaders = Map.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 = >[]; + 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 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; + } + + 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); } - raf?.close(); - return res; + + final concurrency = min(8, chunks.length); + await Future.wait(List.generate(concurrency, (_) => uploadNext())); + + return lastResponse; } bool get _customSchemeAllowed => Platform.isWindows || Platform.isLinux; diff --git a/pubspec.yaml b/pubspec.yaml index 052a6a98..776d6e45 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: appwrite -version: 24.1.1 +version: 24.2.0 description: Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API homepage: https://appwrite.io repository: https://github.com/appwrite/sdk-for-flutter From 935d8f016cf131a6bae2740a963a2f4c555ef5d2 Mon Sep 17 00:00:00 2001 From: TorstenDittmann Date: Thu, 21 May 2026 13:20:43 +0000 Subject: [PATCH 02/10] Commit from GitHub Actions (Format and push) --- lib/src/client_browser.dart | 15 +++++++++++++-- lib/src/client_io.dart | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 2d65cb2e..2c562071 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -63,6 +63,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } + /// Your secret JSON Web Token @override ClientBrowser setJWT(value) { @@ -70,12 +71,14 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } + @override ClientBrowser setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } + /// The user session to authenticate with @override ClientBrowser setSession(value) { @@ -83,6 +86,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } + /// Your secret dev API key @override ClientBrowser setDevKey(value) { @@ -90,6 +94,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } + /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientBrowser setCookie(value) { @@ -97,6 +102,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } + /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserId(value) { @@ -104,6 +110,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } + /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserEmail(value) { @@ -111,6 +118,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } + /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserPhone(value) { @@ -220,7 +228,8 @@ class ClientBrowser extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk(int index, int start, int end, String? id) async { + Future uploadChunk( + int index, int start, int end, String? id) async { List chunk = []; chunk = file.bytes!.getRange(start, end).toList(); @@ -257,7 +266,9 @@ class ClientBrowser extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; + return chunksUploaded is num && + chunksTotal is num && + chunksUploaded >= chunksTotal; } final progress = UploadProgress( diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index a5b538d8..95436bfd 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -89,6 +89,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } + /// Your secret JSON Web Token @override ClientIO setJWT(value) { @@ -96,12 +97,14 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } + @override ClientIO setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } + /// The user session to authenticate with @override ClientIO setSession(value) { @@ -109,6 +112,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } + /// Your secret dev API key @override ClientIO setDevKey(value) { @@ -116,6 +120,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } + /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientIO setCookie(value) { @@ -123,6 +128,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } + /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserId(value) { @@ -130,6 +136,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } + /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserEmail(value) { @@ -137,6 +144,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } + /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserPhone(value) { @@ -341,7 +349,8 @@ class ClientIO extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk(int index, int start, int end, String? id) async { + Future uploadChunk( + int index, int start, int end, String? id) async { List chunk = []; if (file.bytes != null) { chunk = file.bytes!.getRange(start, end).toList(); @@ -388,7 +397,9 @@ class ClientIO extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; + return chunksUploaded is num && + chunksTotal is num && + chunksUploaded >= chunksTotal; } final progress = UploadProgress( From 0cb14165f52a85bb74a521da337bca46403eca4a Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 21 May 2026 20:42:14 +0400 Subject: [PATCH 03/10] feat: support concurrent chunk uploads --- lib/src/client_browser.dart | 15 ++------------- lib/src/client_io.dart | 15 ++------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 2c562071..2d65cb2e 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -63,7 +63,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } - /// Your secret JSON Web Token @override ClientBrowser setJWT(value) { @@ -71,14 +70,12 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } - @override ClientBrowser setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } - /// The user session to authenticate with @override ClientBrowser setSession(value) { @@ -86,7 +83,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } - /// Your secret dev API key @override ClientBrowser setDevKey(value) { @@ -94,7 +90,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } - /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientBrowser setCookie(value) { @@ -102,7 +97,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } - /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserId(value) { @@ -110,7 +104,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } - /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserEmail(value) { @@ -118,7 +111,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } - /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserPhone(value) { @@ -228,8 +220,7 @@ class ClientBrowser extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk( - int index, int start, int end, String? id) async { + Future uploadChunk(int index, int start, int end, String? id) async { List chunk = []; chunk = file.bytes!.getRange(start, end).toList(); @@ -266,9 +257,7 @@ class ClientBrowser extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && - chunksTotal is num && - chunksUploaded >= chunksTotal; + return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; } final progress = UploadProgress( diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index 95436bfd..a5b538d8 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -89,7 +89,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } - /// Your secret JSON Web Token @override ClientIO setJWT(value) { @@ -97,14 +96,12 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } - @override ClientIO setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } - /// The user session to authenticate with @override ClientIO setSession(value) { @@ -112,7 +109,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } - /// Your secret dev API key @override ClientIO setDevKey(value) { @@ -120,7 +116,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } - /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientIO setCookie(value) { @@ -128,7 +123,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } - /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserId(value) { @@ -136,7 +130,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } - /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserEmail(value) { @@ -144,7 +137,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } - /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserPhone(value) { @@ -349,8 +341,7 @@ class ClientIO extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk( - int index, int start, int end, String? id) async { + Future uploadChunk(int index, int start, int end, String? id) async { List chunk = []; if (file.bytes != null) { chunk = file.bytes!.getRange(start, end).toList(); @@ -397,9 +388,7 @@ class ClientIO extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && - chunksTotal is num && - chunksUploaded >= chunksTotal; + return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; } final progress = UploadProgress( From 767db86b0daac33e61b6e708c2a3184b501b0338 Mon Sep 17 00:00:00 2001 From: TorstenDittmann Date: Thu, 21 May 2026 16:43:37 +0000 Subject: [PATCH 04/10] Commit from GitHub Actions (Format and push) --- lib/src/client_browser.dart | 15 +++++++++++++-- lib/src/client_io.dart | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 2d65cb2e..2c562071 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -63,6 +63,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } + /// Your secret JSON Web Token @override ClientBrowser setJWT(value) { @@ -70,12 +71,14 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } + @override ClientBrowser setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } + /// The user session to authenticate with @override ClientBrowser setSession(value) { @@ -83,6 +86,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } + /// Your secret dev API key @override ClientBrowser setDevKey(value) { @@ -90,6 +94,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } + /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientBrowser setCookie(value) { @@ -97,6 +102,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } + /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserId(value) { @@ -104,6 +110,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } + /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserEmail(value) { @@ -111,6 +118,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } + /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserPhone(value) { @@ -220,7 +228,8 @@ class ClientBrowser extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk(int index, int start, int end, String? id) async { + Future uploadChunk( + int index, int start, int end, String? id) async { List chunk = []; chunk = file.bytes!.getRange(start, end).toList(); @@ -257,7 +266,9 @@ class ClientBrowser extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; + return chunksUploaded is num && + chunksTotal is num && + chunksUploaded >= chunksTotal; } final progress = UploadProgress( diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index a5b538d8..95436bfd 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -89,6 +89,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } + /// Your secret JSON Web Token @override ClientIO setJWT(value) { @@ -96,12 +97,14 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } + @override ClientIO setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } + /// The user session to authenticate with @override ClientIO setSession(value) { @@ -109,6 +112,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } + /// Your secret dev API key @override ClientIO setDevKey(value) { @@ -116,6 +120,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } + /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientIO setCookie(value) { @@ -123,6 +128,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } + /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserId(value) { @@ -130,6 +136,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } + /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserEmail(value) { @@ -137,6 +144,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } + /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserPhone(value) { @@ -341,7 +349,8 @@ class ClientIO extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk(int index, int start, int end, String? id) async { + Future uploadChunk( + int index, int start, int end, String? id) async { List chunk = []; if (file.bytes != null) { chunk = file.bytes!.getRange(start, end).toList(); @@ -388,7 +397,9 @@ class ClientIO extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; + return chunksUploaded is num && + chunksTotal is num && + chunksUploaded >= chunksTotal; } final progress = UploadProgress( From 4c3f9e8fbf683a3160f4ffb799a1db306885e458 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 21 May 2026 20:45:37 +0400 Subject: [PATCH 05/10] feat: support concurrent chunk uploads --- lib/src/client_browser.dart | 21 +++------- lib/src/client_io.dart | 79 +++++++++++++++++++------------------ 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 2c562071..9cca30f6 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -63,7 +63,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } - /// Your secret JSON Web Token @override ClientBrowser setJWT(value) { @@ -71,14 +70,12 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } - @override ClientBrowser setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } - /// The user session to authenticate with @override ClientBrowser setSession(value) { @@ -86,7 +83,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } - /// Your secret dev API key @override ClientBrowser setDevKey(value) { @@ -94,7 +90,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } - /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientBrowser setCookie(value) { @@ -102,7 +97,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } - /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserId(value) { @@ -110,7 +104,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } - /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserEmail(value) { @@ -118,7 +111,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } - /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserPhone(value) { @@ -228,8 +220,7 @@ class ClientBrowser extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk( - int index, int start, int end, String? id) async { + Future uploadChunk(int index, int start, int end, String? id) async { List chunk = []; chunk = file.bytes!.getRange(start, end).toList(); @@ -262,13 +253,12 @@ class ClientBrowser extends ClientBase with ClientMixin { var completedChunks = firstIndex + 1; var uploadedBytes = firstEnd; var lastResponse = res; + Response? finalResponse; bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && - chunksTotal is num && - chunksUploaded >= chunksTotal; + return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; } final progress = UploadProgress( @@ -302,8 +292,9 @@ class ClientBrowser extends ClientBase with ClientMixin { ); completedChunks++; uploadedBytes += chunk['end']! - chunk['start']!; + lastResponse = chunkResponse; if (isUploadComplete(chunkResponse)) { - lastResponse = chunkResponse; + finalResponse = chunkResponse; } final progress = UploadProgress( @@ -320,7 +311,7 @@ class ClientBrowser extends ClientBase with ClientMixin { final concurrency = min(8, chunks.length); await Future.wait(List.generate(concurrency, (_) => uploadNext())); - return lastResponse; + return finalResponse ?? lastResponse; } @override diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index 95436bfd..9f1ae781 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -89,7 +89,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } - /// Your secret JSON Web Token @override ClientIO setJWT(value) { @@ -97,14 +96,12 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } - @override ClientIO setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } - /// The user session to authenticate with @override ClientIO setSession(value) { @@ -112,7 +109,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } - /// Your secret dev API key @override ClientIO setDevKey(value) { @@ -120,7 +116,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } - /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientIO setCookie(value) { @@ -128,7 +123,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } - /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserId(value) { @@ -136,7 +130,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } - /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserEmail(value) { @@ -144,7 +137,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } - /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserPhone(value) { @@ -349,18 +341,23 @@ class ClientIO extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk( - int index, int start, int end, String? id) async { + Future uploadChunk(int index, int start, int end, String? id, + [RandomAccessFile? raf]) async { List chunk = []; if (file.bytes != null) { chunk = file.bytes!.getRange(start, end).toList(); } else { - final raf = await iofile!.open(mode: FileMode.read); - try { + if (raf != null) { await raf.setPosition(start); chunk = await raf.read(end - start); - } finally { - await raf.close(); + } else { + final chunkFile = await iofile!.open(mode: FileMode.read); + try { + await chunkFile.setPosition(start); + chunk = await chunkFile.read(end - start); + } finally { + await chunkFile.close(); + } } } @@ -393,13 +390,12 @@ class ClientIO extends ClientBase with ClientMixin { var completedChunks = firstIndex + 1; var uploadedBytes = firstEnd; var lastResponse = res; + Response? finalResponse; bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && - chunksTotal is num && - chunksUploaded >= chunksTotal; + return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; } final progress = UploadProgress( @@ -423,35 +419,42 @@ class ClientIO extends ClientBase with ClientMixin { var nextChunk = 0; Future 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)) { + final raf = file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; + try { + while (nextChunk < chunks.length) { + final chunk = chunks[nextChunk++]; + final chunkResponse = await uploadChunk( + chunk['index']!, + chunk['start']!, + chunk['end']!, + uploadId, + raf, + ); + completedChunks++; + uploadedBytes += chunk['end']! - chunk['start']!; lastResponse = chunkResponse; + if (isUploadComplete(chunkResponse)) { + finalResponse = chunkResponse; + } + + 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: uploadId ?? '', - progress: min(uploadedBytes, size) / size * 100, - sizeUploaded: min(uploadedBytes, size), - chunksTotal: totalChunks, - chunksUploaded: completedChunks, - ); - onProgress?.call(progress); + } finally { + await raf?.close(); } } final concurrency = min(8, chunks.length); await Future.wait(List.generate(concurrency, (_) => uploadNext())); - return lastResponse; + return finalResponse ?? lastResponse; } bool get _customSchemeAllowed => Platform.isWindows || Platform.isLinux; From 1a3ed233c37d58b739dcbfd91e049c89b2171624 Mon Sep 17 00:00:00 2001 From: TorstenDittmann Date: Thu, 21 May 2026 16:47:03 +0000 Subject: [PATCH 06/10] Commit from GitHub Actions (Format and push) --- lib/src/client_browser.dart | 15 +++++++++++++-- lib/src/client_io.dart | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 9cca30f6..9d20bba8 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -63,6 +63,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } + /// Your secret JSON Web Token @override ClientBrowser setJWT(value) { @@ -70,12 +71,14 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } + @override ClientBrowser setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } + /// The user session to authenticate with @override ClientBrowser setSession(value) { @@ -83,6 +86,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } + /// Your secret dev API key @override ClientBrowser setDevKey(value) { @@ -90,6 +94,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } + /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientBrowser setCookie(value) { @@ -97,6 +102,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } + /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserId(value) { @@ -104,6 +110,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } + /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserEmail(value) { @@ -111,6 +118,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } + /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserPhone(value) { @@ -220,7 +228,8 @@ class ClientBrowser extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk(int index, int start, int end, String? id) async { + Future uploadChunk( + int index, int start, int end, String? id) async { List chunk = []; chunk = file.bytes!.getRange(start, end).toList(); @@ -258,7 +267,9 @@ class ClientBrowser extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; + return chunksUploaded is num && + chunksTotal is num && + chunksUploaded >= chunksTotal; } final progress = UploadProgress( diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index 9f1ae781..4d32c10c 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -89,6 +89,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } + /// Your secret JSON Web Token @override ClientIO setJWT(value) { @@ -96,12 +97,14 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } + @override ClientIO setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } + /// The user session to authenticate with @override ClientIO setSession(value) { @@ -109,6 +112,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } + /// Your secret dev API key @override ClientIO setDevKey(value) { @@ -116,6 +120,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } + /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientIO setCookie(value) { @@ -123,6 +128,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } + /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserId(value) { @@ -130,6 +136,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } + /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserEmail(value) { @@ -137,6 +144,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } + /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserPhone(value) { @@ -395,7 +403,9 @@ class ClientIO extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; + return chunksUploaded is num && + chunksTotal is num && + chunksUploaded >= chunksTotal; } final progress = UploadProgress( @@ -419,7 +429,8 @@ class ClientIO extends ClientBase with ClientMixin { var nextChunk = 0; Future uploadNext() async { - final raf = file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; + final raf = + file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; try { while (nextChunk < chunks.length) { final chunk = chunks[nextChunk++]; From 875b287a654d6f026d19258911190b3b724b94ed Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 21 May 2026 21:10:15 +0400 Subject: [PATCH 07/10] feat: support concurrent chunk uploads --- lib/src/client_browser.dart | 15 ++------------- lib/src/client_io.dart | 15 ++------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 9d20bba8..9cca30f6 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -63,7 +63,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } - /// Your secret JSON Web Token @override ClientBrowser setJWT(value) { @@ -71,14 +70,12 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } - @override ClientBrowser setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } - /// The user session to authenticate with @override ClientBrowser setSession(value) { @@ -86,7 +83,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } - /// Your secret dev API key @override ClientBrowser setDevKey(value) { @@ -94,7 +90,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } - /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientBrowser setCookie(value) { @@ -102,7 +97,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } - /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserId(value) { @@ -110,7 +104,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } - /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserEmail(value) { @@ -118,7 +111,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } - /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserPhone(value) { @@ -228,8 +220,7 @@ class ClientBrowser extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk( - int index, int start, int end, String? id) async { + Future uploadChunk(int index, int start, int end, String? id) async { List chunk = []; chunk = file.bytes!.getRange(start, end).toList(); @@ -267,9 +258,7 @@ class ClientBrowser extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && - chunksTotal is num && - chunksUploaded >= chunksTotal; + return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; } final progress = UploadProgress( diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index 4d32c10c..9f1ae781 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -89,7 +89,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } - /// Your secret JSON Web Token @override ClientIO setJWT(value) { @@ -97,14 +96,12 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } - @override ClientIO setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } - /// The user session to authenticate with @override ClientIO setSession(value) { @@ -112,7 +109,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } - /// Your secret dev API key @override ClientIO setDevKey(value) { @@ -120,7 +116,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } - /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientIO setCookie(value) { @@ -128,7 +123,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } - /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserId(value) { @@ -136,7 +130,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } - /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserEmail(value) { @@ -144,7 +137,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } - /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserPhone(value) { @@ -403,9 +395,7 @@ class ClientIO extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && - chunksTotal is num && - chunksUploaded >= chunksTotal; + return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; } final progress = UploadProgress( @@ -429,8 +419,7 @@ class ClientIO extends ClientBase with ClientMixin { var nextChunk = 0; Future uploadNext() async { - final raf = - file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; + final raf = file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; try { while (nextChunk < chunks.length) { final chunk = chunks[nextChunk++]; From 0e09f95b25c6f43c4f496d81401e8a8c9f04ecc6 Mon Sep 17 00:00:00 2001 From: TorstenDittmann Date: Thu, 21 May 2026 17:11:42 +0000 Subject: [PATCH 08/10] Commit from GitHub Actions (Format and push) --- lib/src/client_browser.dart | 15 +++++++++++++-- lib/src/client_io.dart | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 9cca30f6..9d20bba8 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -63,6 +63,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } + /// Your secret JSON Web Token @override ClientBrowser setJWT(value) { @@ -70,12 +71,14 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } + @override ClientBrowser setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } + /// The user session to authenticate with @override ClientBrowser setSession(value) { @@ -83,6 +86,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } + /// Your secret dev API key @override ClientBrowser setDevKey(value) { @@ -90,6 +94,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } + /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientBrowser setCookie(value) { @@ -97,6 +102,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } + /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserId(value) { @@ -104,6 +110,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } + /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserEmail(value) { @@ -111,6 +118,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } + /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserPhone(value) { @@ -220,7 +228,8 @@ class ClientBrowser extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk(int index, int start, int end, String? id) async { + Future uploadChunk( + int index, int start, int end, String? id) async { List chunk = []; chunk = file.bytes!.getRange(start, end).toList(); @@ -258,7 +267,9 @@ class ClientBrowser extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; + return chunksUploaded is num && + chunksTotal is num && + chunksUploaded >= chunksTotal; } final progress = UploadProgress( diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index 9f1ae781..4d32c10c 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -89,6 +89,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } + /// Your secret JSON Web Token @override ClientIO setJWT(value) { @@ -96,12 +97,14 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } + @override ClientIO setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } + /// The user session to authenticate with @override ClientIO setSession(value) { @@ -109,6 +112,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } + /// Your secret dev API key @override ClientIO setDevKey(value) { @@ -116,6 +120,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } + /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientIO setCookie(value) { @@ -123,6 +128,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } + /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserId(value) { @@ -130,6 +136,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } + /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserEmail(value) { @@ -137,6 +144,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } + /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserPhone(value) { @@ -395,7 +403,9 @@ class ClientIO extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; + return chunksUploaded is num && + chunksTotal is num && + chunksUploaded >= chunksTotal; } final progress = UploadProgress( @@ -419,7 +429,8 @@ class ClientIO extends ClientBase with ClientMixin { var nextChunk = 0; Future uploadNext() async { - final raf = file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; + final raf = + file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; try { while (nextChunk < chunks.length) { final chunk = chunks[nextChunk++]; From 84e02cefd7aebdb155d43c33c4cf489157c2fcdc Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 21 May 2026 21:32:41 +0400 Subject: [PATCH 09/10] feat: support concurrent chunk uploads --- lib/src/client_browser.dart | 15 ++------------- lib/src/client_io.dart | 15 ++------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 9d20bba8..9cca30f6 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -63,7 +63,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } - /// Your secret JSON Web Token @override ClientBrowser setJWT(value) { @@ -71,14 +70,12 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } - @override ClientBrowser setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } - /// The user session to authenticate with @override ClientBrowser setSession(value) { @@ -86,7 +83,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } - /// Your secret dev API key @override ClientBrowser setDevKey(value) { @@ -94,7 +90,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } - /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientBrowser setCookie(value) { @@ -102,7 +97,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } - /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserId(value) { @@ -110,7 +104,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } - /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserEmail(value) { @@ -118,7 +111,6 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } - /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserPhone(value) { @@ -228,8 +220,7 @@ class ClientBrowser extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk( - int index, int start, int end, String? id) async { + Future uploadChunk(int index, int start, int end, String? id) async { List chunk = []; chunk = file.bytes!.getRange(start, end).toList(); @@ -267,9 +258,7 @@ class ClientBrowser extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && - chunksTotal is num && - chunksUploaded >= chunksTotal; + return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; } final progress = UploadProgress( diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index 4d32c10c..9f1ae781 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -89,7 +89,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } - /// Your secret JSON Web Token @override ClientIO setJWT(value) { @@ -97,14 +96,12 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } - @override ClientIO setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } - /// The user session to authenticate with @override ClientIO setSession(value) { @@ -112,7 +109,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } - /// Your secret dev API key @override ClientIO setDevKey(value) { @@ -120,7 +116,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } - /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientIO setCookie(value) { @@ -128,7 +123,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } - /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserId(value) { @@ -136,7 +130,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } - /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserEmail(value) { @@ -144,7 +137,6 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } - /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserPhone(value) { @@ -403,9 +395,7 @@ class ClientIO extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && - chunksTotal is num && - chunksUploaded >= chunksTotal; + return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; } final progress = UploadProgress( @@ -429,8 +419,7 @@ class ClientIO extends ClientBase with ClientMixin { var nextChunk = 0; Future uploadNext() async { - final raf = - file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; + final raf = file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; try { while (nextChunk < chunks.length) { final chunk = chunks[nextChunk++]; From a0cf2838276e8b9dfc86355ecfae95a57da06066 Mon Sep 17 00:00:00 2001 From: TorstenDittmann Date: Thu, 21 May 2026 17:34:12 +0000 Subject: [PATCH 10/10] Commit from GitHub Actions (Format and push) --- lib/src/client_browser.dart | 15 +++++++++++++-- lib/src/client_io.dart | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 9cca30f6..9d20bba8 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -63,6 +63,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } + /// Your secret JSON Web Token @override ClientBrowser setJWT(value) { @@ -70,12 +71,14 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } + @override ClientBrowser setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } + /// The user session to authenticate with @override ClientBrowser setSession(value) { @@ -83,6 +86,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } + /// Your secret dev API key @override ClientBrowser setDevKey(value) { @@ -90,6 +94,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } + /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientBrowser setCookie(value) { @@ -97,6 +102,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } + /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserId(value) { @@ -104,6 +110,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } + /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserEmail(value) { @@ -111,6 +118,7 @@ class ClientBrowser extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } + /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientBrowser setImpersonateUserPhone(value) { @@ -220,7 +228,8 @@ class ClientBrowser extends ClientBase with ClientMixin { final totalChunks = (size / chunkSize).ceil(); - Future uploadChunk(int index, int start, int end, String? id) async { + Future uploadChunk( + int index, int start, int end, String? id) async { List chunk = []; chunk = file.bytes!.getRange(start, end).toList(); @@ -258,7 +267,9 @@ class ClientBrowser extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; + return chunksUploaded is num && + chunksTotal is num && + chunksUploaded >= chunksTotal; } final progress = UploadProgress( diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index 9f1ae781..4d32c10c 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -89,6 +89,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Project', value); return this; } + /// Your secret JSON Web Token @override ClientIO setJWT(value) { @@ -96,12 +97,14 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-JWT', value); return this; } + @override ClientIO setLocale(value) { config['locale'] = value; addHeader('X-Appwrite-Locale', value); return this; } + /// The user session to authenticate with @override ClientIO setSession(value) { @@ -109,6 +112,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Session', value); return this; } + /// Your secret dev API key @override ClientIO setDevKey(value) { @@ -116,6 +120,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Dev-Key', value); return this; } + /// The user cookie to authenticate with. Used by SDKs that forward an incoming Cookie header in server-side runtimes. @override ClientIO setCookie(value) { @@ -123,6 +128,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('Cookie', value); return this; } + /// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserId(value) { @@ -130,6 +136,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Id', value); return this; } + /// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserEmail(value) { @@ -137,6 +144,7 @@ class ClientIO extends ClientBase with ClientMixin { addHeader('X-Appwrite-Impersonate-User-Email', value); return this; } + /// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data. @override ClientIO setImpersonateUserPhone(value) { @@ -395,7 +403,9 @@ class ClientIO extends ClientBase with ClientMixin { bool isUploadComplete(Response response) { final chunksUploaded = response.data['chunksUploaded']; final chunksTotal = response.data['chunksTotal'] ?? totalChunks; - return chunksUploaded is num && chunksTotal is num && chunksUploaded >= chunksTotal; + return chunksUploaded is num && + chunksTotal is num && + chunksUploaded >= chunksTotal; } final progress = UploadProgress( @@ -419,7 +429,8 @@ class ClientIO extends ClientBase with ClientMixin { var nextChunk = 0; Future uploadNext() async { - final raf = file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; + final raf = + file.bytes == null ? await iofile!.open(mode: FileMode.read) : null; try { while (nextChunk < chunks.length) { final chunk = chunks[nextChunk++];