diff --git a/CHANGELOG.md b/CHANGELOG.md index 33a6206..5e25022 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.13 +- Upgrade packages +- Correct deprecated methods + ## 0.12.11 - Switch from `http_client` package to `http` package diff --git a/analysis_options.yaml b/analysis_options.yaml index 97f0908..86f043d 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,15 +1,13 @@ -analyzer: - strong-mode: true # exclude: # - path/to/excluded/files/** # Lint rules and documentation, see http://dart-lang.github.io/linter/lints linter: rules: - - cancel_subscriptions - - hash_and_equals - - iterable_contains_unrelated_type - - list_remove_unrelated_type - - test_types_in_equals - - unrelated_type_equality_checks - - valid_regexps + cancel_subscriptions: true + hash_and_equals: true + iterable_contains_unrelated_type: true + list_remove_unrelated_type: true + test_types_in_equals: true + unrelated_type_equality_checks: true + valid_regexps: true diff --git a/example/dospace_example.dart b/example/dospace_example.dart index e3bb1d5..c9955ca 100644 --- a/example/dospace_example.dart +++ b/example/dospace_example.dart @@ -25,7 +25,7 @@ main() async { } } dospace.Bucket bucket = spaces.bucket(bucketName); - String etag = await bucket.uploadFile( + String? etag = await bucket.uploadFile( 'README.md', 'README.md', 'text/plain', dospace.Permissions.public); print('upload: $etag'); @@ -34,7 +34,7 @@ main() async { // Basic pre-signed upload { - String preSignUrl = bucket.preSignUpload('README.md'); + String preSignUrl = bucket.preSignUpload('README.md')!; print('upload url: ${preSignUrl}'); var httpClient = new http.Client(); var httpRequest = new http.Request('PUT', Uri.parse(preSignUrl)); @@ -42,7 +42,7 @@ main() async { String body = await utf8.decodeStream(httpResponse.stream); print('${httpResponse.statusCode} ${httpResponse.reasonPhrase}'); print(body); - await httpClient.close(); + httpClient.close(); } // Pre-signed upload with specific payload @@ -51,7 +51,7 @@ main() async { int contentLength = await input.length(); Digest contentSha256 = await sha256.bind(input.openRead()).first; String preSignUrl = bucket.preSignUpload('README.md', - contentLength: contentLength, contentSha256: contentSha256); + contentLength: contentLength, contentSha256: contentSha256)!; print('strict upload url: ${preSignUrl}'); var httpClient = new http.Client(); var httpRequest = new http.Request('PUT', Uri.parse(preSignUrl)); @@ -59,7 +59,7 @@ main() async { String body = await utf8.decodeStream(httpResponse.stream); print('${httpResponse.statusCode} ${httpResponse.reasonPhrase}'); print(body); - await httpClient.close(); + httpClient.close(); } print('done'); diff --git a/lib/src/dospace_bucket.dart b/lib/src/dospace_bucket.dart index 16869f3..22f80e6 100644 --- a/lib/src/dospace_bucket.dart +++ b/lib/src/dospace_bucket.dart @@ -16,11 +16,11 @@ enum Permissions { class Bucket extends Client { Bucket( - {@required String region, - @required String accessKey, - @required String secretKey, - String endpointUrl, - http.Client httpClient}) + {required String? region, + required String? accessKey, + required String? secretKey, + String? endpointUrl, + http.Client? httpClient}) : super( region: region, accessKey: accessKey, @@ -39,12 +39,32 @@ class Bucket extends Client { } } + /// Delete an object + /// https://developers.digitalocean.com/documentation/spaces/#delete-object + Future deleteFile(String path) async { + String uriStr = endpointUrl + '/' + path; + http.Request request = new http.Request('DELETE', Uri.parse(uriStr)); + request.headers['Content-Type'] = "application/json"; + signRequest(request); + + http.StreamedResponse response = await httpClient.send(request); + String body = await utf8.decodeStream(response.stream); + + // "204 No Content" means the object was deleted (success) + if (response.statusCode != 204) { + throw new ClientException( + response.statusCode, response.reasonPhrase, response.headers, body); + } + + return Future.value(true); + } + /// List the Bucket's Contents. /// https://developers.digitalocean.com/documentation/spaces/#list-bucket-contents Stream listContents( - {String delimiter, String prefix, int maxKeys}) async* { - bool isTruncated; - String marker; + {String? delimiter, String? prefix, int? maxKeys}) async* { + late bool isTruncated; + String? marker; do { Uri uri = Uri.parse(endpointUrl + '/'); Map params = new Map(); @@ -70,10 +90,10 @@ class Bucket extends Client { ele.text.toLowerCase() != "false" && ele.text != "0"; break; case "Contents": - String key; - DateTime lastModifiedUtc; - String eTag; - int size; + String? key; + DateTime? lastModifiedUtc; + String? eTag; + int? size; for (xml.XmlNode node in ele.children) { if (node is xml.XmlElement) { xml.XmlElement ele = node; @@ -108,10 +128,10 @@ class Bucket extends Client { } /// Uploads file. Returns Etag. - Future uploadFile( + Future uploadFile( String key, dynamic file, String contentType, Permissions permissions, - {Map meta}) async { - int contentLength = await file.length(); + {Map? meta}) async { + int? contentLength = await file.length(); Digest contentSha256 = await sha256.bind(file.openRead()).first; String uriStr = endpointUrl + '/' + key; http.StreamedRequest request = @@ -136,14 +156,14 @@ class Bucket extends Client { throw new ClientException( response.statusCode, response.reasonPhrase, response.headers, body); } - String etag = response.headers['etag']; + String? etag = response.headers['etag']; return etag; } /// Uploads data from memory. Returns Etag. - Future uploadData( + Future uploadData( String key, Uint8List data, String contentType, Permissions permissions, - {Map meta, Digest contentSha256}) async { + {Map? meta, Digest? contentSha256}) async { int contentLength = await data.length; Digest contentSha256_ = contentSha256 != null ? contentSha256 : await sha256.convert(data); @@ -168,17 +188,17 @@ class Bucket extends Client { throw new ClientException( response.statusCode, response.reasonPhrase, response.headers, body); } - String etag = response.headers['etag']; + String? etag = response.headers['etag']; return etag; } - String preSignUpload(String key, - {int contentLength, - String contentType, - Digest contentSha256, + String? preSignUpload(String key, + {int? contentLength, + String? contentType, + Digest? contentSha256, Permissions permissions = Permissions.private, int expires = 900, - Map meta}) { + Map? meta}) { String uriStr = endpointUrl + '/' + key; Uri uriBase = Uri.parse(uriStr); Map queryParameters = new Map(); diff --git a/lib/src/dospace_client.dart b/lib/src/dospace_client.dart index 9584903..ec7c80d 100644 --- a/lib/src/dospace_client.dart +++ b/lib/src/dospace_client.dart @@ -7,7 +7,7 @@ import 'package:xml/xml.dart' as xml; class ClientException implements Exception { final int statusCode; - final String reasonPhrase; + final String? reasonPhrase; final Map responseHeaders; final String responseBody; const ClientException(this.statusCode, this.reasonPhrase, @@ -18,9 +18,9 @@ class ClientException implements Exception { } class Client { - final String region; - final String accessKey; - final String secretKey; + final String? region; + final String? accessKey; + final String? secretKey; final String service; final String endpointUrl; @@ -28,12 +28,12 @@ class Client { final http.Client httpClient; Client( - {@required this.region, - @required this.accessKey, - @required this.secretKey, - @required this.service, - String endpointUrl, - http.Client httpClient}) + {required this.region, + required this.accessKey, + required this.secretKey, + required this.service, + String? endpointUrl, + http.Client? httpClient}) : this.endpointUrl = endpointUrl ?? "https://${region}.digitaloceanspaces.com", this.httpClient = httpClient ?? new http.Client() { @@ -43,7 +43,7 @@ class Client { } Future close() async { - await httpClient.close(); + httpClient.close(); } @protected @@ -56,7 +56,7 @@ class Client { throw new ClientException( response.statusCode, response.reasonPhrase, response.headers, body); } - xml.XmlDocument doc = xml.parse(body); + xml.XmlDocument doc = xml.XmlDocument.parse(body); return doc; } @@ -75,8 +75,8 @@ class Client { } @protected - String signRequest(http.BaseRequest request, - {Digest contentSha256, bool preSignedUrl = false, int expires = 86400}) { + String? signRequest(http.BaseRequest request, + {Digest? contentSha256, bool preSignedUrl = false, int expires = 86400}) { // Build canonical request String httpMethod = request.method; String canonicalURI = request.url.path; @@ -104,7 +104,7 @@ class Client { '${accessKey}/${dateYYYYMMDD}/${region}/${service}/aws4_request'; // Build canonical headers string - Map headers = new Map(); + Map headers = new Map(); if (!preSignedUrl) { request.headers['x-amz-date'] = dateIso8601; // Set date in header if (contentSha256 != null) { @@ -117,7 +117,7 @@ class Client { headers['host'] = host; // Host is a builtin header List headerNames = headers.keys.toList()..sort(); String canonicalHeaders = - headerNames.map((s) => '${s}:${_trimAll(headers[s])}' + '\n').join(); + headerNames.map((s) => '${s}:${_trimAll(headers[s]!)}' + '\n').join(); String signedHeaders = headerNames.join(';'); @@ -137,7 +137,7 @@ class Client { } List queryKeys = queryParameters.keys.toList()..sort(); String canonicalQueryString = queryKeys - .map((s) => '${_uriEncode(s)}=${_uriEncode(queryParameters[s])}') + .map((s) => '${_uriEncode(s)}=${_uriEncode(queryParameters[s]!)}') .join('&'); if (preSignedUrl) { @@ -160,7 +160,7 @@ class Client { Digest dateKey = new Hmac(sha256, utf8.encode("AWS4${secretKey}")) .convert(utf8.encode(dateYYYYMMDD)); Digest dateRegionKey = - new Hmac(sha256, dateKey.bytes).convert(utf8.encode(region)); + new Hmac(sha256, dateKey.bytes).convert(utf8.encode(region!)); Digest dateRegionServiceKey = new Hmac(sha256, dateRegionKey.bytes).convert(utf8.encode(service)); Digest signingKey = new Hmac(sha256, dateRegionServiceKey.bytes) diff --git a/lib/src/dospace_results.dart b/lib/src/dospace_results.dart index da7ccec..a66245a 100644 --- a/lib/src/dospace_results.dart +++ b/lib/src/dospace_results.dart @@ -2,21 +2,21 @@ import 'package:meta/meta.dart'; class BucketContent { /// The object's key. - final String key; + final String? key; /// The date and time that the object was last modified in the format: %Y-%m-%dT%H:%M:%S.%3NZ (e.g. 2017-06-23T18:37:48.157Z) - final DateTime lastModifiedUtc; + final DateTime? lastModifiedUtc; /// The entity tag containing an MD5 hash of the object. - final String eTag; + final String? eTag; /// The size of the object in bytes. - final int size; + final int? size; BucketContent({ - @required this.key, - @required this.lastModifiedUtc, - @required this.eTag, - @required this.size, + required this.key, + required this.lastModifiedUtc, + required this.eTag, + required this.size, }); } diff --git a/lib/src/dospace_spaces.dart b/lib/src/dospace_spaces.dart index daa57dc..0f8e72e 100644 --- a/lib/src/dospace_spaces.dart +++ b/lib/src/dospace_spaces.dart @@ -8,11 +8,11 @@ import 'dospace_bucket.dart'; class Spaces extends Client { Spaces( - {@required String region, - @required String accessKey, - @required String secretKey, - String endpointUrl, - http.Client httpClient}) + {required String? region, + required String? accessKey, + required String? secretKey, + String? endpointUrl, + http.Client? httpClient}) : super( region: region, accessKey: accessKey, @@ -23,7 +23,7 @@ class Spaces extends Client { // ... } - Bucket bucket(String bucket) { + Bucket bucket(String? bucket) { if (endpointUrl == "https://${region}.digitaloceanspaces.com") { return new Bucket( region: region, @@ -39,7 +39,7 @@ class Spaces extends Client { Future> listAllBuckets() async { xml.XmlDocument doc = await getUri(Uri.parse(endpointUrl + '/')); - List res = new List(); + List res = new List.empty(growable: true); for (xml.XmlElement root in doc.findElements('ListAllMyBucketsResult')) { for (xml.XmlElement buckets in root.findElements('Buckets')) { for (xml.XmlElement bucket in buckets.findElements('Bucket')) { @@ -52,7 +52,7 @@ class Spaces extends Client { return res; } - String preSignListAllBuckets() { + String? preSignListAllBuckets() { http.Request request = new http.Request('GET', Uri.parse(endpointUrl + '/')); return signRequest(request, preSignedUrl: true); diff --git a/pubspec.yaml b/pubspec.yaml index ef30576..fc8b055 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,18 +1,20 @@ name: dospace -description: Client library to interact with the DigitalOcean Spaces API. Same API as Amazon AWS S3. -version: 0.12.12 +description: Client library to interact with the DigitalOcean Spaces API. Same + API as Amazon AWS S3. +version: 0.12.13 homepage: https://github.com/nbspou/dospace author: NO-BREAK SPACE OÜ environment: - sdk: '>=2.0.0-dev.58.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dependencies: - meta: ^1.1.5 - crypto: ^2.0.6 - http: ^0.12.0 - xml: ^3.0.1 + crypto: ^3.0.1 + http: ^0.13.1 + meta: ^1.3.0 + mime: ^1.0.0 + xml: ^6.1.0 dev_dependencies: - test: ^1.0.0 - ini: ^2.0.1 + ini: ^2.1.0 + test: ^1.16.8 diff --git a/test/dospace_test.dart b/test/dospace_test.dart index 76bf687..ac66de2 100644 --- a/test/dospace_test.dart +++ b/test/dospace_test.dart @@ -4,10 +4,11 @@ import 'dart:typed_data'; import 'package:crypto/crypto.dart'; import 'package:dospace/dospace.dart' as dospace; import 'package:ini/ini.dart' as ini; +import 'package:mime/mime.dart'; import 'package:test/test.dart'; -dospace.Spaces spaces; -dospace.Bucket bucket; +dospace.Spaces? spaces; +dospace.Bucket? bucket; Future main() async { List lines = await new File("test/test.ini").readAsLines(); @@ -15,31 +16,36 @@ Future main() async { setUp(() async { spaces = new dospace.Spaces( - region: cfg.get("Spaces", "region"), - accessKey: cfg.get("Spaces", "key"), - secretKey: cfg.get("Spaces", "secret"), + region: cfg.get("section", "region"), + accessKey: cfg.get("section", "accessKey"), + secretKey: cfg.get("section", "secretKey"), ); - bucket = spaces.bucket(cfg.get("Bucket", "bucket")); + bucket = spaces!.bucket(cfg.get("section", "bucketName")); }); tearDown(() async { - await spaces.close(); + await spaces!.close(); bucket = null; spaces = null; }); test("Bucket is in Spaces", () async { - List buckets = await spaces.listAllBuckets(); - expect(buckets, contains(cfg.get("Bucket", "bucket"))); + List buckets = await spaces!.listAllBuckets(); + expect(buckets, contains(cfg.get("section", "bucketName"))); }); test("Upload file", () async { - String etag = await bucket.uploadFile( + String? etag = await bucket!.uploadFile( "dospace_test.dart", - new File("test/dospace_test.dart"), - "text/plain", + File("test/dospace_test.dart"), + lookupMimeType(File("test/dospace_test.dart").path)!, dospace.Permissions.public); expect(etag, isNotEmpty); + }, timeout: Timeout(Duration(seconds: 20))); + + test("Delete file", () async { + bool etag = await bucket!.deleteFile('dospace_test.dart'); + expect(etag, isTrue); }); test("Upload binary data", () async { @@ -47,7 +53,7 @@ Future main() async { await new File("test/dospace_test.dart").readAsBytes()); Digest contentSha256 = sha256.convert(data); String key = "test/user/1/$contentSha256.dart"; - bucket.uploadData(key, data, "text/plain", dospace.Permissions.public, + bucket!.uploadData(key, data, "text/plain", dospace.Permissions.public, contentSha256: contentSha256); }); } diff --git a/test/flutter_outline.mp4 b/test/flutter_outline.mp4 new file mode 100644 index 0000000..7bc937f Binary files /dev/null and b/test/flutter_outline.mp4 differ