// ignore_for_file: implementation_imports import "dart:io"; import "package:dio/dio.dart"; import "package:logging/logging.dart"; import "package:photos/core/constants.dart"; import "package:photos/core/network/network.dart"; import 'package:photos/module/upload/model/xml.dart'; import "package:photos/service_locator.dart"; final _enteDio = NetworkClient.instance.enteDio; final _dio = NetworkClient.instance.getDio(); class PartETag extends XmlParsableObject { final int partNumber; final String eTag; PartETag(this.partNumber, this.eTag); @override String get elementName => "Part"; @override Map toMap() { return { "PartNumber": partNumber, "ETag": eTag, }; } } class MultipartUploadURLs { final String objectKey; final List partsURLs; final String completeURL; MultipartUploadURLs({ required this.objectKey, required this.partsURLs, required this.completeURL, }); factory MultipartUploadURLs.fromMap(Map map) { return MultipartUploadURLs( objectKey: map["urls"]["objectKey"], partsURLs: (map["urls"]["partURLs"] as List).cast(), completeURL: map["urls"]["completeURL"], ); } } Future calculatePartCount(int fileSize) async { final partCount = (fileSize / multipartPartSize).ceil(); return partCount; } Future getMultipartUploadURLs(int count) async { try { assert( flagService.internalUser, "Multipart upload should not be enabled for external users.", ); final response = await _enteDio.get( "/files/multipart-upload-urls", queryParameters: { "count": count, }, ); return MultipartUploadURLs.fromMap(response.data); } on Exception catch (e) { Logger("MultipartUploadURL").severe(e); rethrow; } } Future putMultipartFile( MultipartUploadURLs urls, File encryptedFile, ) async { // upload individual parts and get their etags final etags = await uploadParts(urls.partsURLs, encryptedFile); // complete the multipart upload await completeMultipartUpload(etags, urls.completeURL); return urls.objectKey; } Future> uploadParts( List partsURLs, File encryptedFile, ) async { final partsLength = partsURLs.length; final etags = {}; for (int i = 0; i < partsLength; i++) { final partURL = partsURLs[i]; final isLastPart = i == partsLength - 1; final fileSize = isLastPart ? encryptedFile.lengthSync() % multipartPartSize : multipartPartSize; final response = await _dio.put( partURL, data: encryptedFile.openRead( i * multipartPartSize, isLastPart ? null : multipartPartSize, ), options: Options( headers: { Headers.contentLengthHeader: fileSize, }, ), ); final eTag = response.headers.value("etag"); if (eTag?.isEmpty ?? true) { throw Exception('ETAG_MISSING'); } etags[i] = eTag!; } return etags; } Future completeMultipartUpload( Map partEtags, String completeURL, ) async { final body = convertJs2Xml({ 'CompleteMultipartUpload': partEtags.entries .map( (e) => PartETag( e.key + 1, e.value, ), ) .toList(), }).replaceAll('"', '').replaceAll('"', ''); try { await _dio.post( completeURL, data: body, options: Options( contentType: "text/xml", ), ); } catch (e) { Logger("MultipartUpload").severe(e); rethrow; } }