From 2917b2b68dd50152905781d1633de16cf8573fae Mon Sep 17 00:00:00 2001
From: xuty <xty50337@hotmail.com>
Date: Sun, 29 Mar 2020 14:50:07 +0800
Subject: [PATCH] finish removeObject

---
 example/minio_example.dart          | 29 +++++++++++++----
 lib/src/minio.dart                  | 50 +++++++++++++++++++++++++----
 lib/src/minio_models_generated.dart | 29 ++++++++++-------
 lib/src/utils.dart                  | 19 ++++++++++-
 4 files changed, 101 insertions(+), 26 deletions(-)

diff --git a/example/minio_example.dart b/example/minio_example.dart
index 4e2329a..c26829b 100644
--- a/example/minio_example.dart
+++ b/example/minio_example.dart
@@ -6,13 +6,16 @@ import 'package:minio/models.dart';
 void main() async {
   final minio = Minio(
     endPoint: 'play.min.io',
-    useSSL: false,
     accessKey: 'Q3AM3UQ867SPQQA43P2F',
     secretKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG',
+    useSSL: false,
+    enableTrace: true,
   );
 
   final bucket = '00test';
   final object = 'teaweb.png';
+  final copy1 = '$object.copy';
+  final copy2 = '$object.copy2';
 
   if (!await minio.bucketExists(bucket)) {
     await minio.makeBucket(bucket);
@@ -42,14 +45,18 @@ void main() async {
   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);
 
-  final copyResult = await minio.copyObject(
-    bucket,
-    '$object.copy',
-    '$bucket/$object',
-  );
-  print(copyResult.eTag);
+  final copyResult1 = await minio.copyObject(bucket, copy1, '$bucket/$object');
+  final copyResult2 = await minio.copyObject(bucket, copy2, '$bucket/$object');
+  print('---');
+  print('Copy1 etag:');
+  print(copyResult1.eTag);
+  print('---');
+  print('Copy2 etag:');
+  print(copyResult2.eTag);
 
   final stat = await minio.statObject(bucket, object);
   print('Stat:');
@@ -57,4 +64,12 @@ void main() async {
   print(stat.size);
   print(stat.lastModified);
   print(stat.metaData);
+
+  await minio.removeObject(bucket, object);
+  print('---');
+  print('Removed');
+
+  await minio.removeObjects(bucket, [copy1, copy2]);
+  print('---');
+  print('Copy removed');
 }
diff --git a/lib/src/minio.dart b/lib/src/minio.dart
index d1c3043..74120b4 100644
--- a/lib/src/minio.dart
+++ b/lib/src/minio.dart
@@ -169,10 +169,8 @@ class MinioClient {
     );
   }
 
-  static var enableTrace = true;
-
-  static void logRequest(MinioRequest request) {
-    if (!enableTrace) return;
+  void logRequest(MinioRequest request) {
+    if (!minio.enableTrace) return;
 
     final buffer = StringBuffer();
     buffer.writeln('REQUEST: ${request.method} ${request.url}');
@@ -189,8 +187,8 @@ class MinioClient {
     print(buffer.toString());
   }
 
-  static void logResponse(BaseResponse response) {
-    if (!enableTrace) return;
+  void logResponse(BaseResponse response) {
+    if (!minio.enableTrace) return;
 
     final buffer = StringBuffer();
     buffer.writeln('RESPONSE: ${response.statusCode} ${response.reasonPhrase}');
@@ -217,6 +215,7 @@ class Minio {
     this.secretKey = '',
     this.sessionToken,
     this.region,
+    this.enableTrace = false,
   })  : assert(isValidEndpoint(endPoint)),
         assert(port == null || isValidPort(port)),
         assert(useSSL != null),
@@ -236,6 +235,7 @@ class Minio {
   final String secretKey;
   final String sessionToken;
   final String region;
+  final bool enableTrace;
 
   MinioClient _client;
   final _regionMap = <String, String>{};
@@ -734,6 +734,44 @@ class Minio {
     _regionMap.remove(bucket);
   }
 
+  Future<void> removeObject(String bucket, String object) async {
+    MinioInvalidBucketNameError.check(bucket);
+    MinioInvalidObjectNameError.check(object);
+
+    final resp = await _client.request(
+      method: 'DELETE',
+      bucket: bucket,
+      object: object,
+    );
+
+    validate(resp, expect: 204);
+  }
+
+  Future<void> removeObjects(String bucket, List<String> objects) async {
+    MinioInvalidBucketNameError.check(bucket);
+
+    final bunches = groupList(objects, 1000);
+
+    for (var bunch in bunches) {
+      final payload = Delete(
+        bunch.map((key) => ObjectIdentifier(key, null)).toList(),
+        true,
+      ).toXml().toString();
+
+      final headers = {
+        'Content-MD5': md5Base64(payload)
+      };
+
+      await _client.request(
+        method: 'POST',
+        bucket: bucket,
+        resource: 'delete',
+        headers: headers,
+        payload: payload
+      );
+    }
+  }
+
   Future<StatObjectResult> statObject(String bucket, String object) async {
     MinioInvalidBucketNameError.check(bucket);
     MinioInvalidObjectNameError.check(object);
diff --git a/lib/src/minio_models_generated.dart b/lib/src/minio_models_generated.dart
index e76ab16..36a67a6 100644
--- a/lib/src/minio_models_generated.dart
+++ b/lib/src/minio_models_generated.dart
@@ -442,7 +442,7 @@ class CompletedPart {
       builder.element('ETag', nest: eTag);
       builder.element('PartNumber', nest: partNumber.toString());
     });
-    return builder.build();
+    return (builder.build() as XmlDocument).rootElement;
   }
 
   /// Entity tag returned when the part was uploaded.
@@ -789,22 +789,24 @@ class Delete {
     this.quiet,
   );
 
-  Delete.fromXml(XmlElement xml) {
-    objects = ObjectIdentifier.fromXml(getProp(xml, 'Objects'));
-    quiet = getProp(xml, 'Quiet')?.text?.toUpperCase() == 'TRUE';
-  }
+  // Delete.fromXml(XmlElement xml) {
+  //   objects = ObjectIdentifier.fromXml(getProp(xml, 'Objects'));
+  //   quiet = getProp(xml, 'Quiet')?.text?.toUpperCase() == 'TRUE';
+  // }
 
   XmlNode toXml() {
     final builder = XmlBuilder();
     builder.element('Delete', nest: () {
-      builder.element('Objects', nest: objects.toXml());
+      for (var object in objects) {
+        builder.element('Object', nest: object.toXml().children);
+      }
       builder.element('Quiet', nest: quiet ? 'TRUE' : 'FALSE');
     });
     return builder.build();
   }
 
   /// The objects to delete.
-  ObjectIdentifier objects;
+  List<ObjectIdentifier> objects;
 
   /// Element to enable quiet mode for the request. When you add this element, you must set its value to true.
   bool quiet;
@@ -1664,7 +1666,8 @@ class LifecycleExpiration {
     date = DateTime.parse(getProp(xml, 'Date')?.text);
     days = int.tryParse(getProp(xml, 'Days')?.text);
     expiredObjectDeleteMarker =
-        getProp(xml, 'ExpiredObjectDeleteMarker')?.text?.toUpperCase() == 'TRUE';
+        getProp(xml, 'ExpiredObjectDeleteMarker')?.text?.toUpperCase() ==
+            'TRUE';
   }
 
   XmlNode toXml() {
@@ -2271,13 +2274,15 @@ class ObjectIdentifier {
     versionId = getProp(xml, 'VersionId')?.text;
   }
 
-  XmlNode toXml() {
+  XmlElement toXml() {
     final builder = XmlBuilder();
-    builder.element('ObjectIdentifier', nest: () {
+    builder.element('Object', nest: () {
       builder.element('Key', nest: key);
-      builder.element('VersionId', nest: versionId);
+      if (versionId != null) {
+        builder.element('VersionId', nest: versionId);
+      }
     });
-    return builder.build();
+    return (builder.build() as XmlDocument).rootElement;
   }
 
   /// Key name of the object to delete.
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index e0be398..1f20c22 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -1,5 +1,6 @@
 import 'dart:async';
 import 'dart:convert';
+import 'dart:math' as math;
 
 import 'package:buffer/buffer.dart';
 import 'package:convert/convert.dart';
@@ -19,6 +20,11 @@ String sha256Hex(Object data) {
   return hex.encode(sha256.convert(data).bytes);
 }
 
+String md5Base64(String source) {
+  final md5digest = md5.convert(utf8.encode(source)).bytes;
+  return base64.encode(md5digest);
+}
+
 XmlElement getNodeProp(XmlElement xml, String name) {
   final result = xml.findElements(name);
   return result.isNotEmpty ? result.first : null;
@@ -73,4 +79,15 @@ String trimDoubleQuote(String str) {
 DateTime parseRfc7231Time(String time) {
   final format = DateFormat('EEE, dd MMM yyyy hh:mm:ss zzz');
   return format.parse(time);
-}
\ No newline at end of file
+}
+
+List<List<T>> groupList<T>(List<T> list, int maxMembers) {
+  final groups = (list.length / maxMembers).ceil();
+  final result = <List<T>>[];
+  for (var i = 0; i < groups; i++) {
+    final start = i * maxMembers;
+    final end = math.min(start + maxMembers, list.length);
+    result.add(list.sublist(start, end));
+  }
+  return result;
+}
-- 
GitLab