Browse Source

add qiniu plugins

wr_202303
zsw 4 months ago
parent
commit
d3b44ac15c
  1. 58
      qiniu-dart-sdk/.travis.yml
  2. 76
      qiniu-dart-sdk/CODE_OF_CONDUCT.md
  3. 15
      qiniu-dart-sdk/README.md
  4. 12
      qiniu-dart-sdk/base/CHANGELOG.md
  5. 201
      qiniu-dart-sdk/base/LICENSE
  6. 29
      qiniu-dart-sdk/base/README.md
  7. 90
      qiniu-dart-sdk/base/analysis_options.yaml
  8. 5
      qiniu-dart-sdk/base/lib/qiniu_sdk_base.dart
  9. 125
      qiniu-dart-sdk/base/lib/src/auth/auth.dart
  10. 209
      qiniu-dart-sdk/base/lib/src/auth/put_policy.dart
  11. 12
      qiniu-dart-sdk/base/lib/src/error/error.dart
  12. 39
      qiniu-dart-sdk/base/lib/src/storage/config/cache.dart
  13. 28
      qiniu-dart-sdk/base/lib/src/storage/config/config.dart
  14. 124
      qiniu-dart-sdk/base/lib/src/storage/config/host.dart
  15. 11
      qiniu-dart-sdk/base/lib/src/storage/config/protocol.dart
  16. 75
      qiniu-dart-sdk/base/lib/src/storage/error/error.dart
  17. 20
      qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/cache_mixin.dart
  18. 51
      qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/complete_parts_task.dart
  19. 92
      qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/init_parts_task.dart
  20. 26
      qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/part.dart
  21. 25
      qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/put_by_part_options.dart
  22. 187
      qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/put_parts_task.dart
  23. 112
      qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/upload_part_task.dart
  24. 260
      qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/upload_parts_task.dart
  25. 13
      qiniu-dart-sdk/base/lib/src/storage/methods/put/by_single/put_by_single_options.dart
  26. 60
      qiniu-dart-sdk/base/lib/src/storage/methods/put/by_single/put_by_single_task.dart
  27. 5
      qiniu-dart-sdk/base/lib/src/storage/methods/put/put.dart
  28. 3
      qiniu-dart-sdk/base/lib/src/storage/methods/put/put_controller.dart
  29. 28
      qiniu-dart-sdk/base/lib/src/storage/methods/put/put_options.dart
  30. 33
      qiniu-dart-sdk/base/lib/src/storage/methods/put/put_response.dart
  31. 21
      qiniu-dart-sdk/base/lib/src/storage/status/status.dart
  32. 99
      qiniu-dart-sdk/base/lib/src/storage/storage.dart
  33. 207
      qiniu-dart-sdk/base/lib/src/storage/task/request_task.dart
  34. 97
      qiniu-dart-sdk/base/lib/src/storage/task/request_task_controller.dart
  35. 15
      qiniu-dart-sdk/base/lib/src/storage/task/request_task_manager.dart
  36. 50
      qiniu-dart-sdk/base/lib/src/storage/task/task.dart
  37. 66
      qiniu-dart-sdk/base/lib/src/storage/task/task_manager.dart
  38. 461
      qiniu-dart-sdk/base/pubspec.lock
  39. 18
      qiniu-dart-sdk/base/pubspec.yaml
  40. 11
      qiniu-dart-sdk/codecov.yml
  41. 8
      qiniu-dart-sdk/flutter/CHANGELOG.md
  42. 201
      qiniu-dart-sdk/flutter/LICENSE
  43. 182
      qiniu-dart-sdk/flutter/README.md
  44. 90
      qiniu-dart-sdk/flutter/analysis_options.yaml
  45. 3
      qiniu-dart-sdk/flutter/lib/qiniu_flutter_sdk.dart
  46. 3
      qiniu-dart-sdk/flutter/lib/src/storage/controller.dart
  47. 33
      qiniu-dart-sdk/flutter/lib/src/storage/storage.dart
  48. 228
      qiniu-dart-sdk/flutter/pubspec.lock
  49. 23
      qiniu-dart-sdk/flutter/pubspec.yaml

58
qiniu-dart-sdk/.travis.yml

@ -0,0 +1,58 @@
language: dart
dart:
- stable
install:
- cd $TRAVIS_BUILD_DIR/base && pub get
# - cd $$TRAVIS_BUILD_DIR/flutter && pub get
before_script:
- cd $TRAVIS_BUILD_DIR/base && rm -rf .env
jobs:
include:
#######################################
######### jobs for base #############
#######################################
# 检查 lint 并在 warnings、infos 时报错退出
- stage: base(analyze,format,test)
name: "Analyze"
os: linux
script: cd $TRAVIS_BUILD_DIR/base && dartanalyzer --fatal-warnings --fatal-infos .
# 检查格式并在异常时退出
- stage: base(analyze,format,test)
name: "Format"
os: linux
script: cd $TRAVIS_BUILD_DIR/base && dartfmt -n --set-exit-if-changed .
# 执行测试(已开启 null safe)
- stage: base(analyze,format,test)
name: "Vm Tests"
os: linux
script: cd $TRAVIS_BUILD_DIR/base && pub run test_coverage --print-test-output && bash <(curl -s https://codecov.io/bash)
#######################################
####### jobs for flutter_sdk ##########
#######################################
# - stage: flutter_sdk(analyze,format,test)
# name: "Analyze"
# os: linux
# script: cd $TRAVIS_BUILD_DIR/flutter
# - stage: flutter_sdk(analyze,format,test)
# name: "Format"
# os: linux
# script: cd $TRAVIS_BUILD_DIR/flutter
# - stage: flutter_sdk(analyze,format,test)
# name: "Vm Tests"
# os: linux
# script: cd $TRAVIS_BUILD_DIR/flutter
stages:
- base(analyze,format,test)
# - flutter_sdk(analyze,format,test)
# - flutter_sdk_example(analyze,format,test)
cache:
directories:
- $HOME/.pub-cache

76
qiniu-dart-sdk/CODE_OF_CONDUCT.md

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at yinxulai@qiniu.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

15
qiniu-dart-sdk/README.md

@ -0,0 +1,15 @@
# Dart SDK
[![codecov](https://codecov.io/gh/qiniu/dart-sdk/branch/master/graph/badge.svg?token=5VOX6NJTKF)](https://codecov.io/gh/qiniu/dart-sdk)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![qiniu_sdk_base](https://img.shields.io/pub/v/qiniu_sdk_base.svg?label=qiniu_sdk_base)](https://pub.dev/packages/qiniu_sdk_base)
[![qiniu_flutter_sdk](https://img.shields.io/pub/v/qiniu_flutter_sdk.svg?label=qiniu_flutter_sdk)](https://pub.dev/packages/qiniu_flutter_sdk)
## 目录说明
- base 封装了七牛各业务的基础实现
- flutter 该目录是 base + Flutter 的绑定实现,同时导出为单独的 package 提供给用户使用
### [Flutter SDK](https://github.com/qiniu/dart-sdk/tree/master/flutter)
七牛云业务基于 Dart 绑定 Flutter 的实现,为 Flutter 提供简易的使用方式,更多信息查看该目录下的 [README.md](https://github.com/qiniu/dart-sdk/tree/master/flutter/README.md) 文件。

12
qiniu-dart-sdk/base/CHANGELOG.md

@ -0,0 +1,12 @@
## 0.1.0
- Initial Release.
## 0.2.0
- 优化了 `StorageError` 输出的调用栈
- `CacheProvider` 的方法都改成异步的
## 0.2.1
- 修复关闭 App 缓存丢失的问题

201
qiniu-dart-sdk/base/LICENSE

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

29
qiniu-dart-sdk/base/README.md

@ -0,0 +1,29 @@
# Qiniu Sdk Base [![qiniu_sdk_base](https://img.shields.io/pub/v/qiniu_sdk_base.svg?label=qiniu_sdk_base)](https://pub.dev/packages/qiniu_sdk_base) [![codecov](https://codecov.io/gh/qiniu/dart-sdk/branch/master/graph/badge.svg?token=5VOX6NJTKF)](https://codecov.io/gh/qiniu/dart-sdk)
七牛 dart 平台 sdk 的 base 包,为上层 sdk 提供基础设施和共享代码。
## 功能列表
* 单文件上传
* 分片上传
* 任务状态
* 任务进度
* 上传进度
* 失败重试
## 如何测试
创建 `.env` 文件,并输入如下内容
```
export QINIU_DART_SDK_ACCESS_KEY=
export QINIU_DART_SDK_SECRET_KEY=
export QINIU_DART_SDK_TOKEN_SCOPE=
```
`.env` 文件中填好敏感数据,即 ak、sk、scope
接着运行如下指令
`pub run test`

90
qiniu-dart-sdk/base/analysis_options.yaml

@ -0,0 +1,90 @@
# copy from https://github.com/dart-lang/http/blob/master/analysis_options.yaml
include: package:pedantic/analysis_options.yaml
analyzer:
# enable-experiment:
# - non-nullable
strong-mode:
implicit-casts: false
implicit-dynamic: false
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

5
qiniu-dart-sdk/base/lib/qiniu_sdk_base.dart

@ -0,0 +1,5 @@
library qiniu_sdk_base;
export 'src/auth/auth.dart';
export 'src/error/error.dart';
export 'src/storage/storage.dart';

125
qiniu-dart-sdk/base/lib/src/auth/auth.dart

@ -0,0 +1,125 @@
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:meta/meta.dart';
import './put_policy.dart';
export 'put_policy.dart';
class TokenInfo {
final String accessKey;
final PutPolicy putPolicy;
const TokenInfo(this.accessKey, this.putPolicy);
}
///
///
/// [-](https://developer.qiniu.com/kodo/manual/1644/security)
class Auth {
/// [accessKey]
///
/// [- AccessKey/SecretKey](https://developer.qiniu.com/kodo/manual/1644/security#aksk)
/// 使[-使](https://developer.qiniu.com/kodo/kb/1334/the-access-key-secret-key-encryption-key-safe-use-instructions)
final String accessKey;
/// [secretKey]
///
/// 使 [accessKey]
final String secretKey;
const Auth({
@required this.accessKey,
@required this.secretKey,
}) : assert(accessKey != null),
assert(secretKey != null);
/// 使 Token
///
/// [PutPolicy]
String generateUploadToken({
@required PutPolicy putPolicy,
}) {
assert(putPolicy != null);
var data = jsonEncode(putPolicy);
var encodedPutPolicy = base64Url.encode(utf8.encode(data));
var baseToken = generateAccessToken(bytes: utf8.encode(encodedPutPolicy));
return '$baseToken:$encodedPutPolicy';
}
/// Token
///
/// [key]
/// [deadline] 1451491200
/// [bucketDomain] http://test.bucket.com
String generateDownloadToken({
@required String key,
@required int deadline,
@required String bucketDomain,
}) {
assert(key != null);
assert(deadline != null);
assert(bucketDomain != null);
var downloadURL = '$bucketDomain/$key?e=$deadline';
return generateAccessToken(bytes: utf8.encode(downloadURL));
}
/// Token访
///
/// 访, Token
String generateAccessToken({@required List<int> bytes}) {
assert(bytes != null);
var hmacEncoder = Hmac(sha1, utf8.encode(secretKey));
var sign = hmacEncoder.convert(bytes);
var encodedSign = base64Url.encode(sign.bytes);
return '$accessKey:$encodedSign';
}
/// token
///
/// Token [accessKey][PutPolicy]
static TokenInfo parseToken(String token) {
assert(token != null && token != '');
var segments = token.split(':');
if (segments.length < 2) {
throw ArgumentError('invalid token');
}
PutPolicy putPolicy;
var accessKey = segments.first;
/// token
/// [](https://github.com/qbox/product/blob/master/kodo/auths/UpToken.md#admin-uptoken-authorization)
if (segments.length >= 3) {
if (segments.last == '') {
throw ArgumentError('invalid token');
}
putPolicy = PutPolicy.fromJson(jsonDecode(
String.fromCharCodes(
base64Url.decode(
segments.last,
),
),
) as Map<String, dynamic>);
}
return TokenInfo(accessKey, putPolicy);
}
/// up token
///
/// Token [accessKey][PutPolicy]
static TokenInfo parseUpToken(String token) {
assert(token != null && token != '');
final tokenInfo = parseToken(token);
if (tokenInfo.putPolicy == null) {
throw ArgumentError('invalid up token');
}
return tokenInfo;
}
}

209
qiniu-dart-sdk/base/lib/src/auth/put_policy.dart

@ -0,0 +1,209 @@
import 'package:meta/meta.dart';
///
///
/// [-](https://developer.qiniu.com/kodo/manual/1206/put-policy)
class PutPolicy {
/// Bucket Key 750
///
///
/// <Bucket> Bucket
/// <Bucket>:<Key> Key
/// <Bucket>:<KeyPrefix> KeyPrefix
///
final String scope;
/// Bucket
///
/// [scope] Bucket
String getBucket() {
return scope.split(':').first;
}
/// 1 [scope] KeyPrefix
final int isPrefixalScope;
///
///
/// Unix
///
/// + 3600s
final int deadline;
///
///
/// 0 [scope]
final int insertOnly;
///
///
/// App-Client
final String endUser;
/// Web 303 URL
///
/// <[returnUrl]>?upload_ret=<QueryString>
/// <QueryString> [returnBody]
/// [returnUrl] [returnBody]
final String returnUrl;
/// [returnBody]
///
/// 使 <> <> JSON
/// <> [-](https://developer.qiniu.com/kodo/manual/1235/vars#magicvar)
/// <> [-](https://developer.qiniu.com/kodo/manual/1235/vars#xvar)
final String returnBody;
/// POST URL
final String callbackUrl;
/// Host
///
/// [callbackUrl] 使 [callbackUrl]
final String callbackHost;
///
///
/// Content-Type: application/x-www-form-urlencoded POST
/// :{"key":"$(key)","hash":"$(etag)","w":"$(imageInfo.width)","h":"$(imageInfo.height)"}
/// 使 <> <>
final String callbackBody;
/// Content-Type
///
/// application/x-www-form-urlencoded application/json
final String callbackBodyType;
///
///
/// [fileType] = 2使
/// API ;
/// 使 <> <>
/// 使[-#persistentOps](https://developer.qiniu.com/kodo/manual/1206/put-policy#persistentOps)
final String persistentOps;
/// URL
///
/// POST HTTP/1.1 200 OK URL
/// URL
/// body Content-Type application/json POST
/// body
final String persistentNotifyUrl;
///
///
///
/// 使使
final String persistentPipeline;
/// [saveKey]
///
/// true [saveKey] Key使 [saveKey]
/// false
final String forceSaveKey;
///
///
/// <><>, [forceSaveKey] false
/// key
/// [forceSaveKey] true
final String saveKey;
/// Byte
final int fsizeMin;
/// Byte
///
/// 413
final int fsizeLimit;
/// MimeType
final int detectMime;
///
final String mimeLimit;
///
///
/// 0
/// 1
/// 2
final int fileType;
const PutPolicy({
@required this.scope,
@required this.deadline,
this.isPrefixalScope,
this.insertOnly,
this.endUser,
this.returnUrl,
this.returnBody,
this.callbackUrl,
this.callbackHost,
this.callbackBody,
this.callbackBodyType,
this.persistentOps,
this.persistentNotifyUrl,
this.persistentPipeline,
this.forceSaveKey,
this.saveKey,
this.fsizeMin,
this.fsizeLimit,
this.detectMime,
this.mimeLimit,
this.fileType,
}) : assert(scope != null),
assert(deadline != null);
Map<String, dynamic> toJson() {
return <String, dynamic>{
'scope': scope,
'isPrefixalScope': isPrefixalScope,
'deadline': deadline,
'insertOnly': insertOnly,
'endUser': endUser,
'returnUrl': returnUrl,
'returnBody': returnBody,
'callbackUrl': callbackUrl,
'callbackHost': callbackHost,
'callbackBody': callbackBody,
'callbackBodyType': callbackBodyType,
'persistentOps': persistentOps,
'persistentNotifyUrl': persistentNotifyUrl,
'persistentPipeline': persistentPipeline,
'forceSaveKey': forceSaveKey,
'saveKey': saveKey,
'fsizeMin': fsizeMin,
'fsizeLimit': fsizeLimit,
'detectMime': detectMime,
'mimeLimit': mimeLimit,
'fileType': fileType,
}..removeWhere((key, dynamic value) => value == null);
}
factory PutPolicy.fromJson(Map<String, dynamic> json) {
return PutPolicy(
scope: json['scope'] as String,
deadline: json['deadline'] as int,
isPrefixalScope: json['isPrefixalScope'] as int,
insertOnly: json['insertOnly'] as int,
endUser: json['endUser'] as String,
returnUrl: json['returnUrl'] as String,
returnBody: json['returnBody'] as String,
callbackUrl: json['callbackUrl'] as String,
callbackHost: json['callbackHost'] as String,
callbackBody: json['callbackBody'] as String,
callbackBodyType: json['callbackBodyType'] as String,
persistentOps: json['persistentOps'] as String,
persistentNotifyUrl: json['persistentNotifyUrl'] as String,
persistentPipeline: json['persistentPipeline'] as String,
forceSaveKey: json['forceSaveKey'] as String,
saveKey: json['saveKey'] as String,
fsizeMin: json['fsizeMin'] as int,
fsizeLimit: json['fsizeLimit'] as int,
detectMime: json['detectMime'] as int,
mimeLimit: json['mimeLimit'] as String,
fileType: json['fileType'] as int,
);
}
}

12
qiniu-dart-sdk/base/lib/src/error/error.dart

@ -0,0 +1,12 @@
class QiniuError extends Error {
final Error rawError;
final String _message;
String get message => _message ?? rawError?.toString() ?? '';
@override
StackTrace get stackTrace => rawError?.stackTrace ?? super.stackTrace;
QiniuError({this.rawError, String message}) : _message = message;
}

39
qiniu-dart-sdk/base/lib/src/storage/config/cache.dart

@ -0,0 +1,39 @@
part of 'config.dart';
abstract class CacheProvider {
///
Future setItem(String key, String item);
/// key
Future<String> getItem(String key);
/// key
Future removeItem(String key);
///
Future clear();
}
class DefaultCacheProvider extends CacheProvider {
Map<String, String> value = {};
@override
Future clear() async {
value.clear();
}
@override
Future<String> getItem(String key) async {
return value[key];
}
@override
Future removeItem(String key) async {
value.remove(key);
}
@override
Future setItem(String key, String item) async {
value[key] = item;
}
}

28
qiniu-dart-sdk/base/lib/src/storage/config/config.dart

@ -0,0 +1,28 @@
import 'package:dio/adapter.dart';
import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
import 'package:qiniu_sdk_base/src/storage/error/error.dart';
part 'protocol.dart';
part 'host.dart';
part 'cache.dart';
class Config {
final HostProvider hostProvider;
final CacheProvider cacheProvider;
final HttpClientAdapter httpClientAdapter;
///
///
///
final int retryLimit;
Config({
HostProvider hostProvider,
CacheProvider cacheProvider,
HttpClientAdapter httpClientAdapter,
this.retryLimit = 3,
}) : hostProvider = hostProvider ?? DefaultHostProvider(),
cacheProvider = cacheProvider ?? DefaultCacheProvider(),
httpClientAdapter = httpClientAdapter ?? DefaultHttpClientAdapter();
}

124
qiniu-dart-sdk/base/lib/src/storage/config/host.dart

@ -0,0 +1,124 @@
part of 'config.dart';
abstract class HostProvider {
Future<String> getUpHost({
@required String accessKey,
@required String bucket,
});
bool isFrozen(String host);
void freezeHost(String host);
}
class DefaultHostProvider extends HostProvider {
final protocol = Protocol.Https.value;
final _http = Dio();
//
final _stashedUpDomains = <_Domain>[];
// accessKey:bucket key up host
String _cacheKey;
//
final List<_Domain> _frozenUpDomains = [];
@override
Future<String> getUpHost({
@required String accessKey,
@required String bucket,
}) async {
// host
_frozenUpDomains.removeWhere((domain) => !domain.isFrozen());
var _upDomains = <_Domain>[];
if ('$accessKey:$bucket' == _cacheKey && _stashedUpDomains.isNotEmpty) {
_upDomains.addAll(_stashedUpDomains);
} else {
final url =
'$protocol://api.qiniu.com/v4/query?ak=$accessKey&bucket=$bucket';
final res = await _http.get<Map>(url);
final hosts = res.data['hosts']
.map((dynamic json) => _Host.fromJson(json as Map))
.cast<_Host>()
.toList() as List<_Host>;
for (var host in hosts) {
final domainList = host.up['domains'].cast<String>() as List<String>;
final domains = domainList.map((domain) => _Domain(domain));
_upDomains.addAll(domains);
}
_cacheKey = '$accessKey:$bucket';
_stashedUpDomains.addAll(_upDomains);
}
// host
for (var index = 0; index < _upDomains.length; index++) {
final availableDomain = _upDomains.elementAt(index);
// host
final frozen = isFrozen(protocol + '://' + availableDomain.value);
if (!frozen) {
return protocol + '://' + availableDomain.value;
}
}
//
throw StorageError(
type: StorageErrorType.NO_AVAILABLE_HOST,
message: '没有可用的上传域名',
);
}
@override
bool isFrozen(String host) {
final uri = Uri.parse(host);
final frozenDomain = _frozenUpDomains.firstWhere(
(domain) => domain.isFrozen() && domain.value == uri.host,
orElse: () => null);
return frozenDomain != null;
}
@override
void freezeHost(String host) {
// http://example.org
// scheme: http
// host: example.org
final uri = Uri.parse(host);
_frozenUpDomains.add(_Domain(uri.host)..freeze());
}
}
class _Host {
String region;
int ttl;
// domains: []
Map<String, dynamic> up;
_Host({this.region, this.ttl, this.up});
factory _Host.fromJson(Map json) {
return _Host(
region: json['region'] as String,
ttl: json['ttl'] as int,
up: json['up'] as Map<String, dynamic>,
);
}
}
class _Domain {
int frozenTime = 0;
final _lockTime = 1000 * 60 * 10;
bool isFrozen() {
return frozenTime + _lockTime > DateTime.now().millisecondsSinceEpoch;
}
void freeze() {
frozenTime = DateTime.now().millisecondsSinceEpoch;
}
String value;
_Domain(this.value);
}

11
qiniu-dart-sdk/base/lib/src/storage/config/protocol.dart

@ -0,0 +1,11 @@
part of 'config.dart';
enum Protocol { Http, Https }
extension ProtocolExt on Protocol {
String get value {
if (this == Protocol.Http) return 'http';
if (this == Protocol.Https) return 'https';
return 'https';
}
}

75
qiniu-dart-sdk/base/lib/src/storage/error/error.dart

@ -0,0 +1,75 @@
import 'package:dio/dio.dart';
import 'package:qiniu_sdk_base/src/error/error.dart';
enum StorageErrorType {
///
CONNECT_TIMEOUT,
///
SEND_TIMEOUT,
///
RECEIVE_TIMEOUT,
/// 400
RESPONSE,
///
CANCEL,
///
NO_AVAILABLE_HOST,
///
IN_PROGRESS,
///
UNKNOWN,
}
class StorageError extends QiniuError {
/// [type] [StorageErrorType.RESPONSE] null
final int code;
final StorageErrorType type;
StorageError({this.type, this.code, Error rawError, String message})
: super(rawError: rawError, message: message);
factory StorageError.fromError(Error error) {
return StorageError(type: StorageErrorType.UNKNOWN, rawError: error);
}
factory StorageError.fromDioError(DioError error) {
return StorageError(
type: _mapDioErrorType(error.type),
code: error.response?.statusCode,
message: error.response?.data.toString(),
rawError: error.error is Error ? (error.error as Error) : null,
);
}
@override
String toString() {
var msg = 'StorageError [$type, $code]: $message';
msg += '\n$stackTrace';
return msg;
}
}
StorageErrorType _mapDioErrorType(DioErrorType type) {
switch (type) {
case DioErrorType.CONNECT_TIMEOUT:
return StorageErrorType.CONNECT_TIMEOUT;
case DioErrorType.SEND_TIMEOUT:
return StorageErrorType.SEND_TIMEOUT;
case DioErrorType.RECEIVE_TIMEOUT:
return StorageErrorType.RECEIVE_TIMEOUT;
case DioErrorType.RESPONSE:
return StorageErrorType.RESPONSE;
case DioErrorType.CANCEL:
return StorageErrorType.CANCEL;
case DioErrorType.DEFAULT:
default:
return StorageErrorType.UNKNOWN;
}
}

20
qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/cache_mixin.dart

@ -0,0 +1,20 @@
part of 'put_parts_task.dart';
/// mixin
///
///
mixin CacheMixin<T> on RequestTask<T> {
String get _cacheKey;
Future clearCache() async {
await config.cacheProvider.removeItem(_cacheKey);
}
Future setCache(String data) async {
await config.cacheProvider.setItem(_cacheKey, data);
}
Future<String> getCache() async {
return await config.cacheProvider.getItem(_cacheKey);
}
}

51
qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/complete_parts_task.dart

@ -0,0 +1,51 @@
part of 'put_parts_task.dart';
///
class CompletePartsTask extends RequestTask<PutResponse> {
final String token;
final String uploadId;
final List<Part> parts;
final String key;
TokenInfo _tokenInfo;
CompletePartsTask({
@required this.token,
@required this.uploadId,
@required this.parts,
this.key,
PutController controller,
}) : super(controller: controller);
@override
void preStart() {
_tokenInfo = Auth.parseUpToken(token);
super.preStart();
}
@override
Future<PutResponse> createTask() async {
final bucket = _tokenInfo.putPolicy.getBucket();
final host = await config.hostProvider.getUpHost(
bucket: bucket,
accessKey: _tokenInfo.accessKey,
);
final headers = <String, dynamic>{'Authorization': 'UpToken $token'};
final encodedKey = key != null ? base64Url.encode(utf8.encode(key)) : '~';
final paramUrl =
'$host/buckets/$bucket/objects/$encodedKey/uploads/$uploadId';
final response = await client.post<Map<String, dynamic>>(
paramUrl,
data: {
'parts': parts
..sort((a, b) => a.partNumber - b.partNumber)
..map((part) => part.toJson()).toList()
},
options: Options(headers: headers),
);
return PutResponse.fromJson(response.data);
}
}

92
qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/init_parts_task.dart

@ -0,0 +1,92 @@
part of 'put_parts_task.dart';
/// initParts
class InitParts {
final int expireAt;
final String uploadId;
InitParts({
@required this.expireAt,
@required this.uploadId,
});
factory InitParts.fromJson(Map json) {
return InitParts(
uploadId: json['uploadId'] as String,
expireAt: json['expireAt'] as int,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'uploadId': uploadId,
'expireAt': expireAt,
};
}
}
/// [UploadPartsTask] uploadId
class InitPartsTask extends RequestTask<InitParts> with CacheMixin<InitParts> {
final File file;
final String token;
final String key;
@override
String _cacheKey;
TokenInfo _tokenInfo;
InitPartsTask({
@required this.file,
@required this.token,
this.key,
PutController controller,
}) : super(controller: controller);
static String getCacheKey(String path, int length, String key) {
return 'qiniu_dart_sdk_init_parts_task_${path}_key_${key}_size_$length';
}
@override
void preStart() {
_tokenInfo = Auth.parseUpToken(token);
_cacheKey = InitPartsTask.getCacheKey(file.path, file.lengthSync(), key);
super.preStart();
}
@override
Future<InitParts> createTask() async {
final headers = {'Authorization': 'UpToken $token'};
final initPartsCache = await getCache();
if (initPartsCache != null) {
return InitParts.fromJson(
json.decode(initPartsCache) as Map<String, dynamic>);
}
final bucket = _tokenInfo.putPolicy.getBucket();
final host = await config.hostProvider.getUpHost(
bucket: bucket,
accessKey: _tokenInfo.accessKey,
);
final encodedKey = key != null ? base64Url.encode(utf8.encode(key)) : '~';
final paramUrl = '$host/buckets/$bucket/objects/$encodedKey/uploads';
final response = await client.post<Map<String, dynamic>>(
paramUrl,
/// data dio cancel
data: <String, dynamic>{},
options: Options(headers: headers),
);
return InitParts.fromJson(response.data);
}
@override
void postReceive(data) async {
await setCache(json.encode(data.toJson()));
super.postReceive(data);
}
}

26
qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/part.dart

@ -0,0 +1,26 @@
part of 'put_parts_task.dart';
///
class Part {
final String etag;
final int partNumber;
Part({
@required this.etag,
@required this.partNumber,
});
factory Part.fromJson(Map<String, dynamic> json) {
return Part(
etag: json['etag'] as String,
partNumber: json['partNumber'] as int,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'etag': etag,
'partNumber': partNumber,
};
}
}

25
qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/put_by_part_options.dart

@ -0,0 +1,25 @@
import '../put_controller.dart';
class PutByPartOptions {
///
///
final String key;
/// MB
///
/// [partSize] [partSize]
/// 4MB 1MB 1024 MB
final int partSize;
final int maxPartsRequestNumber;
///
final PutController controller;
const PutByPartOptions({
this.key,
this.partSize = 4,
this.maxPartsRequestNumber = 5,
this.controller,
});
}

187
qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/put_parts_task.dart

@ -0,0 +1,187 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
import 'package:qiniu_sdk_base/qiniu_sdk_base.dart';
part 'cache_mixin.dart';
part 'complete_parts_task.dart';
part 'init_parts_task.dart';
part 'part.dart';
part 'upload_part_task.dart';
part 'upload_parts_task.dart';
///
class PutByPartTask extends RequestTask<PutResponse> {
final File file;
final String token;
final int partSize;
final int maxPartsRequestNumber;
final String key;
/// 0 [PutByPartTask]
@override
int get retryLimit => 0;
PutByPartTask({
@required this.file,
@required this.token,
@required this.partSize,
@required this.maxPartsRequestNumber,
this.key,
PutController controller,
}) : assert(file != null),
assert(token != null),
assert(partSize != null),
assert(maxPartsRequestNumber != null),
assert(() {
if (partSize < 1 || partSize > 1024) {
throw RangeError.range(partSize, 1, 1024, 'partSize',
'partSize must be greater than 1 and less than 1024');
}
return true;
}()),
super(controller: controller);
RequestTaskController _currentWorkingTaskController;
@override
void preStart() {
super.preStart();
//
final sameTaskExsist = manager.getTasks().firstWhere(
(element) => element is PutByPartTask && isEquals(element),
orElse: () => null,
);
if (sameTaskExsist != null) {
throw StorageError(
type: StorageErrorType.IN_PROGRESS,
message: '$file 已在上传队列中',
);
}
// controller
controller?.cancelToken?.whenCancel?.then((_) {
_currentWorkingTaskController?.cancel();
});
}
@override
void postReceive(PutResponse data) {
_currentWorkingTaskController = null;
super.postReceive(data);
}
@override
Future<PutResponse> createTask() async {
controller?.notifyStatusListeners(StorageStatus.Request);
final initPartsTask = _createInitParts();
final initParts = await initPartsTask.future;
//
controller?.notifyProgressListeners(0.002);
final uploadParts = _createUploadParts(initParts.uploadId);
PutResponse putResponse;
try {
final parts = await uploadParts.future;
putResponse =
await _createCompleteParts(initParts.uploadId, parts).future;
} catch (error) {
// initPartsTask uploadParts postError
if (error is StorageError) {
///
/// 1
/// 2 uploadId 400
if (error.code == 612 || error.code == 400) {
await initPartsTask.clearCache();
await uploadParts.clearCache();
}
///
if (error.code == 612) {
controller?.notifyStatusListeners(StorageStatus.Retry);
return createTask();
}
}
rethrow;
}
///
await initPartsTask.clearCache();
await uploadParts.clearCache();
return putResponse;
}
bool isEquals(PutByPartTask target) {
return target.file.path == file.path &&
target.key == key &&
target.file.lengthSync() == file.lengthSync();
}
///
InitPartsTask _createInitParts() {
final _controller = PutController();
final task = InitPartsTask(
file: file,
token: token,
key: key,
controller: _controller,
);
manager.addTask(task);
_currentWorkingTaskController = _controller;
return task;
}
UploadPartsTask _createUploadParts(String uploadId) {
final _controller = PutController();
final task = UploadPartsTask(
file: file,
token: token,
partSize: partSize,
uploadId: uploadId,
maxPartsRequestNumber: maxPartsRequestNumber,
key: key,
controller: _controller,
);
_controller.addSendProgressListener(onSendProgress);
manager.addTask(task);
_currentWorkingTaskController = _controller;
return task;
}
///
CompletePartsTask _createCompleteParts(
String uploadId,
List<Part> parts,
) {
final _controller = PutController();
final task = CompletePartsTask(
token: token,
uploadId: uploadId,
parts: parts,
key: key,
controller: _controller,
);
manager.addTask(task);
_currentWorkingTaskController = _controller;
return task;
}
}

112
qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/upload_part_task.dart

@ -0,0 +1,112 @@
part of 'put_parts_task.dart';
// part
class UploadPartTask extends RequestTask<UploadPart> {
final String token;
final String uploadId;
final RandomAccessFile raf;
final int partSize;
// data Stream Dio content-length onSendProgress
// https://github.com/flutterchina/dio/blob/21136168ab39a7536835c7a59ce0465bb05feed4/dio/lib/src/dio.dart#L1000
final int byteLength;
final int partNumber;
final String key;
TokenInfo _tokenInfo;
UploadPartTask({
@required this.token,
@required this.raf,
@required this.uploadId,
@required this.byteLength,
@required this.partNumber,
@required this.partSize,
this.key,
PutController controller,
}) : super(controller: controller);
@override
void preStart() {
_tokenInfo = Auth.parseUpToken(token);
super.preStart();
}
@override
void postReceive(data) {
controller?.notifyProgressListeners(1);
super.postReceive(data);
}
@override
Future<UploadPart> createTask() async {
final headers = <String, dynamic>{
'Authorization': 'UpToken $token',
Headers.contentLengthHeader: byteLength,
};
final bucket = _tokenInfo.putPolicy.getBucket();
final host = await config.hostProvider.getUpHost(
bucket: bucket,
accessKey: _tokenInfo.accessKey,
);
final encodedKey = key != null ? base64Url.encode(utf8.encode(key)) : '~';
final paramUrl = 'buckets/$bucket/objects/$encodedKey';
final response = await client.put<Map<String, dynamic>>(
'$host/$paramUrl/uploads/$uploadId/$partNumber',
data: Stream.fromIterable([_readFileByPartNumber(partNumber)]),
// data stream interceptor cancelToken bug
cancelToken: controller.cancelToken,
options: Options(headers: headers),
);
return UploadPart.fromJson(response.data);
}
// File 4m(穿 File )
// 21m 4 * 5
// 90% 100%
// onSendProgress onSendProgress Progress
// ( postReceive)
@override
void onSendProgress(double percent) {
controller?.notifySendProgressListeners(percent);
}
// partNumber
List<int> _readFileByPartNumber(int partNumber) {
final startOffset = (partNumber - 1) * partSize * 1024 * 1024;
raf.setPositionSync(startOffset);
return raf.readSync(byteLength);
}
}
// uploadPart
class UploadPart {
final String md5;
final String etag;
UploadPart({
@required this.md5,
@required this.etag,
});
factory UploadPart.fromJson(Map json) {
return UploadPart(
md5: json['md5'] as String,
etag: json['etag'] as String,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'etag': etag,
'md5': md5,
};
}
}

260
qiniu-dart-sdk/base/lib/src/storage/methods/put/by_part/upload_parts_task.dart

@ -0,0 +1,260 @@
part of 'put_parts_task.dart';
// parts [CompletePartsTask] [Part]
class UploadPartsTask extends RequestTask<List<Part>> with CacheMixin {
final File file;
final String token;
final String uploadId;
final int partSize;
final int maxPartsRequestNumber;
final String key;
@override
String _cacheKey;
/// 0 [UploadPartsTask]
@override
int get retryLimit => 0;
// bytes
int _fileByteLength;
//
//
//
int _partByteLength;
//
int _totalPartCount;
// part
final Map<int, Part> _uploadedPartMap = {};
// UploadPartTask
final List<RequestTaskController> _workingUploadPartTaskControllers = [];
//
int _sentPartCount = 0;
//
int _sentPartToServerCount = 0;
//
int _idleRequestNumber;
RandomAccessFile _raf;
UploadPartsTask({
@required this.file,
@required this.token,
@required this.uploadId,
@required this.partSize,
@required this.maxPartsRequestNumber,
this.key,
PutController controller,
}) : super(controller: controller);
static String getCacheKey(
String path,
int length,
int partSize,
String key,
) {
final keyList = [
'key/$key',
'path/$path',
'file_size/$length',
'part_size/$partSize',
];
return 'qiniu_dart_sdk_upload_parts_task@[${keyList..join("/")}]';
}
@override
void preStart() {
// controller
controller?.cancelToken?.whenCancel?.then((_) {
for (final controller in _workingUploadPartTaskControllers) {
controller.cancel();
}
});
_fileByteLength = file.lengthSync();
_partByteLength = partSize * 1024 * 1024;
_idleRequestNumber = maxPartsRequestNumber;
_totalPartCount = (_fileByteLength / _partByteLength).ceil();
_cacheKey = getCacheKey(file.path, _fileByteLength, partSize, key);
// UploadPartTask file open
// close file stream close open stream
//
// bytes
_raf = file.openSync();
super.preStart();
}
@override
void postReceive(data) async {
await _raf.close();
super.postReceive(data);
}
@override
void postError(Object error) async {
await _raf.close();
//
await storeUploadedPart();
super.postError(error);
}
Future storeUploadedPart() async {
if (_uploadedPartMap.isEmpty) {
return;
}
await setCache(jsonEncode(_uploadedPartMap.values.toList()));
}
// part
Future recoverUploadedPart() async {
//
final cachedData = await getCache();
//
if (cachedData != null) {
var cachedList = <Part>[];
try {
final _cachedList = json.decode(cachedData) as List<dynamic>;
cachedList = _cachedList
.map((dynamic item) => Part.fromJson(item as Map<String, dynamic>))
.toList();
} catch (error) {
rethrow;
}
for (final part in cachedList) {
_uploadedPartMap[part.partNumber] = part;
}
}
}
@override
Future<List<Part>> createTask() async {
///
// ignore: null_aware_in_condition
if (controller != null && controller.cancelToken.isCancelled) {
throw StorageError(type: StorageErrorType.CANCEL);
}
controller.notifyStatusListeners(StorageStatus.Request);
//
await recoverUploadedPart();
//
await _uploadParts();
return _uploadedPartMap.values.toList();
}
int _uploadingPartIndex = 0;
//
Future<void> _uploadParts() async {
final tasksLength =
min(_idleRequestNumber, _totalPartCount - _uploadingPartIndex);
final taskFutures = <Future<Null>>[];
while (taskFutures.length < tasksLength &&
_uploadingPartIndex < _totalPartCount) {
// partNumber 1
final partNumber = ++_uploadingPartIndex;
final _uploadedPart = _uploadedPartMap[partNumber];
if (_uploadedPart != null) {
_sentPartCount++;
_sentPartToServerCount++;
notifySendProgress();
notifyProgress();
continue;
}
final future = _createUploadPartTaskFutureByPartNumber(partNumber);
taskFutures.add(future);
}
await Future.wait<Null>(taskFutures);
}
Future<Null> _createUploadPartTaskFutureByPartNumber(int partNumber) async {
// (part)
final _byteLength = _getPartSizeByPartNumber(partNumber);
_idleRequestNumber--;
final _controller = PutController();
_workingUploadPartTaskControllers.add(_controller);
final task = UploadPartTask(
token: token,
raf: _raf,
uploadId: uploadId,
byteLength: _byteLength,
partNumber: partNumber,
partSize: partSize,
key: key,
controller: _controller,
);
_controller
// UploadPartTask chunk
..addSendProgressListener((percent) {
_sentPartCount++;
notifySendProgress();
})
// UploadPartTask
..addProgressListener((percent) {
_sentPartToServerCount++;
notifyProgress();
});
manager.addTask(task);
final data = await task.future;
_idleRequestNumber++;
_uploadedPartMap[partNumber] =
Part(partNumber: partNumber, etag: data.etag);
_workingUploadPartTaskControllers.remove(_controller);
await storeUploadedPart();
//
if (_uploadedPartMap.length != _totalPartCount) {
//
await _uploadParts();
}
}
// partNumber byte
int _getPartSizeByPartNumber(int partNumber) {
final startOffset = (partNumber - 1) * _partByteLength;
if (partNumber == _totalPartCount) {
return _fileByteLength - startOffset;
}
return _partByteLength;
}
void notifySendProgress() {
controller?.notifySendProgressListeners(_sentPartCount / _totalPartCount);
}
void notifyProgress() {
controller?.notifyProgressListeners(_sentPartToServerCount /
_totalPartCount *
RequestTask.onSendProgressTakePercentOfTotal);
}
// UploadPartsTask
@override
void onSendProgress(double percent) {}
}

13
qiniu-dart-sdk/base/lib/src/storage/methods/put/by_single/put_by_single_options.dart

@ -0,0 +1,13 @@
import '../put_controller.dart';
class PutBySingleOptions {
///
///
///
final String key;
///
final PutController controller;
const PutBySingleOptions({this.key, this.controller});
}

60
qiniu-dart-sdk/base/lib/src/storage/methods/put/by_single/put_by_single_task.dart

@ -0,0 +1,60 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
import 'package:qiniu_sdk_base/src/storage/task/task.dart';
import '../../../../auth/auth.dart';
import '../put_response.dart';
//
class PutBySingleTask extends RequestTask<PutResponse> {
///
final File file;
///
final String token;
///
///
final String key;
TokenInfo _tokenInfo;
PutBySingleTask({
@required this.file,
@required this.token,
this.key,
RequestTaskController controller,
}) : assert(file != null),
assert(token != null),
super(controller: controller);
@override
void preStart() {
_tokenInfo = Auth.parseUpToken(token);
super.preStart();
}
@override
Future<PutResponse> createTask() async {
final formData = FormData.fromMap(<String, dynamic>{
'file': await MultipartFile.fromFile(file.path),
'token': token,
'key': key,
});
final host = await config.hostProvider.getUpHost(
accessKey: _tokenInfo.accessKey,
bucket: _tokenInfo.putPolicy.getBucket(),
);
final response = await client.post<Map<String, dynamic>>(
host,
data: formData,
cancelToken: controller?.cancelToken,
);
return PutResponse.fromJson(response.data);
}
}

5
qiniu-dart-sdk/base/lib/src/storage/methods/put/put.dart

@ -0,0 +1,5 @@
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';

3
qiniu-dart-sdk/base/lib/src/storage/methods/put/put_controller.dart

@ -0,0 +1,3 @@
import '../../task/request_task.dart';
class PutController extends RequestTaskController {}

28
qiniu-dart-sdk/base/lib/src/storage/methods/put/put_options.dart

@ -0,0 +1,28 @@
import 'put_controller.dart';
class PutOptions {
///
///
///
final String key;
/// 使使 false
final bool forceBySingle;
/// 使 4 MB
final int partSize;
/// 5
final int maxPartsRequestNumber;
///
final PutController controller;
const PutOptions({
this.key,
this.forceBySingle = false,
this.partSize = 4,
this.maxPartsRequestNumber = 5,
this.controller,
});
}

33
qiniu-dart-sdk/base/lib/src/storage/methods/put/put_response.dart

@ -0,0 +1,33 @@
import 'package:meta/meta.dart';
class PutResponse {
final String key;
final String hash;
/// [returnBody]
///
final Map<String, dynamic> rawData;
PutResponse({
@required this.key,
@required this.hash,
@required this.rawData,
});
factory PutResponse.fromJson(Map<String, dynamic> json) {
return PutResponse(
key: json['key'] as String,
hash: json['hash'] as String,
rawData: json,
);
}
Map<String, dynamic> toJson() {
return <String, dynamic>{
'key': key,
'hash': hash,
'rawData': rawData
};
}
}

21
qiniu-dart-sdk/base/lib/src/storage/status/status.dart

@ -0,0 +1,21 @@
enum StorageStatus {
None,
///
Init,
///
Request,
///
Success,
///
Cancel,
///
Error,
///
Retry
}

99
qiniu-dart-sdk/base/lib/src/storage/storage.dart

@ -0,0 +1,99 @@
import 'dart:io';
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 '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';
///
class Storage {
Config config;
RequestTaskManager taskManager;
Storage({Config config}) {
this.config = config ?? Config();
taskManager = RequestTaskManager(config: this.config);
}
Future<PutResponse> putFile(
File file,
String token, {
PutOptions options,
}) {
options ??= PutOptions();
RequestTask<PutResponse> task;
final useSingle = options.forceBySingle == true ||
file.lengthSync() < (options.partSize * 1024 * 1024);
if (useSingle) {
task = PutBySingleTask(
file: file,
token: token,
key: options.key,
controller: options.controller,
);
} else {
task = PutByPartTask(
file: file,
token: token,
key: options.key,
maxPartsRequestNumber: options.maxPartsRequestNumber,
partSize: options.partSize,
controller: options.controller,
);
}
taskManager.addTask(task);
return task.future;
}
///
Future<PutResponse> putFileBySingle(
File file,
String token, {
PutBySingleOptions options,
}) {
options ??= PutBySingleOptions();
final task = PutBySingleTask(
file: file,
token: token,
key: options.key,
controller: options.controller,
);
taskManager.addTask(task);
return task.future;
}
///
Future<PutResponse> putFileByPart(
File file,
String token, {
PutByPartOptions options,
}) {
options ??= PutByPartOptions();
final task = PutByPartTask(
file: file,
token: token,
key: options.key,
partSize: options.partSize,
maxPartsRequestNumber: options.maxPartsRequestNumber,
controller: options.controller,
);
taskManager.addTask(task);
return task.future;
}
}

207
qiniu-dart-sdk/base/lib/src/storage/task/request_task.dart

@ -0,0 +1,207 @@
import 'dart:io' show Platform;
import 'package:dio/dio.dart';
import 'package:meta/meta.dart';
import 'package:qiniu_sdk_base/qiniu_sdk_base.dart';
import 'task.dart';
part 'request_task_controller.dart';
part 'request_task_manager.dart';
String _getUserAgent() {
return [
'${Platform.operatingSystem}/${Platform.operatingSystemVersion}',
'Dart/${Platform.version}'
].join(' ');
}
abstract class RequestTask<T> extends Task<T> {
//
static double preStartTakePercentOfTotal = 0.001;
//
static double onSendProgressTakePercentOfTotal = 0.99;
//
static double postReceiveTakePercentOfTotal = 1;
final Dio client = Dio();
/// [RequestTaskManager.addTask]
Config config;
@override
// ignore: overridden_fields
covariant RequestTaskManager manager;
RequestTaskController controller;
//
int _retryCount = 0;
//
int retryLimit;
RequestTask({this.controller});
@override
@mustCallSuper
void preStart() {
//
if (controller != null && controller.cancelToken.isCancelled) {
throw StorageError(type: StorageErrorType.CANCEL);
}
controller?.notifyStatusListeners(StorageStatus.Init);
controller?.notifyProgressListeners(preStartTakePercentOfTotal);
retryLimit = config.retryLimit;
client.httpClientAdapter = config.httpClientAdapter;
client.interceptors.add(InterceptorsWrapper(onRequest: (options) {
controller?.notifyStatusListeners(StorageStatus.Request);
options
..cancelToken = controller?.cancelToken
..onSendProgress = (sent, total) => onSendProgress(sent / total);
return options;
}));
client.interceptors.add(InterceptorsWrapper(onRequest: (options) {
options.headers['User-Agent'] = _getUserAgent();
return options;
}));
super.preStart();
}
@override
@mustCallSuper
void preRestart() {
controller?.notifyStatusListeners(StorageStatus.Retry);
super.preRestart();
}
@override
@mustCallSuper
void postReceive(T data) {
controller?.notifyStatusListeners(StorageStatus.Success);
controller?.notifyProgressListeners(postReceiveTakePercentOfTotal);
super.postReceive(data);
}
/// [createTask]
@mustCallSuper
void postCancel(StorageError error) {
controller?.notifyStatusListeners(StorageStatus.Cancel);
}
@override
@mustCallSuper
void postError(Object error) async {
// Dio
if (error is DioError) {
if (!_canConnectToHost(error)) {
// host host , tls error()
if (_isHostUnavailable(error)) {
config.hostProvider.freezeHost(error.request.path);
}
// host host
if (_retryCount < retryLimit) {
_retryCount++;
manager.restartTask(this);
return;
}
}
// 502
if (_isHostUnavailable(error)) {
config.hostProvider.freezeHost(error.request.path);
// host
if (_retryCount < retryLimit) {
_retryCount++;
manager.restartTask(this);
return;
}
}
final storageError = StorageError.fromDioError(error);
//
if (error.type == DioErrorType.CANCEL) {
postCancel(storageError);
} else {
controller?.notifyStatusListeners(StorageStatus.Error);
}
super.postError(storageError);
return;
}
// Storage StorageError
if (error is StorageError) {
if (error.type == StorageErrorType.CANCEL) {
postCancel(error);
} else {
controller?.notifyStatusListeners(StorageStatus.Error);
}
super.postError(error);
return;
}
//
if (error is Error) {
controller?.notifyStatusListeners(StorageStatus.Error);
final storageError = StorageError.fromError(error);
super.postError(storageError);
return;
}
controller?.notifyStatusListeners(StorageStatus.Error);
super.postError(error);
}
//
void onSendProgress(double percent) {
controller?.notifySendProgressListeners(percent);
controller
?.notifyProgressListeners(percent * onSendProgressTakePercentOfTotal);
}
// host
bool _canConnectToHost(Object error) {
if (error is DioError) {
if (error.type == DioErrorType.RESPONSE &&
error.response.statusCode > 99) {
return true;
}
if (error.type == DioErrorType.CANCEL) {
return true;
}
}
return false;
}
// host
bool _isHostUnavailable(Object error) {
if (error is DioError) {
if (error.type == DioErrorType.RESPONSE) {
final statusCode = error.response.statusCode;
if (statusCode == 502) {
return true;
}
if (statusCode == 503) {
return true;
}
if (statusCode == 504) {
return true;
}
if (statusCode == 599) {
return true;
}
}
// ignore: todo
// TODO SocketException
}
return false;
}
}

97
qiniu-dart-sdk/base/lib/src/storage/task/request_task_controller.dart

@ -0,0 +1,97 @@
part of 'request_task.dart';
class RequestTaskController
with
RequestTaskProgressListenersMixin,
StorageStatusListenersMixin,
RequestTaskSendProgressListenersMixin {
final CancelToken cancelToken = CancelToken();
///
bool get isCancelled => cancelToken.isCancelled;
void cancel() {
//
if (isCancelled) {
return;
}
cancelToken.cancel();
}
}
typedef RequestTaskSendProgressListener = void Function(double percent);
///
///
/// 使 Dio
mixin RequestTaskSendProgressListenersMixin {
final List<RequestTaskSendProgressListener> _sendProgressListeners = [];
void Function() addSendProgressListener(
RequestTaskSendProgressListener listener) {
_sendProgressListeners.add(listener);
return () => removeSendProgressListener(listener);
}
void removeSendProgressListener(RequestTaskSendProgressListener listener) {
_sendProgressListeners.remove(listener);
}
void notifySendProgressListeners(double percent) {
for (final listener in _sendProgressListeners) {
listener(percent);
}
}
}
typedef RequestTaskProgressListener = void Function(double percent);
///
///
/// 1% 98% 1% 100%
mixin RequestTaskProgressListenersMixin {
final List<RequestTaskProgressListener> _progressListeners = [];
void Function() addProgressListener(RequestTaskProgressListener listener) {
_progressListeners.add(listener);
return () => removeProgressListener(listener);
}
void removeProgressListener(RequestTaskProgressListener listener) {
_progressListeners.remove(listener);
}
void notifyProgressListeners(double percent) {
for (final listener in _progressListeners) {
listener(percent);
}
}
}
typedef StorageStatusListener = void Function(StorageStatus status);
///
///
/// (preStart, postReceive)
mixin StorageStatusListenersMixin {
StorageStatus status = StorageStatus.None;
final List<StorageStatusListener> _statusListeners = [];
void Function() addStatusListener(StorageStatusListener listener) {
_statusListeners.add(listener);
return () => removeStatusListener(listener);
}
void removeStatusListener(StorageStatusListener listener) {
_statusListeners.remove(listener);
}
void notifyStatusListeners(StorageStatus status) {
status = status;
for (final listener in _statusListeners) {
listener(status);
}
}
}

15
qiniu-dart-sdk/base/lib/src/storage/task/request_task_manager.dart

@ -0,0 +1,15 @@
part of 'request_task.dart';
class RequestTaskManager extends TaskManager {
final Config config;
RequestTaskManager({
@required this.config,
}) : assert(config != null);
@override
void addTask(covariant RequestTask task) {
task.config = config;
super.addTask(task);
}
}

50
qiniu-dart-sdk/base/lib/src/storage/task/task.dart

@ -0,0 +1,50 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:qiniu_sdk_base/qiniu_sdk_base.dart';
export 'request_task.dart';
export 'task_manager.dart';
/// Task
///
/// Task
abstract class Task<T> {
TaskManager manager;
@protected
Completer<T> completer = Completer();
Future<T> get future => completer.future;
///
Future<T> createTask();
/// [Task] [TaskManager]
@mustCallSuper
void preStart() {}
/// [createTask]
@mustCallSuper
void postStart() {}
/// [createTask]
@mustCallSuper
void postReceive(T data) {
manager.removeTask(this);
completer.complete(data);
}
/// [createTask]
@mustCallSuper
void postError(Object error) {
manager.removeTask(this);
completer.completeError(error);
}
/// Task [Task.restart]
void preRestart() {}
/// Task [createTask]
void postRestart() {}
}

66
qiniu-dart-sdk/base/lib/src/storage/task/task_manager.dart

@ -0,0 +1,66 @@
import 'package:meta/meta.dart';
import 'task.dart';
class TaskManager {
final List<Task> workingTasks = [];
/// [Task]
///
/// [task] [createTask]
@mustCallSuper
void addTask(Task task) {
try {
task
..manager = this
..preStart();
} catch (e) {
task.postError(e);
return;
}
workingTasks.add(task);
task.createTask().then(task.postReceive).catchError(task.postError);
try {
task.postStart();
} catch (e) {
task.postError(e);
return;
}
}
@mustCallSuper
void removeTask(Task task) {
workingTasks.remove(task);
}
@mustCallSuper
void restartTask(Task task) {
try {
task.preRestart();
} catch (e) {
task.postError(e);
return;
}
task.createTask().then(task.postReceive).catchError(task.postError);
try {
task.postRestart();
} catch (e) {
task.postError(e);
return;
}
}
/// [Task]
List<Task<dynamic>> getTasks() {
return workingTasks;
}
/// [T] [Task]
List<T> getTasksByType<T extends Task<dynamic>>() {
return workingTasks.whereType<T>().toList();
}
}

461
qiniu-dart-sdk/base/pubspec.lock

@ -0,0 +1,461 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "4d2a2c7f5e6db6acd2f4a7f713b308b45668c9573a11d0eda10936fb21fc5467"
url: "https://pub.flutter-io.cn"
source: hosted
version: "14.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "9fe5033adc3e8b19884e5ba296400be3794483acb53c2b68f8683744c9a7a9c7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.41.2"
args:
dependency: transitive
description:
name: args
sha256: "6ba785824030bc97154264652acfd6a2dc699cd85f6def708fb7534d23ef1348"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.6.0"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.1"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.5"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.18.0"
convert:
dependency: transitive
description:
name: convert
sha256: "3fa83bc417e26f1d55814443a603747d48e3cdf55b2f4fea27dea8e9224dcefd"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
coverage:
dependency: transitive
description:
name: coverage
sha256: "7c2065a21e40c96eb0674205a48616dc4dcff138c78dfc8f46666d65b7512a46"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.14.2"
crypto:
dependency: "direct main"
description:
name: crypto
sha256: "3ce628f3c6a7144be6c524dbb20ca5af52fa59c850c587bf5e52cd70ece8e7e8"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.5"
dart2_constant:
dependency: transitive
description:
name: dart2_constant
sha256: d9791e9e1f43ab97909333f37562df8e383a906940f44700de77dd0b0a081b18
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2+dart2"
dio:
dependency: "direct main"
description:
name: dio
sha256: "11979099d9ea182d74b6734340704d628b99c7a8316f9edd7718a297d1bcdd27"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.10"
dotenv:
dependency: "direct dev"
description:
name: dotenv
sha256: "7a906a39c0ec36c0427d5ddcf9e78004f28ffcd61be4ca5db56a823cb15e0ad2"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
file:
dependency: transitive
description:
name: file
sha256: "781811e896666e9a728fd85b13129e32d73f05167a5da1891739f4609c6a8d7d"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.2.1"
glob:
dependency: transitive
description:
name: glob
sha256: f699efc018c8783b7955c74c13b1952811feb38fb6fe13f9546752a6573dde5f
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.1"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.2.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "9d2b0626e9e402fc98e6868360da8f256064d6c0b8e4c3edcca5e02fb0b95da9"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.4"
intl:
dependency: transitive
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.18.1"
io:
dependency: transitive
description:
name: io
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.4"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "6cec7404b25d6338c8cb7b30131cd6c760079a4ec1fa7846c55bdda91f9d2819"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.1"
lcov:
dependency: transitive
description:
name: lcov
sha256: "36e28d3face9c62daf00bf6861fcd6fdb579ca09a5280cbb3ac36c5b073c615e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.7.0"
logging:
dependency: transitive
description:
name: logging
sha256: "239402c239e2a390cf745af3a496fb985dc8f0b31b3f5fe0d460e42289d33ad7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.11.4"
matcher:
dependency: transitive
description:
name: matcher
sha256: "38c7be344ac5057e10161a5ecb00c9d9d67ed2f150001278601dd27d9fe64206"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.10"
meta:
dependency: "direct main"
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.16.0"
mime:
dependency: transitive
description:
name: mime
sha256: a7a98ea7f366e2cc9d2b20873815aebec5e2bc124fe0da9d3f7f59b0625ea180
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.0"
node_interop:
dependency: transitive
description:
name: node_interop
sha256: "910d75f8589630cd197b8b7ec70e648bc380950b489b07bda1da0c5b20c4b8c1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.1"
node_io:
dependency: transitive
description:
name: node_io
sha256: "36b74b2bcea9aa7484caa91e24bddde3f56e87fdc84f4da1cab7522a4b3680fe"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "847d2400938dd2594b4617244c4a9d477e939d0e134746ae88d9497e0ecb0a71"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.13"
package_config:
dependency: transitive
description:
name: package_config
sha256: "08b3acb5f8c9736aaf26776864ea68441bb69a82d900cbce47cd962b3ddb12c9"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.9.3"
path:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.3"
pedantic:
dependency: "direct dev"
description:
name: pedantic
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.11.1"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.5.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.4"
shelf:
dependency: transitive
description:
name: shelf
sha256: d16d1197f877e814f767be03c1c5940a397ae0ddb46dcfb9b5465074bb80ee37
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.7.9"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
sha256: "0947da47a2d858bcf2cdf87986098808488b9c509562f55d9748abffc5cd024e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.1"
shelf_static:
dependency: transitive
description:
name: shelf_static
sha256: e5ddf2266fa3d342728476a4c9718735d1e8eff1c5da1f8a3df0042f3b83c83c
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.9+2"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "2a300f6dd3d4a6d41941eef97920671ecd250712ce1275bf60110997691b20d1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.4+1"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
source_maps:
dependency: transitive
description:
name: source_maps
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.10.12"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.1"
test:
dependency: "direct dev"
description:
name: test
sha256: "53e45e20eed3f8859b3af1cb1c913a1667258d727423f46027df9a3f88822435"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.16.5"
test_api:
dependency: transitive
description:
name: test_api
sha256: "637a0c7ff81307fe6e31edfd338c798aeeef4c2c3fad031fc91ec92d5ef47c75"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.19"
test_core:
dependency: transitive
description:
name: test_core
sha256: "6442993591fc131006752949b0dfaaa9650243f615a71d4dfa33743ea2709767"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.15"
test_coverage:
dependency: "direct dev"
description:
name: test_coverage
sha256: "4c38230a26f8b7223de0a14cb0ff2257087d6f824091fa6572a472ee8bff3cb7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.0"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.2"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ec8861f85bf8037ebdb453066879ece9d0f25fdac42cd2ff4d43b47239d0f8aa
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.5.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: bfd93faab893a64920903d7d375517c18789c21e4c7daa5232a31ee126b7fea1
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: "750cd8919c8ce82d399de5092f5dfce5dd295a4e52fa785540b6dbca0cef94d7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.7.5"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.2"
sdks:
dart: ">=2.19.0 <3.0.0"

18
qiniu-dart-sdk/base/pubspec.yaml

@ -0,0 +1,18 @@
name: qiniu_sdk_base
version: 0.2.2
homepage: https://github.com/qiniu/dart-sdk
description: The sdk basic of Qiniu products
environment:
sdk: '>=2.7.0 <3.0.0'
dependencies:
dio: ^3.0.10
crypto: ^2.1.5
meta: ^1.2.3
dev_dependencies:
pedantic: ^1.9.0
test: ^1.14.4
dotenv: ^1.0.0
test_coverage: "^0.5.0"

11
qiniu-dart-sdk/codecov.yml

@ -0,0 +1,11 @@
coverage:
status:
# 提 pr 的时候不让 codecov 评论哪里没覆盖到
patch: off
project:
default:
# pr 里面很多测试用例都被跳过了,所以这个 target 没有意义。如果能让需要 token 的测试在 pr 跑起来可以修改这个 target
target: 0%
branches:
- master
informational: true

8
qiniu-dart-sdk/flutter/CHANGELOG.md

@ -0,0 +1,8 @@
## [0.1.0]
* Initial Release.
## [0.2.0]
* 优化了 `StorageError` 输出的调用栈
* `CacheProvider` 的方法都改成异步的

201
qiniu-dart-sdk/flutter/LICENSE

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

182
qiniu-dart-sdk/flutter/README.md

@ -0,0 +1,182 @@
# 七牛云存储 Flutter SDK [![qiniu_flutter_sdk](https://img.shields.io/pub/v/qiniu_flutter_sdk.svg?label=qiniu_flutter_sdk)](https://pub.dev/packages/qiniu_flutter_sdk)
七牛云存储的 Flutter SDK。
基于七牛云 API 实现,封装了七牛云存储系统的的客户端操作。
## 快速导航
* [概述](#概述)
* [示例](#示例)
* [快速开始](#快速开始)
* [功能简介](#功能简介)
* [贡献代码](#贡献代码)
* [许可证](#许可证)
## 概述
Qiniu-Flutter-SDK 基于七牛云存储官方 [API](https://developer.qiniu.com/kodo) 构建,提供抽象的接口用于快速使用七牛的对象存储功能。
Qiniu-Flutter-SDK 为客户端 SDK,没有包含 `token` 生成实现,为了安全,`token` 建议通过网络从服务端获取,具体生成代码可以参考官方[文档](https://developer.qiniu.com/kodo/manual/1208/upload-token),我们的很多服务端 SDK 已经实现了生成 Token 的功能,推荐直接使用,[查看其他 SDK](https://developer.qiniu.com/sdk#official-sdk)
## 示例
请查看 [Example](https://github.com/qiniu/dart-sdk/tree/master/flutter/example)
## 快速开始
编辑你的 `pubspec.yaml` 文件,在 `dependencies` 添加 `qiniu-flutter-sdk`,如下:
```yaml
dependencies:
...
qiniu-flutter-sdk: // 这里输入你需要的版本
```
在你需要使用的地方 `import`,如下:
```dart
import 'package:qiniu_flutter_sdk/qiniu_flutter_sdk.dart';
```
### 快速使用
```dart
// 创建 storage 对象
storage = Storage();
// 使用 storage 的 putFile 对象进行文件上传
storage.putFile(File('./file.txt'), 'TOKEN')
..then(/* 上传成功 */)
..catchError(/* 上传失败 */);
```
### 监听进度/状态
```dart
// 创建 storage 对象
storage = Storage();
// 创建 Controller 对象
putController = PutController();
// 添加整体进度监听
putController.onProgress((double percent) {
print('任务进度变化:已发送:$percent');
});
// 添加发送进度监听
putController.onSendProgress((double percent) {
print('已上传进度变化:已发送:$percent');
});
// 添加状态监听
putController.addStatusListener((StorageStatus status) {
print('状态变化: 当前任务状态:$status');
});
// 使用 storage 的 putFile 对象进行文件上传
storage.putFile(File('./file.txt'), 'TOKEN', PutOptions(
controller: putController,
))
```
### 取消正在上传的任务
```dart
// 创建 storage 对象
storage = Storage();
// 创建 Controller 对象
putController = PutController();
// 使用 storage 的 putFile 对象进行文件上传
storage.putFile(File('./file.txt'), 'TOKEN', PutOptions(
controller: putController,
))
// 取消当前任务
putController.cancel()
```
## API 说明
### `storage`
使用前必须创建一个 `Storage` 实例
```dart
// 创建 storage 对象
storage = Storage();
```
同时,在构造 `Storage` 时可以传入一个 `Config` 控制内部的一些行为,如下:
```dart
// 创建 storage 对象
storage = Storage(Config(
// 通过自己的 hostProvider 来使用自己的 host 进行上传
hostProvider: HostProvider,
// 可以通过实现 cacheProvider 来自己实现缓存系统支持分片断点续传
cacheProvider: CacheProvider,
// 如果你需要对网络请求进行更基础的一些操作,你可以实现自己的 HttpClientAdapter 处理相关行为
httpClientAdapter: HttpClientAdapter,
// 设定网络请求重试次数
retryLimit: 3,
));
```
#### `HostProvider`
该接口是一个抽象的接口,大多数开发者不需要自己实现这个,除非你使用的是七牛的专/私有云服务,则可以通过实现自己的 `HostProvider` 来向自己的服务进行上传。
#### `CacheProvider`
该接口同样是一个抽象的接口,`SDK` 支持分片断点续传功能,断点续传的信息通过 `CacheProvider` 提供的能力进行存储,如果你需要更好的体验,可以自己实现这个接口来对信息进行持久化的存储。
#### `HttpClientAdapter`
该接口也是一个抽象的接口,如果你需要对网络请求进行进一步的自定义处理时,你可以通过实现一个 `HttpClientAdapter` 来接管 `SDK` 的所有请求。
#### `retryLimit`
用于限制内部重试逻辑的重试次数, 当发生一些可重试级别的错误时,`SDK` 会使用 `retryLimit` 的次数约束自动进行尝试。
#### `PutController`
这里是一个重要的内容,对于整个上传任务的一些交互被封装到了这里,
`PutController` 用于对上传任务添加进度、状态的监听,同时可以通过 `PutController.cancel()` 对正在上传的任务进行取消。使用方式可以参考:[`取消正常上传的任务`](#取消正常上传的任务)
#### `Storage.putFile`
该接口内部封装了分片和直传两种实现,会根据文件的尺寸和上传配置信息自动选择使用分片还是直传的方式来上传对象
### 其他说明
1. 如果您想了解更多七牛的上传策略,建议您仔细阅读 [七牛官方文档-上传](https://developer.qiniu.com/kodo/manual/upload-types)。另外,七牛的上传策略是在后端服务指定的.
## 功能列表
* 单文件上传
* 分片上传
* 任务状态
* 任务进度
* 上传进度
* 失败重试
## 贡献代码
1. 登录 https://github.com
2. Fork git@github.com:qiniu/dart-sdk.git
3. 创建您的特性分支 (git checkout -b new-feature)
4. 提交您的改动 (git commit -am 'Added some features or fixed a bug')
5. 将您的改动记录提交到远程 git 仓库 (git push origin new-feature)
6. 然后到 github 网站的该 git 远程仓库的 new-feature 分支下发起 Pull Request
## 许可证
基于 Apache 2.0 协议发布
> Copyright (c) 2020 qiniu.com

90
qiniu-dart-sdk/flutter/analysis_options.yaml

@ -0,0 +1,90 @@
# copy from https://github.com/dart-lang/http/blob/master/analysis_options.yaml
include: package:pedantic/analysis_options.yaml
analyzer:
# enable-experiment:
# - non-nullable
strong-mode:
implicit-casts: false
implicit-dynamic: false
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

3
qiniu-dart-sdk/flutter/lib/qiniu_flutter_sdk.dart

@ -0,0 +1,3 @@
library qiniu_flutter_sdk;
export './src/storage/storage.dart';

3
qiniu-dart-sdk/flutter/lib/src/storage/controller.dart

@ -0,0 +1,3 @@
import 'package:qiniu_sdk_base/qiniu_sdk_base.dart' as base;
class PutController extends base.PutController {}

33
qiniu-dart-sdk/flutter/lib/src/storage/storage.dart

@ -0,0 +1,33 @@
import 'dart:io';
import 'package:qiniu_sdk_base/qiniu_sdk_base.dart' as base;
export 'package:qiniu_sdk_base/qiniu_sdk_base.dart'
show
Config,
PutOptions,
PutResponse,
HostProvider,
CacheProvider,
HttpClientAdapter,
QiniuError,
StorageError,
StorageErrorType,
PutByPartOptions,
StorageStatus,
PutBySingleOptions;
export './controller.dart';
class Storage {
final base.Storage _baseStorage;
Storage({base.Config config}) : _baseStorage = base.Storage(config: config);
Future<base.PutResponse> putFile(
File file,
String token, {
base.PutOptions options,
}) {
return _baseStorage.putFile(file, token, options: options);
}
}

228
qiniu-dart-sdk/flutter/pubspec.lock

@ -0,0 +1,228 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.10.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
characters:
dependency: transitive
description:
name: characters
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.1"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.17.0"
convert:
dependency: transitive
description:
name: convert
sha256: "3fa83bc417e26f1d55814443a603747d48e3cdf55b2f4fea27dea8e9224dcefd"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: "3ce628f3c6a7144be6c524dbb20ca5af52fa59c850c587bf5e52cd70ece8e7e8"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.5"
dio:
dependency: transitive
description:
name: dio
sha256: "11979099d9ea182d74b6734340704d628b99c7a8316f9edd7718a297d1bcdd27"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.10"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "9d2b0626e9e402fc98e6868360da8f256064d6c0b8e4c3edcca5e02fb0b95da9"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.4"
js:
dependency: transitive
description:
name: js
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.6.5"
matcher:
dependency: transitive
description:
name: matcher
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.13"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0"
meta:
dependency: transitive
description:
name: meta
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.0"
path:
dependency: transitive
description:
name: path
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.2"
pedantic:
dependency: "direct dev"
description:
name: pedantic
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.11.1"
qiniu_sdk_base:
dependency: "direct main"
description:
path: "../base"
relative: true
source: path
version: "0.2.2"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.9.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.11.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.4.16"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.2"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.4"
sdks:
dart: ">=2.18.0 <3.0.0"
flutter: ">=1.17.0"

23
qiniu-dart-sdk/flutter/pubspec.yaml

@ -0,0 +1,23 @@
name: qiniu_flutter_sdk
description: Qiniu Flutter sdk
version: 0.2.0
author: qiniu
homepage: https://github.com/qiniu/dart-sdk/tree/master/flutter
environment:
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.17.0 <2.0.0"
dependencies:
flutter:
sdk: flutter
qiniu_sdk_base:
path: ../base
dev_dependencies:
pedantic: ^1.9.0
flutter_test:
sdk: flutter
flutter:
Loading…
Cancel
Save