multipart_upload_util.dart 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. // ignore_for_file: implementation_imports
  2. import "dart:io";
  3. import "dart:typed_data";
  4. import "package:dio/dio.dart";
  5. import "package:logging/logging.dart";
  6. import "package:photos/core/constants.dart";
  7. import "package:photos/core/network/network.dart";
  8. import "package:photos/db/upload_locks_db.dart";
  9. import "package:photos/utils/crypto_util.dart";
  10. import "package:photos/utils/xml_parser_util.dart";
  11. final _enteDio = NetworkClient.instance.enteDio;
  12. final _dio = NetworkClient.instance.getDio();
  13. final _uploadLocksDb = UploadLocksDB.instance;
  14. class PartETag extends XmlParsableObject {
  15. final int partNumber;
  16. final String eTag;
  17. PartETag(this.partNumber, this.eTag);
  18. @override
  19. String get elementName => "Part";
  20. @override
  21. Map<String, dynamic> toMap() {
  22. return {
  23. "PartNumber": partNumber,
  24. "ETag": eTag,
  25. };
  26. }
  27. }
  28. class MultipartUploadURLs {
  29. final String objectKey;
  30. final List<String> partsURLs;
  31. final String completeURL;
  32. final List<bool>? partUploadStatus;
  33. final Map<int, String>? partETags;
  34. MultipartUploadURLs({
  35. required this.objectKey,
  36. required this.partsURLs,
  37. required this.completeURL,
  38. this.partUploadStatus,
  39. this.partETags,
  40. });
  41. factory MultipartUploadURLs.fromMap(Map<String, dynamic> map) {
  42. return MultipartUploadURLs(
  43. objectKey: map["urls"]["objectKey"],
  44. partsURLs: (map["urls"]["partURLs"] as List).cast<String>(),
  45. completeURL: map["urls"]["completeURL"],
  46. );
  47. }
  48. }
  49. Future<int> calculatePartCount(int fileSize) async {
  50. final partCount = (fileSize / multipartPartSize).ceil();
  51. return partCount;
  52. }
  53. Future<MultipartUploadURLs> getMultipartUploadURLs(int count) async {
  54. try {
  55. final response = await _enteDio.get(
  56. "/files/multipart-upload-urls",
  57. queryParameters: {
  58. "count": count,
  59. },
  60. );
  61. return MultipartUploadURLs.fromMap(response.data);
  62. } on Exception catch (e) {
  63. Logger("MultipartUploadURL").severe(e);
  64. rethrow;
  65. }
  66. }
  67. Future<void> createTableEntry(
  68. String localId,
  69. String fileHash,
  70. MultipartUploadURLs urls,
  71. String encryptedFilePath,
  72. int fileSize,
  73. Uint8List fileKey,
  74. ) async {
  75. await _uploadLocksDb.createTrackUploadsEntry(
  76. localId,
  77. fileHash,
  78. urls,
  79. encryptedFilePath,
  80. fileSize,
  81. CryptoUtil.bin2base64(fileKey),
  82. );
  83. }
  84. Future<String> putExistingMultipartFile(
  85. File encryptedFile,
  86. String localId,
  87. String fileHash,
  88. ) async {
  89. final (urls, status) = await _uploadLocksDb.getCachedLinks(localId, fileHash);
  90. Map<int, String> etags = urls.partETags ?? {};
  91. if (status == UploadLocksDB.trackStatus.pending) {
  92. // upload individual parts and get their etags
  93. etags = await uploadParts(urls, encryptedFile);
  94. }
  95. if (status != UploadLocksDB.trackStatus.completed) {
  96. // complete the multipart upload
  97. await completeMultipartUpload(urls.objectKey, etags, urls.completeURL);
  98. }
  99. return urls.objectKey;
  100. }
  101. Future<String> putMultipartFile(
  102. MultipartUploadURLs urls,
  103. File encryptedFile,
  104. ) async {
  105. // upload individual parts and get their etags
  106. final etags = await uploadParts(urls, encryptedFile);
  107. // complete the multipart upload
  108. await completeMultipartUpload(urls.objectKey, etags, urls.completeURL);
  109. return urls.objectKey;
  110. }
  111. Future<Map<int, String>> uploadParts(
  112. MultipartUploadURLs url,
  113. File encryptedFile,
  114. ) async {
  115. final partsURLs = url.partsURLs;
  116. final partUploadStatus = url.partUploadStatus;
  117. final partsLength = partsURLs.length;
  118. final etags = url.partETags ?? <int, String>{};
  119. for (int i = 0; i < partsLength; i++) {
  120. if (i < (partUploadStatus?.length ?? 0) &&
  121. (partUploadStatus?[i] ?? false)) {
  122. continue;
  123. }
  124. final partURL = partsURLs[i];
  125. final isLastPart = i == partsLength - 1;
  126. final fileSize = isLastPart
  127. ? encryptedFile.lengthSync() % multipartPartSize
  128. : multipartPartSize;
  129. final response = await _dio.put(
  130. partURL,
  131. data: encryptedFile.openRead(
  132. i * multipartPartSize,
  133. isLastPart ? null : (i + 1) * multipartPartSize,
  134. ),
  135. options: Options(
  136. headers: {
  137. Headers.contentLengthHeader: fileSize,
  138. },
  139. ),
  140. );
  141. final eTag = response.headers.value("etag");
  142. if (eTag?.isEmpty ?? true) {
  143. throw Exception('ETAG_MISSING');
  144. }
  145. etags[i] = eTag!;
  146. await _uploadLocksDb.updatePartStatus(url.objectKey, i, eTag);
  147. }
  148. await _uploadLocksDb.updateTrackUploadStatus(
  149. url.objectKey,
  150. UploadLocksDB.trackStatus.uploaded,
  151. );
  152. return etags;
  153. }
  154. Future<void> completeMultipartUpload(
  155. String objectKey,
  156. Map<int, String> partEtags,
  157. String completeURL,
  158. ) async {
  159. final body = convertJs2Xml({
  160. 'CompleteMultipartUpload': partEtags.entries
  161. .map(
  162. (e) => PartETag(
  163. e.key + 1,
  164. e.value,
  165. ),
  166. )
  167. .toList(),
  168. }).replaceAll('"', '').replaceAll('&quot;', '');
  169. try {
  170. await _dio.post(
  171. completeURL,
  172. data: body,
  173. options: Options(
  174. contentType: "text/xml",
  175. ),
  176. );
  177. await _uploadLocksDb.updateTrackUploadStatus(
  178. objectKey,
  179. UploadLocksDB.trackStatus.completed,
  180. );
  181. } catch (e) {
  182. Logger("MultipartUpload").severe(e);
  183. rethrow;
  184. }
  185. }