Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
980fb86
chunked upload
lohanidamodar Nov 1, 2021
a51c784
fixes
lohanidamodar Nov 1, 2021
a6b256f
fix chunked upload issues
lohanidamodar Nov 2, 2021
bbac397
chunked upload test
lohanidamodar Nov 2, 2021
877a03c
refactor test to better check expected outputs
lohanidamodar Nov 2, 2021
a6beb01
fix whitelist override
lohanidamodar Nov 2, 2021
6cc247b
fix count assertions
lohanidamodar Nov 2, 2021
2608929
Merge branch 'feat-dart-response-model' into feat-large-file-dart
lohanidamodar Nov 2, 2021
1391d8e
fix test
lohanidamodar Nov 2, 2021
3228ddc
chunked upload support on dart
lohanidamodar Nov 3, 2021
7160706
add filename parameter
lohanidamodar Nov 3, 2021
c64372b
update test
lohanidamodar Nov 4, 2021
cda5515
Merge branch 'master' into feat-large-file-dart
lohanidamodar Nov 4, 2021
d194d40
fix parameter
lohanidamodar Nov 4, 2021
745424f
fix missing chunk size
lohanidamodar Nov 4, 2021
e5de8eb
fix dart test
lohanidamodar Nov 4, 2021
b1f5001
Merge branch 'master' into feat-large-file-dart
lohanidamodar Nov 4, 2021
d5d78c6
Merge branch 'master' into feat-large-file-dart
lohanidamodar Nov 19, 2021
e9f770c
fixes and update with suggestions
lohanidamodar Nov 22, 2021
593eed5
Merge branch 'master' into feat-large-file-dart
lohanidamodar Jan 10, 2022
ee28ef2
proper spacing
lohanidamodar Jan 10, 2022
54713bd
progress callback for large file uploads
lohanidamodar Jan 10, 2022
1daf7c0
fix tests
lohanidamodar Jan 11, 2022
a102fff
set project for realtime
lohanidamodar Jan 11, 2022
59b0dc4
fix progress
lohanidamodar Jan 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/SDK/Language/Dart.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public function getTypeName($type)
return 'String';
break;
case self::TYPE_FILE:
return 'http.MultipartFile';
return 'InputFile';
break;
case self::TYPE_BOOLEAN:
return 'bool';
Expand Down Expand Up @@ -408,6 +408,24 @@ public function getFiles()
'template' => 'dart/.travis.yml.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => 'lib/src/input_file.dart',
'template' => 'dart/lib/src/input_file.dart.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => 'lib/src/chunked_upload_io.dart',
'template' => 'dart/lib/src/chunked_upload_io.dart.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => 'lib/src/chunked_upload_stub.dart',
'template' => 'dart/lib/src/chunked_upload_stub.dart.twig',
'minify' => false,
],
];
}
}
18 changes: 18 additions & 0 deletions src/SDK/Language/Flutter.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,24 @@ public function getFiles()
'template' => 'flutter/.travis.yml.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => 'lib/src/input_file.dart',
'template' => 'flutter/lib/src/input_file.dart.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => 'lib/src/chunked_upload_io.dart',
'template' => 'flutter/lib/src/chunked_upload_io.dart.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => 'lib/src/chunked_upload_stub.dart',
'template' => 'flutter/lib/src/chunked_upload_stub.dart.twig',
'minify' => false,
],
];
}
}
5 changes: 4 additions & 1 deletion templates/dart/lib/package.dart.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ library {{ language.params.packageName }};

import 'dart:async';
import 'dart:typed_data';
import 'package:http/http.dart' as http;
import 'src/chunked_upload_stub.dart'
if (dart.library.io) 'src/chunked_upload_io.dart';
import 'src/enums.dart';
import 'src/client.dart';
import 'src/service.dart';
import 'src/input_file.dart';
import 'models.dart' as models;

export 'src/response.dart';
export 'src/client.dart';
export 'src/exception.dart';
export 'src/input_file.dart';
export 'package:http/http.dart' show MultipartFile;

part 'query.dart';
Expand Down
35 changes: 31 additions & 4 deletions templates/dart/lib/services/service.dart.twig
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
part of {{ language.params.packageName }};
{% macro parameter(parameter) %}
{% if parameter.required %}required {{ parameter.type | typeName }}{% else %}{{ parameter.type | typeName }}?{% endif %} {{ parameter.name | caseCamel | escapeKeyword }}{% endmacro %}
{% macro method_parameters(parameters) %}
{% if parameters.all|length > 0 %}{{ '{' }}{% for parameter in parameters.all %}{{ _self.parameter(parameter) }}{% if not loop.last %}, {% endif %}{% endfor %}{{ '}' }}{% endif %}
{% macro method_parameters(parameters, consumes) %}
{% if parameters.all|length > 0 %}{{ '{' }}{% for parameter in parameters.all %}{{ _self.parameter(parameter) }}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in consumes %}, Function(double)? onProgress{% endif %}{{ '}' }}{% endif %}
{% endmacro %}
{% macro map_parameter(parameter) %}'{{ parameter.name }}': {{ parameter.name | caseCamel | escapeKeyword }},{% endmacro %}

Expand All @@ -13,10 +13,10 @@ class {{ service.name | caseUcfirst }} extends Service {
/// {{ method.title }}
{% if method.description %}
///
{{ method.description|dartComment }}
{{ method.description | dartComment }}
///
{% endif %}
{% if method.type == 'webAuth' %}Future{% elseif method.type == 'location' %} Future<Uint8List> {% else %} {% if method.responseModel and method.responseModel != 'any' %}Future<models.{{method.responseModel | caseUcfirst | overrideIdentifier}}>{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters) }}) async {
{% if method.type == 'webAuth' %}Future{% elseif method.type == 'location' %} Future<Uint8List> {% else %} {% if method.responseModel and method.responseModel != 'any' %}Future<models.{{method.responseModel | caseUcfirst | overrideIdentifier}}>{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters, method.consumes) }}) async {
final String path = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};

final Map<String, dynamic> params = {
Expand Down Expand Up @@ -51,7 +51,34 @@ class {{ service.name | caseUcfirst }} extends Service {
{% endfor %}
};


{% if 'multipart/form-data' in method.consumes %}
dynamic res;
if(identical(0, 0.0)) {
{% for parameter in method.parameters.all %}
{% if parameter.type == 'file' %}
params['{{ parameter.name }}'] = {{ parameter.name }}.file;
{% endif %}
{% endfor %}
res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, headers: headers);
} else {
{% for parameter in method.parameters.all %}
{% if parameter.type == 'file' %}
final paramName = '{{ parameter.name }}';
{% endif %}
{% endfor %}
res = await chunkedUpload(
client: client,
path: path,
params: params,
paramName: paramName,
headers: headers,
onProgress: onProgress,
);
}
{% else %}
final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, headers: headers);
{% endif %}
return {% if method.responseModel and method.responseModel != 'any' %}models.{{method.responseModel | caseUcfirst | overrideIdentifier}}.fromMap(res.data){% else %} res.data{% endif %};
{% endif %}
}
Expand Down
54 changes: 54 additions & 0 deletions templates/dart/lib/src/chunked_upload_io.dart.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'client.dart';
import 'dart:io' as io;
import 'enums.dart';
import 'package:http/http.dart' as http;
import 'response.dart';
import 'dart:math';
import 'input_file.dart';
import 'exception.dart';

Future<Response> chunkedUpload({
required Client client,
required String path,
required Map<String, dynamic> params,
required String paramName,
required Map<String, String> headers,
Function(double)? onProgress,
}) async {
InputFile file = params[paramName];
if (file.path == null) {
throw AppwriteException("File path must be provided for dart:io");
}
io.File iofile = io.File(file.path!);
final size = await iofile.length();

late Response res;
if (size <= Client.CHUNK_SIZE) {
params[paramName] = await http.MultipartFile.fromPath(paramName, file.path!, filename: file.fileName);
return client.call(
HttpMethod.post,
path: path,
params: params,
headers: headers,
);
}
// read chunk and upload each chunk
var offset = 0;
final raf = iofile.openSync(mode: io.FileMode.read);

while (offset < size) {
final chunk = raf.readSync(Client.CHUNK_SIZE);
params[paramName] =
http.MultipartFile.fromBytes(paramName, chunk, filename: iofile.path);
headers['content-range'] =
'bytes $offset-${min<int>(((offset + Client.CHUNK_SIZE) - 1), size)}/$size';
res = await client.call(HttpMethod.post,
path: path, headers: headers, params: params);
offset += Client.CHUNK_SIZE;
if(offset < size) {
headers['x-appwrite-id'] = res.data['\$id'];
}
onProgress?.call(min(offset,size)/size * 100);
}
return res;
}
12 changes: 12 additions & 0 deletions templates/dart/lib/src/chunked_upload_stub.dart.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'response.dart';
import 'client.dart';

Future<Response> chunkedUpload({
required Client client,
required String path,
required Map<String, dynamic> params,
required String paramName,
required Map<String, String> headers,
Function(double)? onProgress
}) =>
throw UnsupportedError('Cannot redirect to url without dart:html');
1 change: 1 addition & 0 deletions templates/dart/lib/src/client.dart.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'client_stub.dart'
import 'response.dart';

abstract class Client {
static const int CHUNK_SIZE = 5*1024*1024;
late Map<String, String> config;
late String _endPoint;

Expand Down
9 changes: 9 additions & 0 deletions templates/dart/lib/src/input_file.dart.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:http/http.dart' show MultipartFile;

class InputFile {
final MultipartFile? file;
final String? path;
final String? fileName;

InputFile({this.file, this.path, this.fileName});
}
5 changes: 4 additions & 1 deletion templates/flutter/lib/package.dart.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ library {{ language.params.packageName }};

import 'dart:async';
import 'dart:typed_data';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
import 'src/redirect_stub.dart'
if (dart.library.html) 'src/redirect_browser.dart';
import 'src/chunked_upload_stub.dart'
if (dart.library.io) 'src/chunked_upload_io.dart';
import 'src/enums.dart';
import 'src/client.dart';
import 'src/service.dart';
import 'src/input_file.dart';
import 'models.dart' as models;

export 'src/response.dart';
Expand All @@ -17,6 +19,7 @@ export 'src/exception.dart';
export 'src/realtime.dart';
export 'src/realtime_subscription.dart';
export 'src/realtime_message.dart';
export 'src/input_file.dart';
export 'package:http/http.dart' show MultipartFile;

part 'query.dart';
Expand Down
32 changes: 29 additions & 3 deletions templates/flutter/lib/services/service.dart.twig
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
part of {{ language.params.packageName }};

{% macro parameter(parameter) %}{% if parameter.required %}required {{ parameter.type | typeName }}{% else %}{{ parameter.type | typeName }}?{% endif %} {{ parameter.name | caseCamel | escapeKeyword }}{% endmacro %}
{% macro method_parameters(parameters) %}
{% if parameters.all|length > 0 %}{{ '{' }}{% for parameter in parameters.all %}{{ _self.parameter(parameter) }}{% if not loop.last %}, {% endif %}{% endfor %}{{ '}' }}{% endif %}
{% macro method_parameters(parameters, consumes) %}
{% if parameters.all|length > 0 %}{{ '{' }}{% for parameter in parameters.all %}{{ _self.parameter(parameter) }}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in consumes %}, Function(double)? onProgress{% endif %}{{ '}' }}{% endif %}
{% endmacro %}
{% macro map_parameter(parameter) %}'{{ parameter.name }}': {{ parameter.name | caseCamel | escapeKeyword }},{% endmacro %}
class {{ service.name | caseUcfirst }} extends Service {
Expand All @@ -15,7 +15,7 @@ class {{ service.name | caseUcfirst }} extends Service {
{{ method.description|dartComment }}
///
{% endif %}
{% if method.type == 'webAuth' %}Future{% elseif method.type == 'location' %} Future<Uint8List> {% else %} {% if method.responseModel and method.responseModel != 'any' %}Future<models.{{method.responseModel | caseUcfirst}}>{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters) }}) async {
{% if method.type == 'webAuth' %}Future{% elseif method.type == 'location' %} Future<Uint8List> {% else %} {% if method.responseModel and method.responseModel != 'any' %}Future<models.{{method.responseModel | caseUcfirst}}>{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters, method.consumes) }}) async {
final String path = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | escapeKeyword }}){% endfor %};

final Map<String, dynamic> params = {
Expand Down Expand Up @@ -74,7 +74,33 @@ class {{ service.name | caseUcfirst }} extends Service {
{% endfor %}
};

{% if 'multipart/form-data' in method.consumes %}
dynamic res;
if(kIsWeb) {
{% for parameter in method.parameters.all %}
{% if parameter.type == 'file' %}
params['{{ parameter.name }}'] = {{ parameter.name }}.file;
{% endif %}
{% endfor %}
res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, headers: headers);
} else {
{% for parameter in method.parameters.all %}
{% if parameter.type == 'file' %}
final paramName = '{{ parameter.name }}';
{% endif %}
{% endfor %}
res = await chunkedUpload(
client: client,
path: path,
params: params,
paramName: paramName,
headers: headers,
onProgress: onProgress,
);
}
{% else %}
final res = await client.call(HttpMethod.{{ method.method | caseLower }}, path: path, params: params, headers: headers);
{% endif %}
return {% if method.responseModel and method.responseModel != 'any' %}models.{{method.responseModel | caseUcfirst}}.fromMap(res.data){% else %} res.data{% endif %};
{% endif %}
}
Expand Down
54 changes: 54 additions & 0 deletions templates/flutter/lib/src/chunked_upload_io.dart.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'client.dart';
import 'dart:io' as io;
import 'enums.dart';
import 'package:http/http.dart' as http;
import 'response.dart';
import 'dart:math';
import 'input_file.dart';
import 'exception.dart';

Future<Response> chunkedUpload({
required Client client,
required String path,
required Map<String, dynamic> params,
required String paramName,
required Map<String, String> headers,
Function(double)? onProgress,
}) async {
InputFile file = params[paramName];
if (file.path == null) {
throw AppwriteException("File path must be provided for dart:io");
}
io.File iofile = io.File(file.path!);
final size = await iofile.length();

late Response res;
if (size <= Client.CHUNK_SIZE) {
params[paramName] = await http.MultipartFile.fromPath(paramName, file.path!, filename: file.fileName);
return client.call(
HttpMethod.post,
path: path,
params: params,
headers: headers,
);
}
// read chunk and upload each chunk
var offset = 0;
final raf = iofile.openSync(mode: io.FileMode.read);

while (offset < size) {
final chunk = raf.readSync(Client.CHUNK_SIZE);
params[paramName] =
http.MultipartFile.fromBytes(paramName, chunk, filename: iofile.path);
headers['content-range'] =
'bytes $offset-${min<int>(((offset + Client.CHUNK_SIZE) - 1), size)}/$size';
res = await client.call(HttpMethod.post,
path: path, headers: headers, params: params);
offset += Client.CHUNK_SIZE;
if(offset < size) {
headers['x-appwrite-id'] = res.data['\$id'];
}
onProgress?.call(min(offset,size)/size * 100);
}
return res;
}
12 changes: 12 additions & 0 deletions templates/flutter/lib/src/chunked_upload_stub.dart.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'response.dart';
import 'client.dart';

Future<Response> chunkedUpload({
required Client client,
required String path,
required Map<String, dynamic> params,
required String paramName,
required Map<String, String> headers,
Function(double)? onProgress,
}) =>
throw UnsupportedError('Cannot redirect to url without dart:html');
1 change: 1 addition & 0 deletions templates/flutter/lib/src/client.dart.twig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'client_stub.dart'
import 'response.dart';

abstract class Client {
static const int CHUNK_SIZE = 5*1024*1024;
late Map<String, String> config;
late String _endPoint;
late String? _endPointRealtime;
Expand Down
9 changes: 9 additions & 0 deletions templates/flutter/lib/src/input_file.dart.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:http/http.dart' show MultipartFile;

class InputFile {
final MultipartFile? file;
final String? path;
final String? fileName;

InputFile({this.file, this.path, this.fileName});
}
Loading