diff --git a/base/.pubignore b/base/.pubignore new file mode 100644 index 00000000..34c62e31 --- /dev/null +++ b/base/.pubignore @@ -0,0 +1 @@ +pubspec.lock \ No newline at end of file diff --git a/base/CHANGELOG.md b/base/CHANGELOG.md index 6536f611..04a866d1 100644 --- a/base/CHANGELOG.md +++ b/base/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.5.0 + +* upgrade dio to 5.0.0 + +## 0.4.1 + +- fix package file name + ## 0.4.0 - 新增 putBytes 接口用于上传 Uint8List 类型的资源 diff --git a/base/analysis_options.yaml b/base/analysis_options.yaml index 633c1d0f..ad8e012f 100644 --- a/base/analysis_options.yaml +++ b/base/analysis_options.yaml @@ -1,6 +1,4 @@ -# copy from https://github.com/dart-lang/http/blob/master/analysis_options.yaml - -include: package:pedantic/analysis_options.yaml +include: package:lints/recommended.yaml analyzer: # enable-experiment: @@ -12,78 +10,8 @@ analyzer: linter: rules: - - annotate_overrides - - avoid_bool_literals_in_conditional_expressions - - avoid_classes_with_only_static_members - - avoid_empty_else - - avoid_function_literals_in_foreach_calls - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_relative_lib_imports - - avoid_renaming_method_parameters - - avoid_return_types_on_setters - - avoid_returning_null_for_void - - avoid_returning_this - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_types_as_parameter_names - - avoid_unused_constructor_parameters - - await_only_futures - - camel_case_types - - cascade_invocations - # comment_references - - control_flow_in_finally - - curly_braces_in_flow_control_structures - - directives_ordering - - empty_catches - - empty_constructor_bodies - - empty_statements - - file_names - - hash_and_equals - - iterable_contains_unrelated_type - - library_names - - library_prefixes - - list_remove_unrelated_type - - no_adjacent_strings_in_list - - no_duplicate_case_values - - non_constant_identifier_names - - null_closures - - omit_local_variable_types - - only_throw_errors - - overridden_fields - - package_names - - package_prefixed_library_names - - prefer_adjacent_string_concatenation - - prefer_conditional_assignment - - prefer_contains - - prefer_equal_for_default_values - - prefer_final_fields - - prefer_collection_literals - - prefer_generic_function_type_aliases - - prefer_initializing_formals - - prefer_is_empty - - prefer_is_not_empty - - prefer_null_aware_operators - - prefer_single_quotes - - prefer_typing_uninitialized_variables - - recursive_getters - - slash_for_doc_comments - - test_types_in_equals - - throw_in_finally - - type_init_formals - - unawaited_futures - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_overrides - - unnecessary_parenthesis - - unnecessary_statements - - unnecessary_this - - unrelated_type_equality_checks - - use_rethrow_when_possible - - valid_regexps - - void_checks + constant_identifier_names: false + prefer_final_locals: true + prefer_single_quotes: true + always_declare_return_types: true + require_trailing_commas: true diff --git a/base/lib/src/auth/auth.dart b/base/lib/src/auth/auth.dart index d86b2494..4ad5c964 100644 --- a/base/lib/src/auth/auth.dart +++ b/base/lib/src/auth/auth.dart @@ -42,9 +42,9 @@ class Auth { String generateUploadToken({ required PutPolicy putPolicy, }) { - var data = jsonEncode(putPolicy); - var encodedPutPolicy = base64Url.encode(utf8.encode(data)); - var baseToken = generateAccessToken(bytes: utf8.encode(encodedPutPolicy)); + final data = jsonEncode(putPolicy); + final encodedPutPolicy = base64Url.encode(utf8.encode(data)); + final baseToken = generateAccessToken(bytes: utf8.encode(encodedPutPolicy)); return '$baseToken:$encodedPutPolicy'; } @@ -58,7 +58,7 @@ class Auth { required int deadline, required String bucketDomain, }) { - var downloadURL = '$bucketDomain/$key?e=$deadline'; + final downloadURL = '$bucketDomain/$key?e=$deadline'; return generateAccessToken(bytes: utf8.encode(downloadURL)); } @@ -66,10 +66,10 @@ class Auth { /// /// 访问七牛的接口需要对请求进行签名, 该方法提供 Token 签发服务 String generateAccessToken({required List bytes}) { - var hmacEncoder = Hmac(sha1, utf8.encode(secretKey)); + final hmacEncoder = Hmac(sha1, utf8.encode(secretKey)); - var sign = hmacEncoder.convert(bytes); - var encodedSign = base64Url.encode(sign.bytes); + final sign = hmacEncoder.convert(bytes); + final encodedSign = base64Url.encode(sign.bytes); return '$accessKey:$encodedSign'; } @@ -79,13 +79,13 @@ class Auth { static TokenInfo parseToken(String token) { assert(token != ''); - var segments = token.split(':'); + final segments = token.split(':'); if (segments.length < 2) { throw ArgumentError('invalid token'); } PutPolicy? putPolicy; - var accessKey = segments.first; + final accessKey = segments.first; /// 具体的 token 信息可以参考这里。 /// [内部文档](https://github.com/qbox/product/blob/master/kodo/auths/UpToken.md#admin-uptoken-authorization) @@ -94,13 +94,15 @@ class Auth { throw ArgumentError('invalid token'); } - putPolicy = PutPolicy.fromJson(jsonDecode( - String.fromCharCodes( - base64Url.decode( - segments.last, + putPolicy = PutPolicy.fromJson( + jsonDecode( + String.fromCharCodes( + base64Url.decode( + segments.last, + ), ), - ), - ) as Map); + ) as Map, + ); } return TokenInfo(accessKey, putPolicy); diff --git a/base/lib/src/storage/config/config.dart b/base/lib/src/storage/config/config.dart index bad100d6..2716bfdb 100644 --- a/base/lib/src/storage/config/config.dart +++ b/base/lib/src/storage/config/config.dart @@ -1,9 +1,6 @@ import 'package:dio/dio.dart'; -import 'package:qiniu_sdk_base/src/storage/error/error.dart'; - -import 'http_adaptor/native_adaptor.dart' - if (dart.library.html) 'http_adaptor/browser_adaptor.dart'; +import '../../storage/error/error.dart'; part 'cache.dart'; part 'host.dart'; @@ -26,5 +23,5 @@ class Config { this.retryLimit = 3, }) : hostProvider = hostProvider ?? DefaultHostProvider(), cacheProvider = cacheProvider ?? DefaultCacheProvider(), - httpClientAdapter = httpClientAdapter ?? createHttpAdaptor(); + httpClientAdapter = httpClientAdapter ?? HttpClientAdapter(); } diff --git a/base/lib/src/storage/config/host.dart b/base/lib/src/storage/config/host.dart index bb04da80..1cdbb01a 100644 --- a/base/lib/src/storage/config/host.dart +++ b/base/lib/src/storage/config/host.dart @@ -30,9 +30,9 @@ class DefaultHostProvider extends HostProvider { // 解冻需要被解冻的 host _frozenUpDomains.removeWhere((domain) => !domain.isFrozen()); - var _upDomains = <_Domain>[]; + final upDomains = <_Domain>[]; if ('$accessKey:$bucket' == _cacheKey && _stashedUpDomains.isNotEmpty) { - _upDomains.addAll(_stashedUpDomains); + upDomains.addAll(_stashedUpDomains); } else { final url = '$protocol://api.qiniu.com/v4/query?ak=$accessKey&bucket=$bucket'; @@ -47,21 +47,21 @@ class DefaultHostProvider extends HostProvider { for (var host in hosts) { final domainList = host.up['domains'].cast() as List; final domains = domainList.map((domain) => _Domain(domain)); - _upDomains.addAll(domains); + upDomains.addAll(domains); } _cacheKey = '$accessKey:$bucket'; - _stashedUpDomains.addAll(_upDomains); + _stashedUpDomains.addAll(upDomains); } // 每次都从头遍历一遍,最合适的 host 总是会排在最前面 - for (var index = 0; index < _upDomains.length; index++) { - final availableDomain = _upDomains.elementAt(index); + for (var index = 0; index < upDomains.length; index++) { + final availableDomain = upDomains.elementAt(index); // 检查看起来可用的 host 是否之前被冻结过 - final frozen = isFrozen(protocol + '://' + availableDomain.value); + final frozen = isFrozen('$protocol://${availableDomain.value}'); if (!frozen) { - return protocol + '://' + availableDomain.value; + return '$protocol://${availableDomain.value}'; } } // 全部被冻结,几乎不存在的情况 diff --git a/base/lib/src/storage/config/http_adaptor/browser_adaptor.dart b/base/lib/src/storage/config/http_adaptor/browser_adaptor.dart deleted file mode 100644 index 19766790..00000000 --- a/base/lib/src/storage/config/http_adaptor/browser_adaptor.dart +++ /dev/null @@ -1,4 +0,0 @@ -import 'package:dio/adapter_browser.dart'; -import 'package:dio/dio.dart'; - -HttpClientAdapter createHttpAdaptor() => BrowserHttpClientAdapter(); diff --git a/base/lib/src/storage/config/http_adaptor/native_adaptor.dart b/base/lib/src/storage/config/http_adaptor/native_adaptor.dart deleted file mode 100644 index 18056160..00000000 --- a/base/lib/src/storage/config/http_adaptor/native_adaptor.dart +++ /dev/null @@ -1,4 +0,0 @@ -import 'package:dio/adapter.dart'; -import 'package:dio/dio.dart'; - -HttpClientAdapter createHttpAdaptor() => DefaultHttpClientAdapter(); diff --git a/base/lib/src/storage/error/error.dart b/base/lib/src/storage/error/error.dart index 2b9986d7..4f288d97 100644 --- a/base/lib/src/storage/error/error.dart +++ b/base/lib/src/storage/error/error.dart @@ -1,5 +1,6 @@ import 'package:dio/dio.dart'; -import 'package:qiniu_sdk_base/src/error/error.dart'; + +import '../../error/error.dart'; enum StorageErrorType { /// 连接超时 @@ -41,9 +42,10 @@ class StorageError extends QiniuError { factory StorageError.fromError(Error error) { return StorageError( - type: StorageErrorType.UNKNOWN, - rawError: error, - message: error.toString()); + type: StorageErrorType.UNKNOWN, + rawError: error, + message: error.toString(), + ); } factory StorageError.fromDioError(DioError error) { @@ -65,17 +67,17 @@ class StorageError extends QiniuError { StorageErrorType _mapDioErrorType(DioErrorType type) { switch (type) { - case DioErrorType.connectTimeout: + case DioErrorType.connectionTimeout: return StorageErrorType.CONNECT_TIMEOUT; case DioErrorType.sendTimeout: return StorageErrorType.SEND_TIMEOUT; case DioErrorType.receiveTimeout: return StorageErrorType.RECEIVE_TIMEOUT; - case DioErrorType.response: + case DioErrorType.badResponse: return StorageErrorType.RESPONSE; case DioErrorType.cancel: return StorageErrorType.CANCEL; - case DioErrorType.other: + case DioErrorType.unknown: default: return StorageErrorType.UNKNOWN; } diff --git a/base/lib/src/storage/methods/put/by_part/complete_parts_task.dart b/base/lib/src/storage/methods/put/by_part/complete_parts_task.dart index 465e4ce3..e72682bd 100644 --- a/base/lib/src/storage/methods/put/by_part/complete_parts_task.dart +++ b/base/lib/src/storage/methods/put/by_part/complete_parts_task.dart @@ -53,7 +53,9 @@ class CompletePartsTask extends RequestTask { final response = await client.post>( paramUrl, data: data, - options: Options(headers: headers), + options: Options( + headers: headers, + ), ); return PutResponse.fromJson(response.data!); diff --git a/base/lib/src/storage/methods/put/by_part/init_parts_task.dart b/base/lib/src/storage/methods/put/by_part/init_parts_task.dart index acd69731..3ab42e1c 100644 --- a/base/lib/src/storage/methods/put/by_part/init_parts_task.dart +++ b/base/lib/src/storage/methods/put/by_part/init_parts_task.dart @@ -64,7 +64,8 @@ class InitPartsTask extends RequestTask with CacheMixin { final initPartsCache = await getCache(); if (initPartsCache != null) { return InitParts.fromJson( - json.decode(initPartsCache) as Map); + json.decode(initPartsCache) as Map, + ); } final bucket = _tokenInfo.putPolicy.getBucket(); @@ -82,7 +83,9 @@ class InitPartsTask extends RequestTask with CacheMixin { /// 这里 data 不传,dio 不会触发 cancel 事件 data: {}, - options: Options(headers: headers), + options: Options( + headers: headers, + ), ); return InitParts.fromJson(response.data!); diff --git a/base/lib/src/storage/methods/put/by_part/put_by_part_options.dart b/base/lib/src/storage/methods/put/by_part/put_by_part_options.dart deleted file mode 100644 index 2c105e8d..00000000 --- a/base/lib/src/storage/methods/put/by_part/put_by_part_options.dart +++ /dev/null @@ -1,30 +0,0 @@ -import '../put_controller.dart'; - -@Deprecated('use PutOptions') -class PutByPartOptions { - /// 资源名 - /// 如果不传则后端自动生成 - final String? key; - - /// 切片大小,单位 MB - /// - /// 超出 [partSize] 的文件大小会把每片按照 [partSize] 的大小切片并上传 - /// 默认 4MB,最小不得小于 1MB,最大不得大于 1024 MB - final int partSize; - - final int maxPartsRequestNumber; - - /// 自定义变量,key 必须以 x: 开始 - final Map? customVars; - - /// 控制器 - final PutController? controller; - - const PutByPartOptions({ - this.key, - this.partSize = 4, - this.maxPartsRequestNumber = 5, - this.customVars, - this.controller, - }); -} diff --git a/base/lib/src/storage/methods/put/by_part/put_parts_task.dart b/base/lib/src/storage/methods/put/by_part/put_parts_task.dart index a3140e2c..8d4d0d85 100644 --- a/base/lib/src/storage/methods/put/by_part/put_parts_task.dart +++ b/base/lib/src/storage/methods/put/by_part/put_parts_task.dart @@ -3,8 +3,9 @@ import 'dart:convert'; import 'dart:math'; import 'package:dio/dio.dart'; -import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; -import 'package:qiniu_sdk_base/src/storage/resource/resource.dart'; + +import '../../../../../qiniu_sdk_base.dart'; +import '../../../resource/resource.dart'; part 'cache_mixin.dart'; part 'complete_parts_task.dart'; @@ -124,22 +125,22 @@ class PutByPartTask extends RequestTask { /// 初始化上传信息,分片上传的第一步 InitPartsTask _createInitParts() { - final _controller = PutController(); + final controller = PutController(); final task = InitPartsTask( resource: resource, token: token, key: options.key, - controller: _controller, + controller: controller, ); manager.addTask(task); - _currentWorkingTaskController = _controller; + _currentWorkingTaskController = controller; return task; } UploadPartsTask _createUploadParts(String uploadId) { - final _controller = PutController(); + final controller = PutController(); final task = UploadPartsTask( token: token, @@ -147,13 +148,13 @@ class PutByPartTask extends RequestTask { uploadId: uploadId, maxPartsRequestNumber: options.maxPartsRequestNumber, resource: resource, - controller: _controller, + controller: controller, ); - _controller.addSendProgressListener(onSendProgress); + controller.addSendProgressListener(onSendProgress); manager.addTask(task); - _currentWorkingTaskController = _controller; + _currentWorkingTaskController = controller; return task; } @@ -162,18 +163,18 @@ class PutByPartTask extends RequestTask { String uploadId, List parts, ) { - final _controller = PutController(); + final controller = PutController(); final task = CompletePartsTask( token: token, uploadId: uploadId, parts: parts, key: resource.name, customVars: options.customVars, - controller: _controller, + controller: controller, ); manager.addTask(task); - _currentWorkingTaskController = _controller; + _currentWorkingTaskController = controller; return task; } } diff --git a/base/lib/src/storage/methods/put/by_part/upload_part_task.dart b/base/lib/src/storage/methods/put/by_part/upload_part_task.dart index 0194e8b3..1eccb318 100644 --- a/base/lib/src/storage/methods/put/by_part/upload_part_task.dart +++ b/base/lib/src/storage/methods/put/by_part/upload_part_task.dart @@ -8,7 +8,7 @@ class UploadPartTask extends RequestTask { final int partSize; // 如果 data 是 Stream 的话,Dio 需要判断 content-length 才会调用 onSendProgress - // https://github.com/flutterchina/dio/blob/21136168ab39a7536835c7a59ce0465bb05feed4/dio/lib/src/dio.dart#L1000 + // https://github.com/cfug/dio/blob/v5.0.0/dio/lib/src/dio_mixin.dart#L633 final int byteLength; final int partNumber; @@ -62,7 +62,9 @@ class UploadPartTask extends RequestTask { data: Stream.fromIterable([bytes.cast()]), // 在 data 是 stream 的场景下, interceptor 传入 cancelToken 这里不传会有 bug cancelToken: controller?.cancelToken, - options: Options(headers: headers), + options: Options( + headers: headers, + ), ); return UploadPart.fromJson(response.data!); diff --git a/base/lib/src/storage/methods/put/by_part/upload_parts_task.dart b/base/lib/src/storage/methods/put/by_part/upload_parts_task.dart index b81cfc6f..1a2def13 100644 --- a/base/lib/src/storage/methods/put/by_part/upload_parts_task.dart +++ b/base/lib/src/storage/methods/put/by_part/upload_parts_task.dart @@ -96,8 +96,8 @@ class UploadPartsTask extends RequestTask> with CacheMixin { var cachedList = []; try { - final _cachedList = json.decode(cachedData) as List; - cachedList = _cachedList + final cachedList0 = json.decode(cachedData) as List; + cachedList = cachedList0 .map((dynamic item) => Part.fromJson(item as Map)) .toList(); } catch (error) { @@ -131,7 +131,7 @@ class UploadPartsTask extends RequestTask> with CacheMixin { // 从指定的分片位置往后上传切片 Future _uploadParts() async { - final taskFutures = >[]; + final taskFutures = >[]; final tasksLength = min(_idleRequestNumber, _totalPartCount - _uploadingPartIndex); @@ -141,8 +141,8 @@ class UploadPartsTask extends RequestTask> with CacheMixin { final partNumber = ++_uploadingPartIndex; // 跳过上传过的分片 - final _uploadedPart = _uploadedPartMap[partNumber]; - if (_uploadedPart != null) { + final uploadedPart = _uploadedPartMap[partNumber]; + if (uploadedPart != null) { _sentPartCount++; _sentPartToServerCount++; notifySendProgress(); @@ -159,14 +159,16 @@ class UploadPartsTask extends RequestTask> with CacheMixin { } } - await Future.wait(taskFutures); + await Future.wait(taskFutures); } - Future _createUploadPartTaskFutureByPartNumber( - List bytes, int partNumber) async { + Future _createUploadPartTaskFutureByPartNumber( + List bytes, + int partNumber, + ) async { _idleRequestNumber--; - final _controller = PutController(); - _workingUploadPartTaskControllers.add(_controller); + final controller = PutController(); + _workingUploadPartTaskControllers.add(controller); final task = UploadPartTask( token: token, @@ -176,10 +178,10 @@ class UploadPartsTask extends RequestTask> with CacheMixin { partNumber: partNumber, partSize: partSize, key: resource.name, - controller: _controller, + controller: controller, ); - _controller + controller // UploadPartTask 一次上传一个 chunk,通知一次进度 ..addSendProgressListener((percent) { _sentPartCount++; @@ -198,7 +200,7 @@ class UploadPartsTask extends RequestTask> with CacheMixin { _idleRequestNumber++; _uploadedPartMap[partNumber] = Part(partNumber: partNumber, etag: data.etag); - _workingUploadPartTaskControllers.remove(_controller); + _workingUploadPartTaskControllers.remove(controller); await storeUploadedPart(); @@ -214,8 +216,10 @@ class UploadPartsTask extends RequestTask> with CacheMixin { } void notifyProgress() { - controller?.notifyProgressListeners(_sentPartToServerCount / - _totalPartCount * - RequestTask.onSendProgressTakePercentOfTotal); + controller?.notifyProgressListeners( + _sentPartToServerCount / + _totalPartCount * + RequestTask.onSendProgressTakePercentOfTotal, + ); } } diff --git a/base/lib/src/storage/methods/put/by_single/put_by_single_options.dart b/base/lib/src/storage/methods/put/by_single/put_by_single_options.dart deleted file mode 100644 index f7fc4f73..00000000 --- a/base/lib/src/storage/methods/put/by_single/put_by_single_options.dart +++ /dev/null @@ -1,17 +0,0 @@ -import '../put_controller.dart'; - -@Deprecated('use PutOptions') -class PutBySingleOptions { - /// 资源名 - /// - /// 如果不传则后端自动生成 - final String? key; - - /// 自定义变量,key 必须以 x: 开始 - final Map? customVars; - - /// 控制器 - final PutController? controller; - - const PutBySingleOptions({this.key, this.customVars, this.controller}); -} diff --git a/base/lib/src/storage/methods/put/by_single/put_by_single_task.dart b/base/lib/src/storage/methods/put/by_single/put_by_single_task.dart index eac8f7b7..56911820 100644 --- a/base/lib/src/storage/methods/put/by_single/put_by_single_task.dart +++ b/base/lib/src/storage/methods/put/by_single/put_by_single_task.dart @@ -1,6 +1,6 @@ import 'package:dio/dio.dart'; -import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; +import '../../../../../qiniu_sdk_base.dart'; import '../../../resource/resource.dart'; // 直传任务 diff --git a/base/lib/src/storage/methods/put/put.dart b/base/lib/src/storage/methods/put/put.dart index af6273fb..ba579f92 100644 --- a/base/lib/src/storage/methods/put/put.dart +++ b/base/lib/src/storage/methods/put/put.dart @@ -1,5 +1,3 @@ -export 'by_part/put_by_part_options.dart'; -export 'by_single/put_by_single_options.dart'; export 'put_controller.dart'; export 'put_options.dart'; export 'put_response.dart'; diff --git a/base/lib/src/storage/methods/put/put_options.dart b/base/lib/src/storage/methods/put/put_options.dart index fd21b27c..3e616586 100644 --- a/base/lib/src/storage/methods/put/put_options.dart +++ b/base/lib/src/storage/methods/put/put_options.dart @@ -30,6 +30,8 @@ class PutOptions { this.maxPartsRequestNumber = 5, this.customVars, this.controller, - }) : assert(partSize >= 1 && partSize <= 1024, - 'partSize must be greater than or equal to 1 and less than or equal to 1024'); + }) : assert( + partSize >= 1 && partSize <= 1024, + 'partSize must be greater than or equal to 1 and less than or equal to 1024', + ); } diff --git a/base/lib/src/storage/storage.dart b/base/lib/src/storage/storage.dart index 1082fbb4..370d2f62 100644 --- a/base/lib/src/storage/storage.dart +++ b/base/lib/src/storage/storage.dart @@ -1,23 +1,22 @@ import 'dart:io'; import 'dart:typed_data'; -import 'package:path/path.dart' show basename; -import 'package:qiniu_sdk_base/src/storage/resource/resource.dart'; +import 'package:path/path.dart' show basename; import 'config/config.dart'; import 'methods/put/by_part/put_parts_task.dart'; import 'methods/put/by_single/put_by_single_task.dart'; import 'methods/put/put.dart'; +import 'resource/resource.dart'; import 'task/task.dart'; export 'package:dio/dio.dart' show HttpClientAdapter; - -export 'config/config.dart'; export 'error/error.dart'; export 'methods/put/put.dart'; export 'status/status.dart'; export 'task/request_task.dart'; export 'task/task.dart'; +export 'config/config.dart'; /// 客户端 class Storage { diff --git a/base/lib/src/storage/task/request_task.dart b/base/lib/src/storage/task/request_task.dart index aa2ab086..9b017d0f 100644 --- a/base/lib/src/storage/task/request_task.dart +++ b/base/lib/src/storage/task/request_task.dart @@ -1,6 +1,7 @@ import 'package:dio/dio.dart'; import 'package:meta/meta.dart'; -import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; + +import '../../../qiniu_sdk_base.dart'; part 'request_task_controller.dart'; part 'request_task_manager.dart'; @@ -52,15 +53,27 @@ abstract class RequestTask extends Task { controller?.notifyProgressListeners(preStartTakePercentOfTotal); retryLimit = config.retryLimit; client.httpClientAdapter = config.httpClientAdapter; - client.interceptors.add(InterceptorsWrapper(onRequest: (options, handler) { - controller?.notifyStatusListeners(StorageStatus.Request); - options - ..cancelToken = controller?.cancelToken - ..onSendProgress = (sent, total) => onSendProgress(sent / total); - options.headers['User-Agent'] = _getUserAgent(); - - handler.next(options); - })); + client.interceptors.add( + InterceptorsWrapper( + onRequest: (options, handler) { + controller?.notifyStatusListeners(StorageStatus.Request); + options + ..cancelToken = controller?.cancelToken + ..onSendProgress = (sent, total) => onSendProgress(sent / total); + options.headers['User-Agent'] = _getUserAgent(); + + if (options.contentType == null) { + if (options.data is Stream) { + options.contentType = 'application/octet-stream'; + } else { + options.contentType = 'application/json'; + } + } + + handler.next(options); + }, + ), + ); super.preStart(); } @@ -152,7 +165,7 @@ abstract class RequestTask extends Task { if (!_canConnectToHost(error) || _isHostUnavailable(error)) { return true; } - if (error.type == DioErrorType.response) { + if (error.type == DioErrorType.badResponse) { if (error.response?.statusCode == 612) { return true; } @@ -163,7 +176,7 @@ abstract class RequestTask extends Task { // host 是否可以连接上 bool _canConnectToHost(Object error) { if (error is DioError) { - if (error.type == DioErrorType.response) { + if (error.type == DioErrorType.badResponse) { final statusCode = error.response?.statusCode; if (statusCode is int && statusCode > 99) { return true; @@ -181,7 +194,7 @@ abstract class RequestTask extends Task { // host 是否不可用 bool _isHostUnavailable(Object error) { if (error is DioError) { - if (error.type == DioErrorType.response) { + if (error.type == DioErrorType.badResponse) { final statusCode = error.response?.statusCode; if (statusCode == 502) { return true; diff --git a/base/lib/src/storage/task/request_task_controller.dart b/base/lib/src/storage/task/request_task_controller.dart index d908a463..035ae5f0 100644 --- a/base/lib/src/storage/task/request_task_controller.dart +++ b/base/lib/src/storage/task/request_task_controller.dart @@ -29,7 +29,8 @@ mixin RequestTaskSendProgressListenersMixin { final List _sendProgressListeners = []; void Function() addSendProgressListener( - RequestTaskSendProgressListener listener) { + RequestTaskSendProgressListener listener, + ) { _sendProgressListeners.add(listener); return () => removeSendProgressListener(listener); } diff --git a/base/lib/src/storage/task/task.dart b/base/lib/src/storage/task/task.dart index a8df65ac..c7d0f218 100644 --- a/base/lib/src/storage/task/task.dart +++ b/base/lib/src/storage/task/task.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'package:meta/meta.dart'; -import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; + +import '../../../qiniu_sdk_base.dart'; export 'request_task.dart'; export 'task_manager.dart'; diff --git a/base/pubspec.yaml b/base/pubspec.yaml index 13ed7703..edb43d83 100644 --- a/base/pubspec.yaml +++ b/base/pubspec.yaml @@ -1,5 +1,5 @@ name: qiniu_sdk_base -version: 0.4.0 +version: 0.5.0 homepage: https://github.com/qiniu/dart-sdk description: The sdk basic of Qiniu products @@ -7,17 +7,17 @@ false_secrets: - /test/auth/auth_test.dart environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.15.0 <4.0.0' dependencies: - dio: ^4.0.0 + dio: ^5.0.0 crypto: ^3.0.0 meta: ^1.3.0 uuid: ^3.0.5 path: ^1.8.0 dev_dependencies: - pedantic: ^1.11.0 - test: ^1.16.8 - dotenv: 3.0.0-nullsafety.0 - coverage: "^1.0.2" + test: + lints: + dotenv: ^4.1.0 + coverage: "^1.6.3" diff --git a/base/test/auth/auth_test.dart b/base/test/auth/auth_test.dart index 1f718541..4baa3d1f 100644 --- a/base/test/auth/auth_test.dart +++ b/base/test/auth/auth_test.dart @@ -39,13 +39,13 @@ class AccessTokenTestData { void main() { group('Auth', () { - var auth = Auth( + final auth = Auth( accessKey: 'iN7NgwM31j4-BZacMjPrOQBs34UG1maYCAQmhdCV', secretKey: '6QTOr2Jg1gcZEWDQXKOGZh5PziC2MCV5KsntT70j', ); test('GenerateUploadToken and parseToken should all well', () { - var testDataTable = [ + final testDataTable = [ UploadTokenTestData( PutPolicy( scope: 'testBucket', @@ -70,10 +70,11 @@ void main() { ), UploadTokenTestData( PutPolicy( - scope: 'testBucket', - deadline: 1600000000, - returnBody: '{"key": \$(key)}', - callbackUrl: 'http://test.qiniu.com'), + scope: 'testBucket', + deadline: 1600000000, + returnBody: '{"key": \$(key)}', + callbackUrl: 'http://test.qiniu.com', + ), 'iN7NgwM31j4-BZacMjPrOQBs34UG1maYCAQmhdCV:noK7jkMNZbw-padaaHy71buHpy8=:eyJzY29wZSI6InRlc3RCdWNrZXQiLCJkZWFkbGluZSI6MTYwMDAwMDAwMCwicmV0dXJuQm9keSI6IntcImtleVwiOiAkKGtleSl9IiwiY2FsbGJhY2tVcmwiOiJodHRwOi8vdGVzdC5xaW5pdS5jb20ifQ==', ), UploadTokenTestData( @@ -86,10 +87,10 @@ void main() { ]; for (final testData in testDataTable) { - var token = auth.generateUploadToken(putPolicy: testData.putPolicy); + final token = auth.generateUploadToken(putPolicy: testData.putPolicy); expect(token, equals(testData.expectedToken)); - var tokenInfo = Auth.parseToken(token); + final tokenInfo = Auth.parseToken(token); expect(tokenInfo.accessKey, equals(auth.accessKey)); expect( @@ -100,7 +101,7 @@ void main() { }); test('GenerateDownloadToken and parseToken should all well', () { - var testDataTable = [ + final testDataTable = [ DownloadTokenTestData( 'testFileName', 1600000000, @@ -128,21 +129,21 @@ void main() { ]; for (final testData in testDataTable) { - var token = auth.generateDownloadToken( + final token = auth.generateDownloadToken( key: testData.key, deadline: testData.deadline, bucketDomain: testData.bucketDomain, ); expect(token, equals(testData.expectedToken)); - var tokenInfo = Auth.parseToken(token); + final tokenInfo = Auth.parseToken(token); expect(tokenInfo.accessKey, equals(auth.accessKey)); expect(tokenInfo.putPolicy, equals(null)); } }); test('GenerateAccessToken and parseToken should all well', () { - var testDataTable = [ + final testDataTable = [ AccessTokenTestData( utf8.encode('POST /move/test\nHost: rs.qiniu.com\n\n'), 'iN7NgwM31j4-BZacMjPrOQBs34UG1maYCAQmhdCV:-m6mplPX8YzlVQ-NE08BqHvFC-Y=', @@ -154,9 +155,9 @@ void main() { ]; for (final testData in testDataTable) { - var token = auth.generateAccessToken(bytes: testData.bytes); + final token = auth.generateAccessToken(bytes: testData.bytes); expect(token, equals(testData.expectedToken)); - var tokenInfo = Auth.parseToken(token); + final tokenInfo = Auth.parseToken(token); expect(tokenInfo.accessKey, equals(auth.accessKey)); expect(tokenInfo.putPolicy, equals(null)); } diff --git a/base/test/config.dart b/base/test/config.dart index d64263ca..d5aec3ac 100644 --- a/base/test/config.dart +++ b/base/test/config.dart @@ -1,39 +1,39 @@ import 'dart:io'; // ignore: import_of_legacy_library_into_null_safe -import 'package:dotenv/dotenv.dart' show load, clean, isEveryDefined, env; +import 'package:dotenv/dotenv.dart' show DotEnv; import 'package:qiniu_sdk_base/src/auth/auth.dart'; import 'package:test/test.dart'; late String token; -bool get isSensitiveDataDefined { - load(); - return isEveryDefined([ - 'QINIU_DART_SDK_ACCESS_KEY', - 'QINIU_DART_SDK_SECRET_KEY', - 'QINIU_DART_SDK_TOKEN_SCOPE' - ]); -} +final env = DotEnv(includePlatformEnvironment: true)..load(); + +final isSensitiveDataDefined = env.isEveryDefined([ + 'QINIU_DART_SDK_ACCESS_KEY', + 'QINIU_DART_SDK_SECRET_KEY', + 'QINIU_DART_SDK_TOKEN_SCOPE' +]); void configEnv() { setUpAll(() { if (isSensitiveDataDefined) { - var auth = Auth( + final auth = Auth( accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, secretKey: env['QINIU_DART_SDK_SECRET_KEY']!, ); token = auth.generateUploadToken( putPolicy: PutPolicy( - insertOnly: 0, - scope: env['QINIU_DART_SDK_TOKEN_SCOPE']!, - deadline: DateTime.now().millisecondsSinceEpoch + 3600), + insertOnly: 0, + scope: env['QINIU_DART_SDK_TOKEN_SCOPE']!, + deadline: DateTime.now().millisecondsSinceEpoch + 3600, + ), ); } else { stderr.writeln('没有在 .env 文件里配置测试用的必要信息,一些测试用例会被跳过'); } }); - tearDownAll(clean); + tearDownAll(env.clear); } diff --git a/base/test/config_test.dart b/base/test/config_test.dart index afe54bc0..eaa0c663 100644 --- a/base/test/config_test.dart +++ b/base/test/config_test.dart @@ -1,5 +1,3 @@ -// ignore: import_of_legacy_library_into_null_safe -import 'package:dotenv/dotenv.dart'; import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; import 'package:test/test.dart'; @@ -7,19 +5,23 @@ import 'config.dart'; void main() { configEnv(); - test('RegionProvider should works well.', () async { - final hostProvider = DefaultHostProvider(); - final tokenInfo = Auth.parseUpToken(token); - final putPolicy = tokenInfo.putPolicy; - - final hostInToken = await hostProvider.getUpHost( - accessKey: tokenInfo.accessKey, - bucket: putPolicy.getBucket(), - ); - - // 根据传入的 token 的 bucket 对应的区域,需要对应的修改这里 - expect(hostInToken, 'https://upload-na0.qiniup.com'); - }, skip: !isSensitiveDataDefined); + test( + 'RegionProvider should works well.', + () async { + final hostProvider = DefaultHostProvider(); + final tokenInfo = Auth.parseUpToken(token); + final putPolicy = tokenInfo.putPolicy; + + final hostInToken = await hostProvider.getUpHost( + accessKey: tokenInfo.accessKey, + bucket: putPolicy.getBucket(), + ); + + // 根据传入的 token 的 bucket 对应的区域,需要对应的修改这里 + expect(hostInToken, 'https://upload-na0.qiniup.com'); + }, + skip: !isSensitiveDataDefined, + ); test('DefaultCacheProvider should works well.', () async { final cacheProvider = DefaultCacheProvider(); @@ -35,20 +37,23 @@ void main() { expect(cacheProvider.value.length, 0); }); - test('freeze mechanism should works well with DefaultHostProvider.', - () async { - final config = Config(); - final hostA = await config.hostProvider.getUpHost( - accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, - bucket: env['QINIU_DART_SDK_TOKEN_SCOPE']!, - ); - config.hostProvider.freezeHost(hostA); - final hostB = await config.hostProvider.getUpHost( - accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, - bucket: env['QINIU_DART_SDK_TOKEN_SCOPE']!, - ); - - // getUpHost 会返回至少2个host,不用担心会少于两个 - expect(hostA == hostB, false); - }, skip: !isSensitiveDataDefined); + test( + 'freeze mechanism should works well with DefaultHostProvider.', + () async { + final config = Config(); + final hostA = await config.hostProvider.getUpHost( + accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, + bucket: env['QINIU_DART_SDK_TOKEN_SCOPE']!, + ); + config.hostProvider.freezeHost(hostA); + final hostB = await config.hostProvider.getUpHost( + accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, + bucket: env['QINIU_DART_SDK_TOKEN_SCOPE']!, + ); + + // getUpHost 会返回至少2个host,不用担心会少于两个 + expect(hostA == hostB, false); + }, + skip: !isSensitiveDataDefined, + ); } diff --git a/base/test/put/helpers.dart b/base/test/put/helpers.dart index 483a149f..76a533fa 100644 --- a/base/test/put/helpers.dart +++ b/base/test/put/helpers.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'dart:typed_data'; -import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; import 'package:test/test.dart'; @@ -57,19 +56,22 @@ class PutControllerBuilder { } } -class HttpAdapterTestWith612 extends HttpClientAdapter { +class HttpAdapterTestWith612 implements HttpClientAdapter { /// 记录 CompletePartsTask 被创建的次数 /// 第一次我们拦截并返回 612,第二次不拦截 bool completePartsTaskResponse612 = false; - final DefaultHttpClientAdapter _adapter = DefaultHttpClientAdapter(); + final HttpClientAdapter _adapter = HttpClientAdapter(); @override void close({bool force = false}) { _adapter.close(force: force); } @override - Future fetch(RequestOptions options, - Stream? requestStream, Future? cancelFuture) async { + Future fetch( + RequestOptions options, + Stream? requestStream, + Future? cancelFuture, + ) async { /// 如果是 CompletePartsTask 发出去的请求,则返回 612 if (options.path.contains('uploads/') && options.method == 'POST' && diff --git a/base/test/put/put_by_parts/put_bytes_test.dart b/base/test/put/put_by_parts/put_bytes_test.dart index fdbfa2e9..fb696ba1 100644 --- a/base/test/put/put_by_parts/put_bytes_test.dart +++ b/base/test/put/put_by_parts/put_bytes_test.dart @@ -1,5 +1,4 @@ @Timeout(Duration(seconds: 60)) -import 'package:dotenv/dotenv.dart' show env; import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; import 'package:qiniu_sdk_base/src/storage/methods/put/by_part/put_parts_task.dart'; import 'package:qiniu_sdk_base/src/storage/resource/resource.dart'; @@ -13,119 +12,122 @@ void main() { final bytes = fileForPart.readAsBytesSync(); - test('customVars&returnBody customVars should works well.', () async { - final storage = Storage(); - - final auth = Auth( - accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, - secretKey: env['QINIU_DART_SDK_SECRET_KEY']!, - ); - - final token = auth.generateUploadToken( - putPolicy: PutPolicy( - insertOnly: 0, - scope: env['QINIU_DART_SDK_TOKEN_SCOPE']!, - returnBody: '{"key":"\$(key)","type":"\$(x:type)","ext":"\$(x:ext)"}', - deadline: DateTime.now().millisecondsSinceEpoch + 3600, - ), - ); - - var customVars = { - 'x:type': 'testXType', - 'x:ext': 'testXExt', - }; - - var putController = PutController(); - final response = await storage.putBytes( - bytes, - token, - options: PutOptions( - key: fileKeyForPart, - partSize: 1, - customVars: customVars, - controller: putController, - ), - ); - - expect(response.key, fileKeyForPart); - expect(response.rawData['type'], 'testXType'); - expect(response.rawData['ext'], 'testXExt'); - }, skip: !isSensitiveDataDefined); - - test('putBytes should works well.', () async { - final storage = Storage(); - final pcb = PutControllerBuilder(); - var callnumber = 0; - pcb.putController.addSendProgressListener((percent) { - callnumber++; - }); - final response = await storage.putBytes( - bytes, - token, - options: PutOptions( - key: fileKeyForPart, - partSize: 1, - controller: pcb.putController, - ), - ); - expect(response, isA()); - // 2 片分片所以 2 次 - expect(callnumber, 2); - - pcb.testAll(); - - // 不设置参数的情况 - final responseNoOps = await storage.putBytes( - bytes, - token, - ); - - expect(responseNoOps, isA()); - }, skip: !isSensitiveDataDefined); - - test('putBytes should throw error with incorrect partSize.', () async { - final storage = Storage(); - try { - await storage.putBytes( + test( + 'customVars&returnBody customVars should works well.', + () async { + final storage = Storage(); + + final auth = Auth( + accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, + secretKey: env['QINIU_DART_SDK_SECRET_KEY']!, + ); + + final token = auth.generateUploadToken( + putPolicy: PutPolicy( + insertOnly: 0, + scope: env['QINIU_DART_SDK_TOKEN_SCOPE']!, + returnBody: '{"key":"\$(key)","type":"\$(x:type)","ext":"\$(x:ext)"}', + deadline: DateTime.now().millisecondsSinceEpoch + 3600, + ), + ); + + final customVars = { + 'x:type': 'testXType', + 'x:ext': 'testXExt', + }; + + final putController = PutController(); + final response = await storage.putBytes( bytes, token, - options: PutOptions(partSize: 0), + options: PutOptions( + key: fileKeyForPart, + partSize: 1, + customVars: customVars, + controller: putController, + ), ); - } catch (e) { - expect(e, isA()); - } - try { - await storage.putBytes( + expect(response.key, fileKeyForPart); + expect(response.rawData['type'], 'testXType'); + expect(response.rawData['ext'], 'testXExt'); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putBytes should works well.', + () async { + final storage = Storage(); + final pcb = PutControllerBuilder(); + var callnumber = 0; + pcb.putController.addSendProgressListener((percent) { + callnumber++; + }); + final response = await storage.putBytes( bytes, token, - options: PutOptions(partSize: 1025), + options: PutOptions( + key: fileKeyForPart, + partSize: 1, + controller: pcb.putController, + ), ); - } catch (e) { - expect(e, isA()); - } - }, skip: !isSensitiveDataDefined); - - test('putBytes should throw error if there is a same task is working.', - () async { - final storage = Storage(); - final key = fileKeyForPart; - - var errorOccurred = false; - - // 故意不 await,让后面发送一个相同的任务 - // ignore: unawaited_futures - storage.putBytes( - bytes, - token, - options: PutOptions( - key: key, - partSize: 1, - ), - ); + expect(response, isA()); + // 2 片分片所以 2 次 + expect(callnumber, 2); + + pcb.testAll(); + + // 不设置参数的情况 + final responseNoOps = await storage.putBytes( + bytes, + token, + ); + + expect(responseNoOps, isA()); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putBytes should throw error with incorrect partSize.', + () async { + final storage = Storage(); + try { + await storage.putBytes( + bytes, + token, + options: PutOptions(partSize: 0), + ); + } catch (e) { + expect(e, isA()); + } + + try { + await storage.putBytes( + bytes, + token, + options: PutOptions(partSize: 1025), + ); + } catch (e) { + expect(e, isA()); + } + }, + skip: !isSensitiveDataDefined, + ); - try { - await storage.putBytes( + test( + 'putBytes should throw error if there is a same task is working.', + () async { + final storage = Storage(); + final key = fileKeyForPart; + + var errorOccurred = false; + + // 故意不 await,让后面发送一个相同的任务 + // ignore: unawaited_futures + storage.putBytes( bytes, token, options: PutOptions( @@ -133,64 +135,58 @@ void main() { partSize: 1, ), ); - } catch (e) { - errorOccurred = true; - expect(e, isA()); - expect((e as StorageError).type, StorageErrorType.IN_PROGRESS); - } - expect(errorOccurred, true); - }, skip: !isSensitiveDataDefined); - - test('putBytes should works well while response 612.', () async { - final httpAdapterTest = HttpAdapterTestWith612(); - final storage = Storage(config: Config(httpClientAdapter: httpAdapterTest)); - final response = await storage.putBytes( - bytes, - token, - options: PutOptions(key: fileKeyForPart, partSize: 1), - ); - - /// httpAdapterTest 应该会触发一次 612 response - expect(httpAdapterTest.completePartsTaskResponse612, true); - expect(response, isA()); - }, skip: !isSensitiveDataDefined); - - test('putBytes can be cancelled.', () async { - final pcb = PutControllerBuilder(); - final storage = Storage(config: Config(hostProvider: HostProviderTest())); - final key = fileKeyForPart; - pcb.putController.addSendProgressListener((percent) { - // 开始上传并且 InitPartsTask 设置完缓存后取消 - if (percent > 0.1) { - pcb.putController.cancel(); + + try { + await storage.putBytes( + bytes, + token, + options: PutOptions( + key: key, + partSize: 1, + ), + ); + } catch (e) { + errorOccurred = true; + expect(e, isA()); + expect((e as StorageError).type, StorageErrorType.IN_PROGRESS); } - }); - var future = storage.putBytes( - bytes, - token, - options: PutOptions( - key: key, - partSize: 1, - controller: pcb.putController, - ), - ); - try { - await future; - } catch (error) { - expect((error as StorageError).type, StorageErrorType.CANCEL); - } - expect(future, throwsA(TypeMatcher())); - pcb.testStatus(targetStatusList: [ - StorageStatus.Init, - StorageStatus.Request, - StorageStatus.Cancel - ], targetProgressList: [ - 0.001, - 0.002, - ]); - - try { - await storage.putBytes( + expect(errorOccurred, true); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putBytes should works well while response 612.', + () async { + final httpAdapterTest = HttpAdapterTestWith612(); + final storage = + Storage(config: Config(httpClientAdapter: httpAdapterTest)); + final response = await storage.putBytes( + bytes, + token, + options: PutOptions(key: fileKeyForPart, partSize: 1), + ); + + /// httpAdapterTest 应该会触发一次 612 response + expect(httpAdapterTest.completePartsTaskResponse612, true); + expect(response, isA()); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putBytes can be cancelled.', + () async { + final pcb = PutControllerBuilder(); + final storage = Storage(config: Config(hostProvider: HostProviderTest())); + final key = fileKeyForPart; + pcb.putController.addSendProgressListener((percent) { + // 开始上传并且 InitPartsTask 设置完缓存后取消 + if (percent > 0.1) { + pcb.putController.cancel(); + } + }); + final future = storage.putBytes( bytes, token, options: PutOptions( @@ -199,148 +195,190 @@ void main() { controller: pcb.putController, ), ); - } catch (error) { - // 复用了相同的 controller,所以也会触发取消的错误 - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.CANCEL); - } - - expect(future, throwsA(isA())); - - final response = await storage.putBytes( - bytes, - token, - options: PutOptions(key: key, partSize: 1), - ); - expect(response, isA()); - }, skip: !isSensitiveDataDefined); - - test('putBytes can be resumed.', () async { - final storage = Storage(config: Config(hostProvider: HostProviderTest())); - final putController = PutController(); - putController.addSendProgressListener((percent) { - // 开始上传了取消 - if (percent > 0.1) { - putController.cancel(); + try { + await future; + } catch (error) { + expect((error as StorageError).type, StorageErrorType.CANCEL); + } + expect(future, throwsA(TypeMatcher())); + pcb.testStatus( + targetStatusList: [ + StorageStatus.Init, + StorageStatus.Request, + StorageStatus.Cancel + ], + targetProgressList: [ + 0.001, + 0.002, + ], + ); + + try { + await storage.putBytes( + bytes, + token, + options: PutOptions( + key: key, + partSize: 1, + controller: pcb.putController, + ), + ); + } catch (error) { + // 复用了相同的 controller,所以也会触发取消的错误 + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.CANCEL); + } + + expect(future, throwsA(isA())); + + final response = await storage.putBytes( + bytes, + token, + options: PutOptions(key: key, partSize: 1), + ); + expect(response, isA()); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putBytes can be resumed.', + () async { + final storage = Storage(config: Config(hostProvider: HostProviderTest())); + final putController = PutController(); + putController.addSendProgressListener((percent) { + // 开始上传了取消 + if (percent > 0.1) { + putController.cancel(); + } + }); + + final future = storage.putBytes( + bytes, + token, + options: PutOptions( + key: fileKeyForPart, + partSize: 1, + controller: putController, + ), + ); + + try { + await future; + } catch (error) { + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.CANCEL); } - }); - - final future = storage.putBytes( - bytes, - token, - options: PutOptions( - key: fileKeyForPart, - partSize: 1, - controller: putController, - ), - ); - - try { - await future; - } catch (error) { - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.CANCEL); - } - - expect(future, throwsA(TypeMatcher())); - - final response = await storage.putBytes( - bytes, - token, - options: PutOptions(key: fileKeyForPart, partSize: 1), - ); - - expect(response, isA()); - }, skip: !isSensitiveDataDefined); - - test('putBytes should works well with cacheProvider.', () async { - final cacheProvider = CacheProviderForTest(); - final config = Config(cacheProvider: cacheProvider); - final storage = Storage(config: config); - final key = fileKeyForPart; - final resource = BytesResource(bytes: bytes, length: bytes.length); - - /// 手动初始化一个初始化文件的任务,确定分片上传的第一步会被缓存 - final task = InitPartsTask( - token: token, - resource: resource, - key: key, - ); - - storage.taskManager.addTask(task); - - await task.future; - - final putController = PutController(); - - putController.addSendProgressListener((percent) { - // 因为一共 2 个分片,取 0.5 一个完成后就取消 - if (percent > 0.5) { - putController.cancel(); + + expect(future, throwsA(TypeMatcher())); + + final response = await storage.putBytes( + bytes, + token, + options: PutOptions(key: fileKeyForPart, partSize: 1), + ); + + expect(response, isA()); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putBytes should works well with cacheProvider.', + () async { + final cacheProvider = CacheProviderForTest(); + final config = Config(cacheProvider: cacheProvider); + final storage = Storage(config: config); + final key = fileKeyForPart; + final resource = BytesResource(bytes: bytes, length: bytes.length); + + /// 手动初始化一个初始化文件的任务,确定分片上传的第一步会被缓存 + final task = InitPartsTask( + token: token, + resource: resource, + key: key, + ); + + storage.taskManager.addTask(task); + + await task.future; + + final putController = PutController(); + + putController.addSendProgressListener((percent) { + // 因为一共 2 个分片,取 0.5 一个完成后就取消 + if (percent > 0.5) { + putController.cancel(); + } + }); + + final future = storage.putBytes( + bytes, + token, + options: PutOptions(key: key, partSize: 1, controller: putController), + ); + + /// 这个时候应该只缓存了初始化的缓存信息 + expect(cacheProvider.value.length, 1); + + /// 初始化的缓存 key 生成逻辑 + final cacheKey = InitPartsTask.getCacheKey( + resource.id, + key, + ); + + expect(await cacheProvider.getItem(cacheKey), isA()); + await cacheProvider.clear(); + + try { + await future; + } catch (error) { + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.CANCEL); + // 每个分片完成后会保存一次 + // init 一次,仅有的一个分片完成后一次共 2 次 + expect(cacheProvider.callNumber, 2); } - }); - - final future = storage.putBytes( - bytes, - token, - options: PutOptions(key: key, partSize: 1, controller: putController), - ); - - /// 这个时候应该只缓存了初始化的缓存信息 - expect(cacheProvider.value.length, 1); - - /// 初始化的缓存 key 生成逻辑 - final cacheKey = InitPartsTask.getCacheKey( - resource.id, - key, - ); - - expect(await cacheProvider.getItem(cacheKey), isA()); - await cacheProvider.clear(); - - try { - await future; - } catch (error) { - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.CANCEL); - // 每个分片完成后会保存一次 - // init 一次,仅有的一个分片完成后一次共 2 次 - expect(cacheProvider.callNumber, 2); - } - - await cacheProvider.clear(); - cacheProvider.callNumber = 0; - - final response = await storage.putBytes( - bytes, - token, - options: PutOptions(key: key, partSize: 1), - ); - - expect(response, isA()); - - /// 上传完成后缓存应该被清理 - expect(cacheProvider.value.length, 0); - // init + 2 个分片 2 次 = 3 次 - expect(cacheProvider.callNumber, 3); - }, skip: !isSensitiveDataDefined); - - test('putBytes\'s status and progress should works well.', () async { - final storage = Storage(); - final pcb = PutControllerBuilder(); - - final response = await storage.putBytes( - bytes, - token, - options: PutOptions( - key: fileKeyForPart, - partSize: 1, - controller: pcb.putController, - ), - ); - expect(response, isA()); - pcb - ..testProcess() - ..testStatus(targetProgressList: [0.001, 0.002, 0.99, 1]); - }, skip: !isSensitiveDataDefined); + + await cacheProvider.clear(); + cacheProvider.callNumber = 0; + + final response = await storage.putBytes( + bytes, + token, + options: PutOptions(key: key, partSize: 1), + ); + + expect(response, isA()); + + /// 上传完成后缓存应该被清理 + expect(cacheProvider.value.length, 0); + // init + 2 个分片 2 次 = 3 次 + expect(cacheProvider.callNumber, 3); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putBytes\'s status and progress should works well.', + () async { + final storage = Storage(); + final pcb = PutControllerBuilder(); + + final response = await storage.putBytes( + bytes, + token, + options: PutOptions( + key: fileKeyForPart, + partSize: 1, + controller: pcb.putController, + ), + ); + expect(response, isA()); + pcb + ..testProcess() + ..testStatus(targetProgressList: [0.001, 0.002, 0.99, 1]); + }, + skip: !isSensitiveDataDefined, + ); } diff --git a/base/test/put/put_by_parts/put_file_test.dart b/base/test/put/put_by_parts/put_file_test.dart index fadfcdfc..60cbf2f6 100644 --- a/base/test/put/put_by_parts/put_file_test.dart +++ b/base/test/put/put_by_parts/put_file_test.dart @@ -1,5 +1,4 @@ @Timeout(Duration(seconds: 60)) -import 'package:dotenv/dotenv.dart' show env; import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; import 'package:qiniu_sdk_base/src/storage/methods/put/by_part/put_parts_task.dart'; import 'package:qiniu_sdk_base/src/storage/resource/resource.dart'; @@ -11,149 +10,143 @@ import '../helpers.dart'; void main() { configEnv(); - test('customVars&returnBody should works well.', () async { - final storage = Storage(); - - final auth = Auth( - accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, - secretKey: env['QINIU_DART_SDK_SECRET_KEY']!, - ); - - final token = auth.generateUploadToken( - putPolicy: PutPolicy( - insertOnly: 0, - scope: env['QINIU_DART_SDK_TOKEN_SCOPE']!, - returnBody: '{"key":"\$(key)","type":"\$(x:type)","ext":"\$(x:ext)"}', - deadline: DateTime.now().millisecondsSinceEpoch + 3600, - ), - ); - - var customVars = { - 'x:type': 'testXType', - 'x:ext': 'testXExt', - }; - - var putController = PutController(); - final response = await storage.putFile( - fileForPart, - token, - options: PutOptions( - key: fileKeyForPart, - partSize: 1, - customVars: customVars, - controller: putController, - ), - ); - - expect(response.key, fileKeyForPart); - expect(response.rawData['type'], 'testXType'); - expect(response.rawData['ext'], 'testXExt'); - }, skip: !isSensitiveDataDefined); - - test('putFile should works well.', () async { - final storage = Storage(); - final pcb = PutControllerBuilder(); - var callnumber = 0; - pcb.putController.addSendProgressListener((percent) { - callnumber++; - }); - final response = await storage.putFile( - fileForPart, - token, - options: PutOptions( - key: fileKeyForPart, - partSize: 1, - controller: pcb.putController, - ), - ); - expect(response, isA()); - // 2 片分片所以 2 次 - expect(callnumber, 2); - - pcb.testAll(); - - // 不设置参数的情况 - final responseNoOps = await storage.putFile( - fileForPart, - token, - ); - - expect(responseNoOps, isA()); - }, skip: !isSensitiveDataDefined); - - test('putFile should throw error with incorrect partSize.', () async { - final storage = Storage(); - try { - await storage.putFile( + test( + 'customVars&returnBody should works well.', + () async { + final storage = Storage(); + + final auth = Auth( + accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, + secretKey: env['QINIU_DART_SDK_SECRET_KEY']!, + ); + + final token = auth.generateUploadToken( + putPolicy: PutPolicy( + insertOnly: 0, + scope: env['QINIU_DART_SDK_TOKEN_SCOPE']!, + returnBody: '{"key":"\$(key)","type":"\$(x:type)","ext":"\$(x:ext)"}', + deadline: DateTime.now().millisecondsSinceEpoch + 3600, + ), + ); + + final customVars = { + 'x:type': 'testXType', + 'x:ext': 'testXExt', + }; + + final putController = PutController(); + final response = await storage.putFile( + fileForPart, + token, + options: PutOptions( + key: fileKeyForPart, + partSize: 1, + customVars: customVars, + controller: putController, + ), + ); + + expect(response.key, fileKeyForPart); + expect(response.rawData['type'], 'testXType'); + expect(response.rawData['ext'], 'testXExt'); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile should works well.', + () async { + final storage = Storage(); + final pcb = PutControllerBuilder(); + var callnumber = 0; + pcb.putController.addSendProgressListener((percent) { + callnumber++; + }); + final response = await storage.putFile( fileForPart, token, - options: PutOptions(partSize: 0), + options: PutOptions( + key: fileKeyForPart, + partSize: 1, + controller: pcb.putController, + ), ); - } catch (e) { - expect(e, isA()); - } + expect(response, isA()); + // 2 片分片所以 2 次 + expect(callnumber, 2); - try { - await storage.putFile( + pcb.testAll(); + + // 不设置参数的情况 + final responseNoOps = await storage.putFile( fileForPart, token, - options: PutOptions(partSize: 1025), ); - } catch (e) { - expect(e, isA()); - } - }, skip: !isSensitiveDataDefined); - - test('putFile should works well while response 612.', () async { - final httpAdapterTest = HttpAdapterTestWith612(); - final storage = Storage(config: Config(httpClientAdapter: httpAdapterTest)); - final response = await storage.putFile( - fileForPart, - token, - options: PutOptions(key: fileKeyForPart, partSize: 1), - ); - - /// httpAdapterTest 应该会触发一次 612 response - expect(httpAdapterTest.completePartsTaskResponse612, true); - expect(response, isA()); - }, skip: !isSensitiveDataDefined); - - test('putFile can be cancelled.', () async { - final pcb = PutControllerBuilder(); - final storage = Storage(config: Config(hostProvider: HostProviderTest())); - final key = fileKeyForPart; - pcb.putController.addSendProgressListener((percent) { - // 开始上传并且 InitPartsTask 设置完缓存后取消 - if (percent > 0.1) { - pcb.putController.cancel(); + + expect(responseNoOps, isA()); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile should throw error with incorrect partSize.', + () async { + final storage = Storage(); + try { + await storage.putFile( + fileForPart, + token, + options: PutOptions(partSize: 0), + ); + } catch (e) { + expect(e, isA()); } - }); - var future = storage.putFile( - fileForPart, - token, - options: PutOptions( - key: key, - partSize: 1, - controller: pcb.putController, - ), - ); - try { - await future; - } catch (error) { - expect((error as StorageError).type, StorageErrorType.CANCEL); - } - expect(future, throwsA(TypeMatcher())); - pcb.testStatus(targetStatusList: [ - StorageStatus.Init, - StorageStatus.Request, - StorageStatus.Cancel - ], targetProgressList: [ - 0.001, - 0.002, - ]); - - try { - await storage.putFile( + + try { + await storage.putFile( + fileForPart, + token, + options: PutOptions(partSize: 1025), + ); + } catch (e) { + expect(e, isA()); + } + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile should works well while response 612.', + () async { + final httpAdapterTest = HttpAdapterTestWith612(); + final storage = + Storage(config: Config(httpClientAdapter: httpAdapterTest)); + final response = await storage.putFile( + fileForPart, + token, + options: PutOptions(key: fileKeyForPart, partSize: 1), + ); + + /// httpAdapterTest 应该会触发一次 612 response + expect(httpAdapterTest.completePartsTaskResponse612, true); + expect(response, isA()); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile can be cancelled.', + () async { + final pcb = PutControllerBuilder(); + final storage = Storage(config: Config(hostProvider: HostProviderTest())); + final key = fileKeyForPart; + pcb.putController.addSendProgressListener((percent) { + // 开始上传并且 InitPartsTask 设置完缓存后取消 + if (percent > 0.1) { + pcb.putController.cancel(); + } + }); + final future = storage.putFile( fileForPart, token, options: PutOptions( @@ -162,153 +155,182 @@ void main() { controller: pcb.putController, ), ); - } catch (error) { - // 复用了相同的 controller,所以也会触发取消的错误 - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.CANCEL); - } - - expect(future, throwsA(isA())); - - final response = await storage.putFile( - fileForPart, - token, - options: PutOptions(key: key, partSize: 1), - ); - expect(response, isA()); - }, skip: !isSensitiveDataDefined); - - test('putFile can be resumed.', () async { - final storage = Storage(config: Config(hostProvider: HostProviderTest())); - final putController = PutController(); - putController.addSendProgressListener((percent) { - // 开始上传了取消 - if (percent > 0.1) { - putController.cancel(); + try { + await future; + } catch (error) { + expect((error as StorageError).type, StorageErrorType.CANCEL); + } + expect(future, throwsA(TypeMatcher())); + pcb.testStatus( + targetStatusList: [ + StorageStatus.Init, + StorageStatus.Request, + StorageStatus.Cancel + ], + targetProgressList: [ + 0.001, + 0.002, + ], + ); + + try { + await storage.putFile( + fileForPart, + token, + options: PutOptions( + key: key, + partSize: 1, + controller: pcb.putController, + ), + ); + } catch (error) { + // 复用了相同的 controller,所以也会触发取消的错误 + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.CANCEL); } - }); - - final future = storage.putFile( - fileForPart, - token, - options: PutOptions( - key: fileKeyForPart, - partSize: 1, - controller: putController, - ), - ); - - try { - await future; - } catch (error) { - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.CANCEL); - } - - expect(future, throwsA(TypeMatcher())); - - final response = await storage.putFile( - fileForPart, - token, - options: PutOptions(key: fileKeyForPart, partSize: 1), - ); - - expect(response, isA()); - }, skip: !isSensitiveDataDefined); - - test('putFile should works well with cacheProvider.', () async { - final cacheProvider = CacheProviderForTest(); - final config = Config(cacheProvider: cacheProvider); - final storage = Storage(config: config); - final key = fileKeyForPart; - final resource = - FileResource(file: fileForPart, length: fileForPart.lengthSync()); - - /// 手动初始化一个初始化文件的任务,确定分片上传的第一步会被缓存 - final task = InitPartsTask( - token: token, - resource: resource, - key: key, - ); - - storage.taskManager.addTask(task); - - await task.future; - - final putController = PutController(); - - putController.addSendProgressListener((percent) { - // 因为一共 2 个分片,取 0.5 一个完成后就取消 - if (percent > 0.5) { - putController.cancel(); + + expect(future, throwsA(isA())); + + final response = await storage.putFile( + fileForPart, + token, + options: PutOptions(key: key, partSize: 1), + ); + expect(response, isA()); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile can be resumed.', + () async { + final storage = Storage(config: Config(hostProvider: HostProviderTest())); + final putController = PutController(); + putController.addSendProgressListener((percent) { + // 开始上传了取消 + if (percent > 0.1) { + putController.cancel(); + } + }); + + final future = storage.putFile( + fileForPart, + token, + options: PutOptions( + key: fileKeyForPart, + partSize: 1, + controller: putController, + ), + ); + + try { + await future; + } catch (error) { + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.CANCEL); } - }); - - final future = storage.putFile( - fileForPart, - token, - options: PutOptions(key: key, partSize: 1, controller: putController), - ); - - /// 这个时候应该只缓存了初始化的缓存信息 - expect(cacheProvider.value.length, 1); - - /// 初始化的缓存 key 生成逻辑 - final cacheKey = InitPartsTask.getCacheKey( - resource.id, - key, - ); - - expect(await cacheProvider.getItem(cacheKey), isA()); - await cacheProvider.clear(); - - try { - await future; - } catch (error) { - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.CANCEL); - // 每个分片完成后会保存一次 - // init 一次,仅有的一个分片完成后一次共 2 次 - expect(cacheProvider.callNumber, 2); - } - - await cacheProvider.clear(); - cacheProvider.callNumber = 0; - - final response = await storage.putFile( - fileForPart, - token, - options: PutOptions(key: key, partSize: 1), - ); - - expect(response, isA()); - - /// 上传完成后缓存应该被清理 - expect(cacheProvider.value.length, 0); - // init + 2 个分片 2 次 = 3 次 - expect(cacheProvider.callNumber, 3); - }, skip: !isSensitiveDataDefined); - - test('putFile should throw error if there is a same task is working.', - () async { - final storage = Storage(); - final key = fileKeyForPart; - - var errorOccurred = false; - - // 故意不 await,让后面发送一个相同的任务 - // ignore: unawaited_futures - storage.putFile( - fileForPart, - token, - options: PutOptions( + + expect(future, throwsA(TypeMatcher())); + + final response = await storage.putFile( + fileForPart, + token, + options: PutOptions(key: fileKeyForPart, partSize: 1), + ); + + expect(response, isA()); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile should works well with cacheProvider.', + () async { + final cacheProvider = CacheProviderForTest(); + final config = Config(cacheProvider: cacheProvider); + final storage = Storage(config: config); + final key = fileKeyForPart; + final resource = + FileResource(file: fileForPart, length: fileForPart.lengthSync()); + + /// 手动初始化一个初始化文件的任务,确定分片上传的第一步会被缓存 + final task = InitPartsTask( + token: token, + resource: resource, key: key, - partSize: 1, - ), - ); + ); + + storage.taskManager.addTask(task); + + await task.future; + + final putController = PutController(); + + putController.addSendProgressListener((percent) { + // 因为一共 2 个分片,取 0.5 一个完成后就取消 + if (percent > 0.5) { + putController.cancel(); + } + }); + + final future = storage.putFile( + fileForPart, + token, + options: PutOptions(key: key, partSize: 1, controller: putController), + ); + + /// 这个时候应该只缓存了初始化的缓存信息 + expect(cacheProvider.value.length, 1); + + /// 初始化的缓存 key 生成逻辑 + final cacheKey = InitPartsTask.getCacheKey( + resource.id, + key, + ); - try { - await storage.putFile( + expect(await cacheProvider.getItem(cacheKey), isA()); + await cacheProvider.clear(); + + try { + await future; + } catch (error) { + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.CANCEL); + // 每个分片完成后会保存一次 + // init 一次,仅有的一个分片完成后一次共 2 次 + expect(cacheProvider.callNumber, 2); + } + + await cacheProvider.clear(); + cacheProvider.callNumber = 0; + + final response = await storage.putFile( + fileForPart, + token, + options: PutOptions(key: key, partSize: 1), + ); + + expect(response, isA()); + + /// 上传完成后缓存应该被清理 + expect(cacheProvider.value.length, 0); + // init + 2 个分片 2 次 = 3 次 + expect(cacheProvider.callNumber, 3); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile should throw error if there is a same task is working.', + () async { + final storage = Storage(); + final key = fileKeyForPart; + + var errorOccurred = false; + + // 故意不 await,让后面发送一个相同的任务 + // ignore: unawaited_futures + storage.putFile( fileForPart, token, options: PutOptions( @@ -316,30 +338,46 @@ void main() { partSize: 1, ), ); - } catch (e) { - errorOccurred = true; - expect(e, isA()); - expect((e as StorageError).type, StorageErrorType.IN_PROGRESS); - } - expect(errorOccurred, true); - }, skip: !isSensitiveDataDefined); - - test('putFile\'s status and progress should works well.', () async { - final storage = Storage(); - final pcb = PutControllerBuilder(); - - final response = await storage.putFile( - fileForPart, - token, - options: PutOptions( - key: fileKeyForPart, - partSize: 1, - controller: pcb.putController, - ), - ); - expect(response, isA()); - pcb - ..testProcess() - ..testStatus(targetProgressList: [0.001, 0.002, 0.99, 1]); - }, skip: !isSensitiveDataDefined); + + try { + await storage.putFile( + fileForPart, + token, + options: PutOptions( + key: key, + partSize: 1, + ), + ); + } catch (e) { + errorOccurred = true; + expect(e, isA()); + expect((e as StorageError).type, StorageErrorType.IN_PROGRESS); + } + expect(errorOccurred, true); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile\'s status and progress should works well.', + () async { + final storage = Storage(); + final pcb = PutControllerBuilder(); + + final response = await storage.putFile( + fileForPart, + token, + options: PutOptions( + key: fileKeyForPart, + partSize: 1, + controller: pcb.putController, + ), + ); + expect(response, isA()); + pcb + ..testProcess() + ..testStatus(targetProgressList: [0.001, 0.002, 0.99, 1]); + }, + skip: !isSensitiveDataDefined, + ); } diff --git a/base/test/put/put_by_single/put_bytes.dart b/base/test/put/put_by_single/put_bytes.dart deleted file mode 100644 index 6c094b2b..00000000 --- a/base/test/put/put_by_single/put_bytes.dart +++ /dev/null @@ -1,135 +0,0 @@ -@Timeout(Duration(seconds: 60)) -import 'package:dotenv/dotenv.dart' show env; -import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; -import 'package:test/test.dart'; - -import '../../config.dart'; -import '../helpers.dart'; - -void main() { - configEnv(); - - final storage = Storage(); - final bytes = fileForSingle.readAsBytesSync(); - - test('customVars&returnBody should works well.', () async { - final auth = Auth( - accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, - secretKey: env['QINIU_DART_SDK_SECRET_KEY']!, - ); - - final token = auth.generateUploadToken( - putPolicy: PutPolicy( - insertOnly: 0, - scope: env['QINIU_DART_SDK_TOKEN_SCOPE']!, - returnBody: '{"key":"\$(key)","type":"\$(x:type)","ext":"\$(x:ext)"}', - deadline: DateTime.now().millisecondsSinceEpoch + 3600, - ), - ); - - var customVars = { - 'x:type': 'testXType', - 'x:ext': 'testXExt', - }; - - var putController = PutController(); - final response = await storage.putBytes( - bytes, - token, - options: PutOptions( - forceBySingle: true, - key: fileKeyForSingle, - customVars: customVars, - controller: putController, - ), - ); - - expect(response.key, fileKeyForSingle); - expect(response.rawData['type'], 'testXType'); - expect(response.rawData['ext'], 'testXExt'); - }, skip: !isSensitiveDataDefined); - - test('putBytes should works well.', () async { - var pcb = PutControllerBuilder(); - final response = await storage.putBytes( - bytes, - token, - options: PutOptions( - forceBySingle: true, - key: fileKeyForSingle, - controller: pcb.putController, - ), - ); - - pcb.testAll(); - expect(response.key, fileKeyForSingle); - }, skip: !isSensitiveDataDefined); - - test('putBytes can be cancelled.', () async { - final putController = PutController(); - final key = fileKeyForSingle; - - final statusList = []; - putController.addStatusListener((status) { - statusList.add(status); - if (status == StorageStatus.Request) { - putController.cancel(); - } - }); - var future = storage.putBytes( - bytes, - token, - options: - PutOptions(forceBySingle: true, key: key, controller: putController), - ); - try { - await future; - } catch (error) { - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.CANCEL); - } - expect(future, throwsA(TypeMatcher())); - expect(statusList[0], StorageStatus.Init); - expect(statusList[1], StorageStatus.Request); - expect(statusList[2], StorageStatus.Cancel); - - try { - await storage.putBytes( - bytes, - token, - options: PutOptions( - forceBySingle: true, key: key, controller: putController), - ); - } catch (error) { - // 复用了相同的 controller,所以也会触发取消的错误 - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.CANCEL); - } - - expect(future, throwsA(TypeMatcher())); - - final response = await storage.putBytes( - bytes, - token, - options: PutOptions(forceBySingle: true, key: key), - ); - - expect(response, isA()); - }, skip: !isSensitiveDataDefined); - - test('putBytes\'s status and progress should works well.', () async { - final pcb = PutControllerBuilder(); - - final response = await storage.putBytes( - bytes, - token, - options: PutOptions( - forceBySingle: true, - key: fileKeyForSingle, - controller: pcb.putController, - ), - ); - expect(response.key, fileKeyForSingle); - pcb.testAll(); - }, skip: !isSensitiveDataDefined); -} diff --git a/base/test/put/put_by_single/put_bytes_test.dart b/base/test/put/put_by_single/put_bytes_test.dart new file mode 100644 index 00000000..b589f17d --- /dev/null +++ b/base/test/put/put_by_single/put_bytes_test.dart @@ -0,0 +1,156 @@ +@Timeout(Duration(seconds: 60)) +import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; +import 'package:test/test.dart'; + +import '../../config.dart'; +import '../helpers.dart'; + +void main() { + configEnv(); + + final storage = Storage(); + final bytes = fileForSingle.readAsBytesSync(); + + test( + 'customVars&returnBody should works well.', + () async { + final auth = Auth( + accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, + secretKey: env['QINIU_DART_SDK_SECRET_KEY']!, + ); + + final token = auth.generateUploadToken( + putPolicy: PutPolicy( + insertOnly: 0, + scope: env['QINIU_DART_SDK_TOKEN_SCOPE']!, + returnBody: '{"key":"\$(key)","type":"\$(x:type)","ext":"\$(x:ext)"}', + deadline: DateTime.now().millisecondsSinceEpoch + 3600, + ), + ); + + final customVars = { + 'x:type': 'testXType', + 'x:ext': 'testXExt', + }; + + final putController = PutController(); + final response = await storage.putBytes( + bytes, + token, + options: PutOptions( + forceBySingle: true, + key: fileKeyForSingle, + customVars: customVars, + controller: putController, + ), + ); + + expect(response.key, fileKeyForSingle); + expect(response.rawData['type'], 'testXType'); + expect(response.rawData['ext'], 'testXExt'); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putBytes should works well.', + () async { + final pcb = PutControllerBuilder(); + final response = await storage.putBytes( + bytes, + token, + options: PutOptions( + forceBySingle: true, + key: fileKeyForSingle, + controller: pcb.putController, + ), + ); + + pcb.testAll(); + expect(response.key, fileKeyForSingle); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putBytes can be cancelled.', + () async { + final putController = PutController(); + final key = fileKeyForSingle; + + final statusList = []; + putController.addStatusListener((status) { + statusList.add(status); + if (status == StorageStatus.Request) { + putController.cancel(); + } + }); + final future = storage.putBytes( + bytes, + token, + options: PutOptions( + forceBySingle: true, + key: key, + controller: putController, + ), + ); + try { + await future; + } catch (error) { + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.CANCEL); + } + expect(future, throwsA(TypeMatcher())); + expect(statusList[0], StorageStatus.Init); + expect(statusList[1], StorageStatus.Request); + expect(statusList[2], StorageStatus.Cancel); + + try { + await storage.putBytes( + bytes, + token, + options: PutOptions( + forceBySingle: true, + key: key, + controller: putController, + ), + ); + } catch (error) { + // 复用了相同的 controller,所以也会触发取消的错误 + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.CANCEL); + } + + expect(future, throwsA(TypeMatcher())); + + final response = await storage.putBytes( + bytes, + token, + options: PutOptions(forceBySingle: true, key: key), + ); + + expect(response, isA()); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putBytes\'s status and progress should works well.', + () async { + final pcb = PutControllerBuilder(); + + final response = await storage.putBytes( + bytes, + token, + options: PutOptions( + forceBySingle: true, + key: fileKeyForSingle, + controller: pcb.putController, + ), + ); + expect(response.key, fileKeyForSingle); + pcb.testAll(); + }, + skip: !isSensitiveDataDefined, + ); +} diff --git a/base/test/put/put_by_single/put_file.dart b/base/test/put/put_by_single/put_file.dart deleted file mode 100644 index 8bbe222f..00000000 --- a/base/test/put/put_by_single/put_file.dart +++ /dev/null @@ -1,134 +0,0 @@ -@Timeout(Duration(seconds: 60)) -import 'package:dotenv/dotenv.dart' show env; -import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; -import 'package:test/test.dart'; - -import '../../config.dart'; -import '../helpers.dart'; - -void main() { - configEnv(); - - final storage = Storage(); - - test('putFile customVars should works well.', () async { - final auth = Auth( - accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, - secretKey: env['QINIU_DART_SDK_SECRET_KEY']!, - ); - - final token = auth.generateUploadToken( - putPolicy: PutPolicy( - insertOnly: 0, - scope: env['QINIU_DART_SDK_TOKEN_SCOPE']!, - returnBody: '{"key":"\$(key)","type":"\$(x:type)","ext":"\$(x:ext)"}', - deadline: DateTime.now().millisecondsSinceEpoch + 3600, - ), - ); - - var customVars = { - 'x:type': 'testXType', - 'x:ext': 'testXExt', - }; - - var putController = PutController(); - final response = await storage.putFile( - fileForSingle, - token, - options: PutOptions( - forceBySingle: true, - key: fileKeyForSingle, - customVars: customVars, - controller: putController, - ), - ); - - expect(response.key, fileKeyForSingle); - expect(response.rawData['type'], 'testXType'); - expect(response.rawData['ext'], 'testXExt'); - }, skip: !isSensitiveDataDefined); - - test('putFile should works well.', () async { - var pcb = PutControllerBuilder(); - final response = await storage.putFile( - fileForSingle, - token, - options: PutOptions( - forceBySingle: true, - key: fileKeyForSingle, - controller: pcb.putController, - ), - ); - - pcb.testAll(); - expect(response.key, fileKeyForSingle); - }, skip: !isSensitiveDataDefined); - - test('putFile can be cancelled.', () async { - final putController = PutController(); - final key = fileKeyForSingle; - - final statusList = []; - putController.addStatusListener((status) { - statusList.add(status); - if (status == StorageStatus.Request) { - putController.cancel(); - } - }); - var future = storage.putFile( - fileForSingle, - token, - options: - PutOptions(forceBySingle: true, key: key, controller: putController), - ); - try { - await future; - } catch (error) { - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.CANCEL); - } - expect(future, throwsA(TypeMatcher())); - expect(statusList[0], StorageStatus.Init); - expect(statusList[1], StorageStatus.Request); - expect(statusList[2], StorageStatus.Cancel); - - try { - await storage.putFile( - fileForSingle, - token, - options: PutOptions( - forceBySingle: true, key: key, controller: putController), - ); - } catch (error) { - // 复用了相同的 controller,所以也会触发取消的错误 - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.CANCEL); - } - - expect(future, throwsA(TypeMatcher())); - - final response = await storage.putFile( - fileForSingle, - token, - options: PutOptions(forceBySingle: true, key: key), - ); - - expect(response, isA()); - }, skip: !isSensitiveDataDefined); - - test('putFile\'s status and progress should works well.', () async { - final pcb = PutControllerBuilder(); - - final response = await storage.putFile( - fileForSingle, - token, - options: PutOptions( - forceBySingle: true, - key: fileKeyForSingle, - controller: pcb.putController, - ), - ); - expect(response.key, fileKeyForSingle); - pcb.testAll(); - }, skip: !isSensitiveDataDefined); -} diff --git a/base/test/put/put_by_single/put_file_test.dart b/base/test/put/put_by_single/put_file_test.dart new file mode 100644 index 00000000..e7046172 --- /dev/null +++ b/base/test/put/put_by_single/put_file_test.dart @@ -0,0 +1,155 @@ +@Timeout(Duration(seconds: 60)) +import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; +import 'package:test/test.dart'; + +import '../../config.dart'; +import '../helpers.dart'; + +void main() { + configEnv(); + + final storage = Storage(); + + test( + 'putFile customVars should works well.', + () async { + final auth = Auth( + accessKey: env['QINIU_DART_SDK_ACCESS_KEY']!, + secretKey: env['QINIU_DART_SDK_SECRET_KEY']!, + ); + + final token = auth.generateUploadToken( + putPolicy: PutPolicy( + insertOnly: 0, + scope: env['QINIU_DART_SDK_TOKEN_SCOPE']!, + returnBody: '{"key":"\$(key)","type":"\$(x:type)","ext":"\$(x:ext)"}', + deadline: DateTime.now().millisecondsSinceEpoch + 3600, + ), + ); + + final customVars = { + 'x:type': 'testXType', + 'x:ext': 'testXExt', + }; + + final putController = PutController(); + final response = await storage.putFile( + fileForSingle, + token, + options: PutOptions( + forceBySingle: true, + key: fileKeyForSingle, + customVars: customVars, + controller: putController, + ), + ); + + expect(response.key, fileKeyForSingle); + expect(response.rawData['type'], 'testXType'); + expect(response.rawData['ext'], 'testXExt'); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile should works well.', + () async { + final pcb = PutControllerBuilder(); + final response = await storage.putFile( + fileForSingle, + token, + options: PutOptions( + forceBySingle: true, + key: fileKeyForSingle, + controller: pcb.putController, + ), + ); + + pcb.testAll(); + expect(response.key, fileKeyForSingle); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile can be cancelled.', + () async { + final putController = PutController(); + final key = fileKeyForSingle; + + final statusList = []; + putController.addStatusListener((status) { + statusList.add(status); + if (status == StorageStatus.Request) { + putController.cancel(); + } + }); + final future = storage.putFile( + fileForSingle, + token, + options: PutOptions( + forceBySingle: true, + key: key, + controller: putController, + ), + ); + try { + await future; + } catch (error) { + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.CANCEL); + } + expect(future, throwsA(TypeMatcher())); + expect(statusList[0], StorageStatus.Init); + expect(statusList[1], StorageStatus.Request); + expect(statusList[2], StorageStatus.Cancel); + + try { + await storage.putFile( + fileForSingle, + token, + options: PutOptions( + forceBySingle: true, + key: key, + controller: putController, + ), + ); + } catch (error) { + // 复用了相同的 controller,所以也会触发取消的错误 + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.CANCEL); + } + + expect(future, throwsA(TypeMatcher())); + + final response = await storage.putFile( + fileForSingle, + token, + options: PutOptions(forceBySingle: true, key: key), + ); + + expect(response, isA()); + }, + skip: !isSensitiveDataDefined, + ); + + test( + 'putFile\'s status and progress should works well.', + () async { + final pcb = PutControllerBuilder(); + + final response = await storage.putFile( + fileForSingle, + token, + options: PutOptions( + forceBySingle: true, + key: fileKeyForSingle, + controller: pcb.putController, + ), + ); + expect(response.key, fileKeyForSingle); + pcb.testAll(); + }, + skip: !isSensitiveDataDefined, + ); +} diff --git a/base/test/retry_test.dart b/base/test/retry_test.dart index 70d22707..75628006 100644 --- a/base/test/retry_test.dart +++ b/base/test/retry_test.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; -import 'package:dio/adapter.dart'; import 'package:dio/dio.dart'; import 'package:qiniu_sdk_base/qiniu_sdk_base.dart'; import 'package:qiniu_sdk_base/src/storage/methods/put/by_part/put_parts_task.dart'; @@ -16,161 +15,175 @@ void main() { configEnv(); test( - 'putBySingle\'s retry mechanism should throw error directly while cannot connect to host.', - () async { - final httpAdapter = HttpAdapterTestWithConnectFailedToHost(0); - final config = Config( - hostProvider: HostProviderTest(), - httpClientAdapter: httpAdapter, - retryLimit: 3, - ); - final storage = Storage(config: config); - final statusList = []; - final future = storage.putFile( - File('test_resource/test_for_put.txt'), - token, - options: PutOptions( - controller: PutController()..addStatusListener(statusList.add), - ), - ); - try { - await future; - } catch (error) { - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.UNKNOWN); - } - expect(future, throwsA(isA())); - // 初始 1 次 + 重试 3 次 - expect(httpAdapter.callTimes, 4); - expect(statusList, [ - StorageStatus.Init, - StorageStatus.Request, - StorageStatus.Retry, - StorageStatus.Request, - StorageStatus.Retry, - StorageStatus.Request, - StorageStatus.Retry, - StorageStatus.Request, - StorageStatus.Error - ]); - }, skip: !isSensitiveDataDefined); + 'putBySingle\'s retry mechanism should throw error directly while cannot connect to host.', + () async { + final httpAdapter = HttpAdapterTestWithConnectFailedToHost(0); + final config = Config( + hostProvider: HostProviderTest(), + httpClientAdapter: httpAdapter, + retryLimit: 3, + ); + final storage = Storage(config: config); + final statusList = []; + final future = storage.putFile( + File('test_resource/test_for_put.txt'), + token, + options: PutOptions( + controller: PutController()..addStatusListener(statusList.add), + ), + ); + try { + await future; + } catch (error) { + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.UNKNOWN); + } + expect(future, throwsA(isA())); + // 初始 1 次 + 重试 3 次 + expect(httpAdapter.callTimes, 4); + expect(statusList, [ + StorageStatus.Init, + StorageStatus.Request, + StorageStatus.Retry, + StorageStatus.Request, + StorageStatus.Retry, + StorageStatus.Request, + StorageStatus.Retry, + StorageStatus.Request, + StorageStatus.Error + ]); + }, + skip: !isSensitiveDataDefined, + ); test( - 'putFileByPart\'s retry mechanism should throw error directly while cannot connect to host.', - () async { - final httpAdapter = HttpAdapterTestWithConnectFailedToHost(1); - final cacheProvider = DefaultCacheProvider(); - final config = Config( - cacheProvider: cacheProvider, - hostProvider: HostProviderTest(), - httpClientAdapter: httpAdapter, - ); - final storage = Storage(config: config); - final file = File('test_resource/test_for_put_parts.mp4'); - final resource = FileResource(file: file, length: file.lengthSync()); - final statusList = []; - // 设置一个假的初始化缓存,让分片上传跳过初始化文件,便于测试后面的上传文件流程 - await cacheProvider.setItem(InitPartsTask.getCacheKey(resource.id, null), - json.encode({'expireAt': 0, 'uploadId': '0'})); - final future = storage.putFile(file, token, + 'putFileByPart\'s retry mechanism should throw error directly while cannot connect to host.', + () async { + final httpAdapter = HttpAdapterTestWithConnectFailedToHost(1); + final cacheProvider = DefaultCacheProvider(); + final config = Config( + cacheProvider: cacheProvider, + hostProvider: HostProviderTest(), + httpClientAdapter: httpAdapter, + ); + final storage = Storage(config: config); + final file = File('test_resource/test_for_put_parts.mp4'); + final resource = FileResource(file: file, length: file.lengthSync()); + final statusList = []; + // 设置一个假的初始化缓存,让分片上传跳过初始化文件,便于测试后面的上传文件流程 + await cacheProvider.setItem( + InitPartsTask.getCacheKey(resource.id, null), + json.encode({'expireAt': 0, 'uploadId': '0'}), + ); + final future = storage.putFile( + file, + token, options: PutOptions( partSize: 1, controller: PutController()..addStatusListener(statusList.add), - )); - try { - await future; - } catch (error) { - expect(error, isA()); - expect((error as StorageError).type, StorageErrorType.UNKNOWN); - } - expect(future, throwsA(isA())); - // UploadPartTask 4 次 * 2 个分片 - expect(httpAdapter.callTimes, 8); - expect(statusList, - [StorageStatus.Init, StorageStatus.Request, StorageStatus.Error]); - }, skip: !isSensitiveDataDefined); + ), + ); + try { + await future; + } catch (error) { + expect(error, isA()); + expect((error as StorageError).type, StorageErrorType.UNKNOWN); + } + expect(future, throwsA(isA())); + // UploadPartTask 4 次 * 2 个分片 + expect(httpAdapter.callTimes, 8); + expect( + statusList, + [StorageStatus.Init, StorageStatus.Request, StorageStatus.Error], + ); + }, + skip: !isSensitiveDataDefined, + ); test( - 'retry mechanism should works well with putBySingle while host is unavailable.', - () async { - final config = Config( - hostProvider: HostProviderTest(), - httpClientAdapter: HttpAdapterTestWith502(), - ); - final storage = Storage(config: config); - final putController = PutController(); - final statusList = []; - putController.addStatusListener(statusList.add); - final response = await storage.putFile( - File('test_resource/test_for_put.txt'), - token, - options: PutOptions( - forceBySingle: true, - key: 'test_for_put.txt', - controller: putController, - ), - ); - expect(statusList, [ - StorageStatus.Init, - StorageStatus.Request, - // 重试了 1 次 - StorageStatus.Retry, - // 重试后会重新发请求 - StorageStatus.Request, - StorageStatus.Success - ]); - expect(response.key, 'test_for_put.txt'); - }, skip: !isSensitiveDataDefined); + 'retry mechanism should works well with putBySingle while host is unavailable.', + () async { + final config = Config( + hostProvider: HostProviderTest(), + httpClientAdapter: HttpAdapterTestWith502(), + ); + final storage = Storage(config: config); + final putController = PutController(); + final statusList = []; + putController.addStatusListener(statusList.add); + final response = await storage.putFile( + File('test_resource/test_for_put.txt'), + token, + options: PutOptions( + forceBySingle: true, + key: 'test_for_put.txt', + controller: putController, + ), + ); + expect(statusList, [ + StorageStatus.Init, + StorageStatus.Request, + // 重试了 1 次 + StorageStatus.Retry, + // 重试后会重新发请求 + StorageStatus.Request, + StorageStatus.Success + ]); + expect(response.key, 'test_for_put.txt'); + }, + skip: !isSensitiveDataDefined, + ); test( - 'retry mechanism should works well with putByPart while host is unavailable.', - () async { - final config = Config( - hostProvider: HostProviderTest(), - httpClientAdapter: HttpAdapterTestWith502(), - ); - final storage = Storage(config: config); - final file = File('test_resource/test_for_put_parts.mp4'); - final resource = FileResource(file: file, length: file.lengthSync()); - final key = 'test_for_put_parts.mp4'; - final initPartsTaskStatusList = []; - - // 重试阶段会发生在 InitPartsTask 调用 getUpHost 的时候 - // 手动初始化一个用于测试 - final task = InitPartsTask( + 'retry mechanism should works well with putByPart while host is unavailable.', + () async { + final config = Config( + hostProvider: HostProviderTest(), + httpClientAdapter: HttpAdapterTestWith502(), + ); + final storage = Storage(config: config); + final file = File('test_resource/test_for_put_parts.mp4'); + final resource = FileResource(file: file, length: file.lengthSync()); + final key = 'test_for_put_parts.mp4'; + final initPartsTaskStatusList = []; + + // 重试阶段会发生在 InitPartsTask 调用 getUpHost 的时候 + // 手动初始化一个用于测试 + final task = InitPartsTask( token: token, resource: resource, key: key, controller: PutController() - ..addStatusListener(initPartsTaskStatusList.add)); - storage.taskManager.addTask(task); - - // 接下来是正常流程 - final putController = PutController(); - final statusList = []; - late double _sendPercent, _totalPercent; - putController - ..addStatusListener(statusList.add) - ..addProgressListener((percent) { - _totalPercent = percent; - }) - ..addSendProgressListener((percent) { - _sendPercent = percent; - }); - final response = await storage.putFile( - file, - token, - options: PutOptions( - key: key, - partSize: 1, - controller: putController, - ), - ); - expect(response, isA()); - - expect(_sendPercent, 1); - expect(_totalPercent, 1); - expect( + ..addStatusListener(initPartsTaskStatusList.add), + ); + storage.taskManager.addTask(task); + + // 接下来是正常流程 + final putController = PutController(); + final statusList = []; + late double sendPercent, totalPercent; + putController + ..addStatusListener(statusList.add) + ..addProgressListener((percent) { + totalPercent = percent; + }) + ..addSendProgressListener((percent) { + sendPercent = percent; + }); + final response = await storage.putFile( + file, + token, + options: PutOptions( + key: key, + partSize: 1, + controller: putController, + ), + ); + expect(response, isA()); + + expect(sendPercent, 1); + expect(totalPercent, 1); + expect( initPartsTaskStatusList, equals([ StorageStatus.Init, @@ -178,18 +191,21 @@ void main() { StorageStatus.Retry, StorageStatus.Request, StorageStatus.Success - ])); - expect(statusList[0], StorageStatus.Init); - expect(statusList[1], StorageStatus.Request); - // 分片上传 PutPartsTask 本身不会重试,子任务会去重试,所以没有 Retry 状态 - expect(statusList[2], StorageStatus.Success); - }, skip: !isSensitiveDataDefined); + ]), + ); + expect(statusList[0], StorageStatus.Init); + expect(statusList[1], StorageStatus.Request); + // 分片上传 PutPartsTask 本身不会重试,子任务会去重试,所以没有 Retry 状态 + expect(statusList[2], StorageStatus.Success); + }, + skip: !isSensitiveDataDefined, + ); } // 会扔出 DioError,错误类型是 DioErrorType.other,每个请求调用了 3 次 -class HttpAdapterTestWithConnectFailedToHost extends HttpClientAdapter { +class HttpAdapterTestWithConnectFailedToHost implements HttpClientAdapter { int callTimes = 0; - final DefaultHttpClientAdapter _adapter = DefaultHttpClientAdapter(); + final HttpClientAdapter _adapter = HttpClientAdapter(); // 0 single, 1 parts int type = 0; HttpAdapterTestWithConnectFailedToHost(this.type); @@ -199,8 +215,11 @@ class HttpAdapterTestWithConnectFailedToHost extends HttpClientAdapter { } @override - Future fetch(RequestOptions options, - Stream? requestStream, Future? cancelFuture) async { + Future fetch( + RequestOptions options, + Stream? requestStream, + Future? cancelFuture, + ) async { if (options.path.contains('test.com')) { if ((type == 0 && options.method == 'POST') || (type == 1 && options.method == 'PUT')) { @@ -214,16 +233,19 @@ class HttpAdapterTestWithConnectFailedToHost extends HttpClientAdapter { } // 502 会触发服务不可用逻辑导致该 host 被冻结,并重试其他 host -class HttpAdapterTestWith502 extends HttpClientAdapter { - final DefaultHttpClientAdapter _adapter = DefaultHttpClientAdapter(); +class HttpAdapterTestWith502 implements HttpClientAdapter { + final HttpClientAdapter _adapter = HttpClientAdapter(); @override void close({bool force = false}) { _adapter.close(force: force); } @override - Future fetch(RequestOptions options, - Stream? requestStream, Future? cancelFuture) async { + Future fetch( + RequestOptions options, + Stream? requestStream, + Future? cancelFuture, + ) async { if (options.path.contains('test.com') && options.method == 'POST') { return ResponseBody.fromString('', 502); } @@ -239,8 +261,10 @@ class HostProviderTest extends HostProvider { } @override - Future getUpHost( - {required String accessKey, required String bucket}) async { + Future getUpHost({ + required String accessKey, + required String bucket, + }) async { if (isFrozen('https://test.com')) { return _hostProvider.getUpHost(accessKey: accessKey, bucket: bucket); } diff --git a/base/test/test_coverage.dart b/base/test/test_coverage.dart index cb2e2250..53ebd5e4 100644 --- a/base/test/test_coverage.dart +++ b/base/test/test_coverage.dart @@ -2,8 +2,8 @@ import 'auth/auth_test.dart' as auth_test; import 'config_test.dart' as config_test; import 'put/put_by_parts/put_bytes_test.dart' as put_by_part_bytes_test; import 'put/put_by_parts/put_file_test.dart' as put_by_part_file_test; -import 'put/put_by_single/put_bytes.dart' as put_by_single_bytes_test; -import 'put/put_by_single/put_file.dart' as put_by_single_file_test; +import 'put/put_by_single/put_bytes_test.dart' as put_by_single_bytes_test; +import 'put/put_by_single/put_file_test.dart' as put_by_single_file_test; import 'resource_test.dart' as resource_test; import 'retry_test.dart' as retry_test; diff --git a/base/test_resource/test_for_put.txt b/base/test_resource/test_for_put.txt old mode 100755 new mode 100644 diff --git a/flutter/.pubignore b/flutter/.pubignore new file mode 100644 index 00000000..cc7e8921 --- /dev/null +++ b/flutter/.pubignore @@ -0,0 +1,2 @@ +pubspec.lock +example/pubspec.lock \ No newline at end of file diff --git a/flutter/CHANGELOG.md b/flutter/CHANGELOG.md index 1dcad2a9..1daac798 100644 --- a/flutter/CHANGELOG.md +++ b/flutter/CHANGELOG.md @@ -1,16 +1,25 @@ -## [0.1.0] +## 0.5.0 -* Initial Release. +* Upgrade dio to 5.0.0 -## [0.2.0] +Breaking Changes +* Dart >= 2.15 & Flutter >= 2.8.0 +* Deprecated `PutByPartOptions` +* Deprecated `PutBySingleOptions` -* 优化了 `StorageError` 输出的调用栈 -* `CacheProvider` 的方法都改成异步的 +## 0.4.0 + +* 增加 putBytes 支持 WEB 平台(#50) ## 0.3.0 * 增加 null-safety(#44) -## 0.4.0 +## [0.2.0] -* 增加 putBytes 支持 WEB 平台(#50) +* 优化了 `StorageError` 输出的调用栈 +* `CacheProvider` 的方法都改成异步的 + +## [0.1.0] + +* Initial Release. diff --git a/flutter/README.md b/flutter/README.md index a77287f3..3c3a1ff1 100644 --- a/flutter/README.md +++ b/flutter/README.md @@ -152,6 +152,11 @@ import 'package:qiniu_flutter_sdk/qiniu_flutter_sdk.dart'; ### 其他说明 1. 如果您想了解更多七牛的上传策略,建议您仔细阅读 [七牛官方文档-上传](https://developer.qiniu.com/kodo/manual/upload-types)。另外,七牛的上传策略是在后端服务指定的. +2. 如果Flutter SDK版本低于 3.0.0,需要override flutter_lints版本 +```yaml +dependency_overrides: + flutter_lints: ^1.0.0 +``` ## 功能列表 diff --git a/flutter/analysis_options.yaml b/flutter/analysis_options.yaml index 96c1e8bf..91e0eee3 100644 --- a/flutter/analysis_options.yaml +++ b/flutter/analysis_options.yaml @@ -1,6 +1,6 @@ # copy from https://github.com/dart-lang/http/blob/master/analysis_options.yaml -include: package:pedantic/analysis_options.yaml +include: package:flutter_lints/flutter.yaml analyzer: # enable-experiment: @@ -12,79 +12,9 @@ analyzer: linter: rules: - - annotate_overrides - - avoid_bool_literals_in_conditional_expressions - - avoid_classes_with_only_static_members - - avoid_empty_else - - avoid_function_literals_in_foreach_calls - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - - avoid_relative_lib_imports - - avoid_renaming_method_parameters - - avoid_return_types_on_setters - - avoid_returning_null_for_void - - avoid_returning_this - - avoid_shadowing_type_parameters - - avoid_single_cascade_in_expression_statements - - avoid_types_as_parameter_names - - avoid_unused_constructor_parameters - - await_only_futures - - camel_case_types - - cascade_invocations - # comment_references - - control_flow_in_finally - - curly_braces_in_flow_control_structures - - directives_ordering - - empty_catches - - empty_constructor_bodies - - empty_statements - - file_names - - hash_and_equals - - invariant_booleans - - iterable_contains_unrelated_type - - library_names - - library_prefixes - - list_remove_unrelated_type - - no_adjacent_strings_in_list - - no_duplicate_case_values - - non_constant_identifier_names - - null_closures - - omit_local_variable_types - - only_throw_errors - - overridden_fields - - package_names - - package_prefixed_library_names - - prefer_adjacent_string_concatenation - - prefer_conditional_assignment - - prefer_contains - - prefer_equal_for_default_values - - prefer_final_fields - - prefer_collection_literals - - prefer_generic_function_type_aliases - - prefer_initializing_formals - - prefer_is_empty - - prefer_is_not_empty - - prefer_null_aware_operators - - prefer_single_quotes - - prefer_typing_uninitialized_variables - - recursive_getters - - slash_for_doc_comments - - test_types_in_equals - - throw_in_finally - - type_init_formals - - unawaited_futures - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - - unnecessary_lambdas - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - - unnecessary_overrides - - unnecessary_parenthesis - - unnecessary_statements - - unnecessary_this - - unrelated_type_equality_checks - - use_rethrow_when_possible - - valid_regexps - - void_checks + constant_identifier_names: false + prefer_final_locals: true + prefer_single_quotes: true + always_declare_return_types: true + require_trailing_commas: true + public_member_api_docs: true diff --git a/flutter/example/.gitignore b/flutter/example/.gitignore index 0fa6b675..2ee9b90c 100644 --- a/flutter/example/.gitignore +++ b/flutter/example/.gitignore @@ -44,3 +44,5 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +pubspec.lock \ No newline at end of file diff --git a/flutter/example/.metadata b/flutter/example/.metadata index e09c69f5..a0dcf313 100644 --- a/flutter/example/.metadata +++ b/flutter/example/.metadata @@ -1,10 +1,36 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 37ebe3d82a9d5faeda7d3c1a6ad193030210a2cc - channel: dev + revision: c07f7888888435fd9df505aa2efc38d3cf65681b + channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + - platform: android + create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + - platform: ios + create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + - platform: macos + create_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + base_revision: c07f7888888435fd9df505aa2efc38d3cf65681b + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/flutter/example/android/app/build.gradle b/flutter/example/android/app/build.gradle index 0bdfa3a0..c498abee 100644 --- a/flutter/example/android/app/build.gradle +++ b/flutter/example/android/app/build.gradle @@ -27,6 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -43,7 +44,9 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.qiniu.flutter_sdk_example" + applicationId "com.qiniu.example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() diff --git a/flutter/example/android/app/src/debug/AndroidManifest.xml b/flutter/example/android/app/src/debug/AndroidManifest.xml index 4ef3824b..07fcd91c 100644 --- a/flutter/example/android/app/src/debug/AndroidManifest.xml +++ b/flutter/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,7 @@ - diff --git a/flutter/example/android/app/src/main/AndroidManifest.xml b/flutter/example/android/app/src/main/AndroidManifest.xml index 272ce9b7..2795db8e 100644 --- a/flutter/example/android/app/src/main/AndroidManifest.xml +++ b/flutter/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.qiniu.example"> diff --git a/flutter/example/android/build.gradle b/flutter/example/android/build.gradle index 4256f917..58a8c74b 100644 --- a/flutter/example/android/build.gradle +++ b/flutter/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.6.10' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/flutter/example/android/gradle/wrapper/gradle-wrapper.properties b/flutter/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58af..3c472b99 100644 --- a/flutter/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/flutter/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 66c177f2..4ed915ab 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -16,11 +16,13 @@ import 'widgets/select_file.dart'; import 'widgets/string_input.dart'; void main() { - runApp(App(child: Base())); + runApp(const App(child: Base())); } // 基础的上传示例 class Base extends StatefulWidget implements Example { + const Base({Key? key}) : super(key: key); + @override String get title => '基础上传示例'; @@ -57,16 +59,6 @@ class BaseState extends DisposableState { // 控制器,可以用于取消任务、获取上述的状态,进度等信息 PutController? putController; - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - super.dispose(); - } - void onStatus(StorageStatus status) { printToConsole('状态变化: 当前任务状态:${status.toString()}'); setState(() => statusValue = status); @@ -107,7 +99,7 @@ class BaseState extends DisposableState { var usedToken = token; if (token == null || token == '') { - if (builtinToken != null && builtinToken != '') { + if (builtinToken.isNotEmpty) { printToConsole('使用内建 Token 进行上传'); usedToken = builtinToken; } @@ -183,6 +175,7 @@ class BaseState extends DisposableState { } printToConsole('------------------------'); + return PutResponse.fromJson({'error': error}); }); } @@ -240,24 +233,21 @@ class BaseState extends DisposableState { Widget get cancelButton { if (statusValue == StorageStatus.Request) { return Padding( - padding: EdgeInsets.all(10), - child: RaisedButton( - child: Text('取消上传'), + padding: const EdgeInsets.all(10), + child: ElevatedButton( + child: const Text('取消上传'), onPressed: () => putController?.cancel(), - padding: EdgeInsets.symmetric(vertical: 10, horizontal: 40), - textColor: Colors.white, - color: Colors.red, ), ); } - return SizedBox.shrink(); + return const SizedBox.shrink(); } @override Widget build(BuildContext context) { return ListView(children: [ Padding( - padding: EdgeInsets.all(20), + padding: const EdgeInsets.all(20), child: Progress(progressValue), ), Padding( @@ -287,10 +277,10 @@ class BaseState extends DisposableState { ), // 取消按钮 cancelButton, - Padding( + const Padding( key: Key('console'), - child: const Console(), padding: EdgeInsets.all(8.0), + child: Console(), ), ]); } diff --git a/flutter/example/lib/token.dart b/flutter/example/lib/token.dart index b3e5c80f..dc18e060 100644 --- a/flutter/example/lib/token.dart +++ b/flutter/example/lib/token.dart @@ -1,2 +1,2 @@ -final String? builtinToken = +const builtinToken = 'lVgtk5xr03Oz_uvkzDtQ8LtpiEUWx5tGEDUZVg1y:KBGQR5DaE7J03MYSrlF9kxtE8EY=:eyJyZXR1cm5Cb2R5Ijoie1wia2V5XCI6ICQoa2V5KX0iLCJzY29wZSI6InFpbml1LWRhcnQtc2RrIiwiZGVhZGxpbmUiOjE2MTg4NjIxMDZ9'; diff --git a/flutter/example/lib/utils/uint.dart b/flutter/example/lib/utils/uint.dart index 8dbe00ab..e5883777 100644 --- a/flutter/example/lib/utils/uint.dart +++ b/flutter/example/lib/utils/uint.dart @@ -31,12 +31,12 @@ String humanizeBigNumber( displayValue = value.toStringAsFixed(decimal); } - return displayValue + ' ' + units[index]; + return '$displayValue ${units[index]}'; } String humanizeFileSize( double value, { int decimal = 2, }) { - return humanizeBigNumber(value, decimal: decimal, radix: 1024) + 'B'; + return '${humanizeBigNumber(value, decimal: decimal, radix: 1024)}B'; } diff --git a/flutter/example/lib/widgets/app.dart b/flutter/example/lib/widgets/app.dart index 67e76013..1295249b 100644 --- a/flutter/example/lib/widgets/app.dart +++ b/flutter/example/lib/widgets/app.dart @@ -25,7 +25,7 @@ class App extends StatelessWidget { appBar: AppBar( title: Text( child.title, - style: TextStyle( + style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), diff --git a/flutter/example/lib/widgets/console.dart b/flutter/example/lib/widgets/console.dart index a32d8b42..83cfe367 100644 --- a/flutter/example/lib/widgets/console.dart +++ b/flutter/example/lib/widgets/console.dart @@ -93,8 +93,8 @@ class ConsoleState extends DisposableState { color: Colors.green[800], ), child: Container( - padding: EdgeInsets.all(10), - decoration: BoxDecoration( + padding: const EdgeInsets.all(10), + decoration: const BoxDecoration( color: Colors.black87, borderRadius: BorderRadius.all(Radius.circular(4)), ), diff --git a/flutter/example/lib/widgets/progress.dart b/flutter/example/lib/widgets/progress.dart index 5393cd32..4d30b707 100644 --- a/flutter/example/lib/widgets/progress.dart +++ b/flutter/example/lib/widgets/progress.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; class Progress extends StatelessWidget { + const Progress(this.value, {Key? key}) : super(key: key); + final double value; - Progress(this.value); Widget get progress { return Container( width: 200, height: 200, - padding: EdgeInsets.all(20), + padding: const EdgeInsets.all(20), child: CircularProgressIndicator( value: value, strokeWidth: 14, @@ -20,10 +21,10 @@ class Progress extends StatelessWidget { Widget get progressText { final integer = (value * 100).toInt(); final decimal = (((value * 100) - integer) * 100).toInt(); - final unit = '%'; + const unit = '%'; return DefaultTextStyle( - style: TextStyle( + style: const TextStyle( color: Colors.black87, fontWeight: FontWeight.bold, ), @@ -32,12 +33,12 @@ class Progress extends StatelessWidget { children: [ Text( integer.toString(), - style: TextStyle(fontSize: 50), + style: const TextStyle(fontSize: 50), ), Column( children: [ - Text(unit.toString(), style: TextStyle(fontSize: 14)), - Text('.$decimal', style: TextStyle(fontSize: 18)), + Text(unit.toString(), style: const TextStyle(fontSize: 14)), + Text('.$decimal', style: const TextStyle(fontSize: 18)), ], ) ], diff --git a/flutter/example/lib/widgets/select_file.dart b/flutter/example/lib/widgets/select_file.dart index 75bf8178..bed0457f 100644 --- a/flutter/example/lib/widgets/select_file.dart +++ b/flutter/example/lib/widgets/select_file.dart @@ -4,8 +4,9 @@ import 'package:flutter/material.dart'; typedef OnSelected = void Function(PlatformFile file); class SelectFile extends StatefulWidget { + const SelectFile(this.onSelected, {Key? key}) : super(key: key); + final OnSelected onSelected; - const SelectFile(this.onSelected); @override State createState() { @@ -23,15 +24,12 @@ class SelectFileState extends State { } Widget get selectButton { - return Container( + return SizedBox( width: double.maxFinite, - child: RaisedButton.icon( - label: Text('点击选择文件'), - icon: Icon(Icons.folder), + child: ElevatedButton.icon( + label: const Text('点击选择文件'), + icon: const Icon(Icons.folder), onPressed: openSelectFileWindow, - padding: EdgeInsets.symmetric(vertical: 10, horizontal: 40), - textColor: Colors.white, - color: Colors.blue, ), ); } diff --git a/flutter/example/lib/widgets/string_input.dart b/flutter/example/lib/widgets/string_input.dart index ca63ea4a..6be566ec 100644 --- a/flutter/example/lib/widgets/string_input.dart +++ b/flutter/example/lib/widgets/string_input.dart @@ -2,21 +2,23 @@ import 'package:flutter/material.dart'; class StringInput extends StatelessWidget { final String label; + final String value; final void Function(String token) onChange; - const StringInput(this.onChange, {Key? key, required this.label}) : super(key: key); + const StringInput(this.onChange, + {Key? key, required this.label, this.value = ''}) + : super(key: key); @override Widget build(BuildContext context) { - return Container( - child: TextField( - onSubmitted: onChange, - decoration: InputDecoration( - labelText: label, - contentPadding: EdgeInsets.all(20.0), - border: OutlineInputBorder( - borderSide: BorderSide(width: 2), // 设置无效,好像有 BUG - borderRadius: BorderRadius.circular(0.0), - ), + return TextField( + onSubmitted: onChange, + controller: TextEditingController(text: value), + decoration: InputDecoration( + labelText: label, + contentPadding: const EdgeInsets.all(20.0), + border: OutlineInputBorder( + borderSide: const BorderSide(width: 2), // 设置无效,好像有 BUG + borderRadius: BorderRadius.circular(0.0), ), ), ); diff --git a/flutter/example/pubspec.lock b/flutter/example/pubspec.lock deleted file mode 100644 index 9ff3f878..00000000 --- a/flutter/example/pubspec.lock +++ /dev/null @@ -1,236 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - dio: - dependency: transitive - description: - name: dio - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.4" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - file_picker: - dependency: "direct main" - description: - name: file_picker - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.4" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.0" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.3" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.11" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.3" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" - qiniu_flutter_sdk: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.3.0" - qiniu_sdk_base: - dependency: transitive - description: - path: "../../base" - relative: true - source: path - version: "0.4.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.8" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - uuid: - dependency: transitive - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.5" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" -sdks: - dart: ">=2.14.0 <3.0.0" - flutter: ">=2.0.0" diff --git a/flutter/example/pubspec.yaml b/flutter/example/pubspec.yaml index 7e62d3e0..f113725f 100644 --- a/flutter/example/pubspec.yaml +++ b/flutter/example/pubspec.yaml @@ -20,6 +20,10 @@ version: 1.0.0+1 environment: sdk: ">=2.12.0 <3.0.0" +dependency_overrides: + qiniu_sdk_base: + path: ../../base/ + dependencies: flutter: sdk: flutter @@ -32,6 +36,7 @@ dependencies: cupertino_icons: ^1.0.2 dev_dependencies: + flutter_lints: flutter_test: sdk: flutter diff --git a/flutter/lib/src/storage/controller.dart b/flutter/lib/src/storage/controller.dart index 999c77b0..46cd8df3 100644 --- a/flutter/lib/src/storage/controller.dart +++ b/flutter/lib/src/storage/controller.dart @@ -1,3 +1,4 @@ import 'package:qiniu_sdk_base/qiniu_sdk_base.dart' as base; +/// PutController class PutController extends base.PutController {} diff --git a/flutter/lib/src/storage/storage.dart b/flutter/lib/src/storage/storage.dart index 75da5d55..2012b77b 100644 --- a/flutter/lib/src/storage/storage.dart +++ b/flutter/lib/src/storage/storage.dart @@ -14,16 +14,18 @@ export 'package:qiniu_sdk_base/qiniu_sdk_base.dart' QiniuError, StorageError, StorageErrorType, - PutByPartOptions, - StorageStatus, - PutBySingleOptions; + StorageStatus; export './controller.dart'; +/// Storage class Storage { - final base.Storage _baseStorage; + /// Storage Storage({base.Config? config}) : _baseStorage = base.Storage(config: config); + final base.Storage _baseStorage; + + /// putFile Future putFile( File file, String token, { @@ -32,6 +34,7 @@ class Storage { return _baseStorage.putFile(file, token, options: options); } + /// putBytes Future putBytes( Uint8List bytes, String token, { diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index f757f1df..860d4496 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -1,20 +1,20 @@ name: qiniu_flutter_sdk description: Qiniu Flutter sdk -version: 0.4.0 +version: 0.5.0 homepage: https://github.com/qiniu/dart-sdk/tree/master/flutter environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=1.17.0" + sdk: ">=2.15.0 <4.0.0" + flutter: ">=2.8.0" dependencies: flutter: sdk: flutter - dio: ^4.0.0 - qiniu_sdk_base: ^0.4.0 + dio: ^5.0.0 + qiniu_sdk_base: ^0.5.0 dev_dependencies: - pedantic: ^1.9.0 + flutter_lints: flutter_test: sdk: flutter