From 8b1c3cc2e6723b49593e372c08ee64e5e7a11c96 Mon Sep 17 00:00:00 2001
From: xuty <xty50337@hotmail.com>
Date: Sun, 29 Mar 2020 19:43:10 +0800
Subject: [PATCH] finish listObjectsV2

---
 README.md                  |   2 +-
 example/minio_example.dart |  55 +++++++++----------
 lib/src/minio.dart         | 108 ++++++++++++++++++++++++++++++++-----
 lib/src/minio_models.dart  |   7 +++
 lib/src/utils.dart         |   2 +-
 5 files changed, 127 insertions(+), 47 deletions(-)

diff --git a/README.md b/README.md
index 9539925..836c683 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Created from templates made available by Stagehand under a BSD-style
 | `bucketExists`         	| fGetObject             	| presignedPutObject   	| removeAllBucketNotification             	|
 | `removeBucket`         	| `putObject`            	| presignedPostPolicy  	| getBucketPolicy                         	|
 | `listObjects`          	| fPutObject             	|                      	| setBucketPolicy                         	|
-| listObjectsV2         	| `copyObject`           	|                      	| listenBucketNotification                	|
+| `listObjectsV2`        	| `copyObject`           	|                      	| listenBucketNotification                	|
 | `listIncompleteUploads`	| `statObject`           	|                      	|                                         	|
 |                       	| `removeObject`         	|                      	|                                         	|
 |                       	| `removeObjects`        	|                      	|                                         	|
diff --git a/example/minio_example.dart b/example/minio_example.dart
index c26829b..882c397 100644
--- a/example/minio_example.dart
+++ b/example/minio_example.dart
@@ -1,7 +1,6 @@
 import 'dart:io';
 
 import 'package:minio/minio.dart';
-import 'package:minio/models.dart';
 
 void main() async {
   final minio = Minio(
@@ -9,12 +8,12 @@ void main() async {
     accessKey: 'Q3AM3UQ867SPQQA43P2F',
     secretKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG',
     useSSL: false,
-    enableTrace: true,
+    // enableTrace: true,
   );
 
   final bucket = '00test';
   final object = 'teaweb.png';
-  final copy1 = '$object.copy';
+  final copy1 = '$object.copy1';
   final copy2 = '$object.copy2';
 
   if (!await minio.bucketExists(bucket)) {
@@ -24,52 +23,46 @@ void main() async {
     print('bucket $bucket already exists');
   }
 
-  // print(await minio.bucketExists('02test'));
-  // await minio.makeBucket('00test');
-  // await minio.removeBucket('05test');
-  // print(await minio.getBucketRegion('00test'));
-  // print(await minio.getBucketRegion('00test'));
-  // print((await minio.listBuckets()).map((e) => e.name));
-  // print(await minio.listObjectsQuery('00test', '/', null, '', null));
-
-  // await minio.listObjects('0inst').forEach((chunk) {
-  //   print(chunk.objects.join('\n'));
-  //   print(chunk.prefixes.join('\n'));
-  // });
-
-  // await minio.listObjects('0inst', recursive: true).forEach((o) => print(o.key));
-
-  // final object = await minio.getObject('00test', 'sys8_captcha.png');
-  // await File('sys8_captcha.png').openWrite().addStream(object);
+  final region = await minio.getBucketRegion('00test');
+  print('--- object region:');
+  print(region);
 
   final file = File('example/$object');
   final size = await file.length();
   final etag = await minio.putObject(bucket, object, file.openRead(), size);
-  print('---');
-  print('etag:');
+  print('--- etag:');
   print(etag);
 
   final copyResult1 = await minio.copyObject(bucket, copy1, '$bucket/$object');
   final copyResult2 = await minio.copyObject(bucket, copy2, '$bucket/$object');
-  print('---');
-  print('Copy1 etag:');
+  print('--- copy1 etag:');
   print(copyResult1.eTag);
-  print('---');
-  print('Copy2 etag:');
+  print('--- copy2 etag:');
   print(copyResult2.eTag);
 
+  await minio.listObjects(bucket).forEach((chunk) {
+    print('--- objects:');
+    chunk.objects.forEach((o) => print(o.key));
+  });
+
+  await minio.listObjectsV2(bucket).forEach((chunk) {
+    print('--- objects(v2):');
+    chunk.objects.forEach((o) => print(o.key));
+  });
+
   final stat = await minio.statObject(bucket, object);
-  print('Stat:');
+  print('--- object stat:');
   print(stat.etag);
   print(stat.size);
   print(stat.lastModified);
   print(stat.metaData);
 
   await minio.removeObject(bucket, object);
-  print('---');
-  print('Removed');
+  print('--- object removed');
 
   await minio.removeObjects(bucket, [copy1, copy2]);
-  print('---');
-  print('Copy removed');
+  print('--- copy1, copy2 removed');
+
+  await minio.removeBucket(bucket);
+  print('--- bucket removed');
 }
diff --git a/lib/src/minio.dart b/lib/src/minio.dart
index 74120b4..134dc08 100644
--- a/lib/src/minio.dart
+++ b/lib/src/minio.dart
@@ -156,8 +156,8 @@ class MinioClient {
       if (object != null) path = '/${bucket}/${object}';
     }
 
-    final resourcePart = resource == null ? '' : '$resource&';
-    final queryPart = queries == null ? '' : encodeQueries(queries);
+    final resourcePart = resource == null ? '' : '$resource';
+    final queryPart = queries == null ? '' : '&${encodeQueries(queries)}';
     final query = resourcePart + queryPart;
 
     return Uri(
@@ -399,9 +399,13 @@ class Minio {
     validate(resp);
 
     final node = xml.parse(resp.body);
-    final location = node.findAllElements('LocationConstraint').first.text;
 
-    _regionMap[bucket] = location ?? 'us-east-1';
+    var location = node.findAllElements('LocationConstraint').first.text;
+    if (location == null || location.isEmpty) {
+      location = 'us-east-1';
+    }
+
+    _regionMap[bucket] = location;
     return location;
   }
 
@@ -560,7 +564,7 @@ class Minio {
     MinioInvalidPrefixError.check(prefix);
     final delimiter = recursive ? '' : '/';
 
-    var marker = '';
+    String marker;
     var isTruncated = false;
 
     do {
@@ -625,6 +629,85 @@ class Minio {
       ..nextMarker = nextMarker;
   }
 
+  /// Returns all [Object]s in a bucket.
+  /// If recursive is true, the returned stream may also contains [CommonPrefix]
+  Stream<ListObjectsChunk> listObjectsV2(
+    String bucket, {
+    String prefix = '',
+    bool recursive = false,
+    String startAfter,
+  }) async* {
+    MinioInvalidBucketNameError.check(bucket);
+    MinioInvalidPrefixError.check(prefix);
+    final delimiter = recursive ? '' : '/';
+
+    var isTruncated = false;
+    String continuationToken;
+
+    do {
+      final resp = await listObjectsV2Query(
+          bucket, prefix, continuationToken, delimiter, 1000, startAfter);
+      isTruncated = resp.isTruncated;
+      continuationToken = resp.nextContinuationToken;
+      yield ListObjectsChunk()
+        ..objects = resp.contents
+        ..prefixes = resp.commonPrefixes.map((e) => e.prefix).toList();
+    } while (isTruncated);
+  }
+
+  Future<ListObjectsV2Output> listObjectsV2Query(
+    String bucket,
+    String prefix,
+    String continuationToken,
+    String delimiter,
+    int maxKeys,
+    String startAfter,
+  ) async {
+    MinioInvalidBucketNameError.check(bucket);
+    MinioInvalidPrefixError.check(prefix);
+
+    final queries = <String, String>{};
+    queries['prefix'] = prefix;
+    queries['delimiter'] = delimiter;
+    queries['list-type'] = '2';
+
+    if (continuationToken != null) {
+      queries['continuation-token'] = continuationToken;
+    }
+
+    if (startAfter != null) {
+      queries['start-after'] = startAfter;
+    }
+
+    if (maxKeys != null) {
+      maxKeys = maxKeys >= 1000 ? 1000 : maxKeys;
+      queries['maxKeys'] = maxKeys.toString();
+    }
+
+    final resp = await _client.request(
+      method: 'GET',
+      bucket: bucket,
+      queries: queries,
+    );
+
+    validate(resp);
+
+    final node = xml.parse(resp.body);
+    final isTruncated = getNodeProp(node.rootElement, 'IsTruncated')?.text;
+    final nextContinuationToken =
+        getNodeProp(node.rootElement, 'NextContinuationToken')?.text;
+    final objs = node.findAllElements('Contents').map((c) => Object.fromXml(c));
+    final prefixes = node
+        .findAllElements('CommonPrefixes')
+        .map((c) => CommonPrefix.fromXml(c));
+
+    return ListObjectsV2Output()
+      ..contents = objs.toList()
+      ..commonPrefixes = prefixes.toList()
+      ..isTruncated = isTruncated.toLowerCase() == 'true'
+      ..nextContinuationToken = nextContinuationToken;
+  }
+
   Stream<Part> listParts(
     String bucket,
     String object,
@@ -758,17 +841,14 @@ class Minio {
         true,
       ).toXml().toString();
 
-      final headers = {
-        'Content-MD5': md5Base64(payload)
-      };
+      final headers = {'Content-MD5': md5Base64(payload)};
 
       await _client.request(
-        method: 'POST',
-        bucket: bucket,
-        resource: 'delete',
-        headers: headers,
-        payload: payload
-      );
+          method: 'POST',
+          bucket: bucket,
+          resource: 'delete',
+          headers: headers,
+          payload: payload);
     }
   }
 
diff --git a/lib/src/minio_models.dart b/lib/src/minio_models.dart
index 404c4dc..8de010b 100644
--- a/lib/src/minio_models.dart
+++ b/lib/src/minio_models.dart
@@ -13,6 +13,13 @@ class ListObjectsOutput {
   List<CommonPrefix> commonPrefixes;
 }
 
+class ListObjectsV2Output {
+  bool isTruncated;
+  String nextContinuationToken;
+  List<Object> contents;
+  List<CommonPrefix> commonPrefixes;
+}
+
 class CompleteMultipartUpload {
   CompleteMultipartUpload(
     this.parts,
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 1f20c22..d62d422 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -44,7 +44,7 @@ String encodeQueries(Map<String, String> queries) {
     final value = queries[key];
     pairs.add(encodeQuery(key, value));
   }
-  return pairs.join('=');
+  return pairs.join('&');
 }
 
 class BlockStream extends StreamTransformerBase<List<int>, List<int>> {
-- 
GitLab