From 4323ae9422b937412875880f977686032e3ac725 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 28 Feb 2025 16:10:53 +0530 Subject: [PATCH 1/3] [mob] Move --- mobile/lib/models/api/metadata.dart | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 mobile/lib/models/api/metadata.dart diff --git a/mobile/lib/models/api/metadata.dart b/mobile/lib/models/api/metadata.dart new file mode 100644 index 0000000000..e69de29bb2 From 5b88b2b5633f96a70c999e9db050f282a0f6f4b6 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:43:37 +0530 Subject: [PATCH 2/3] [mob] Move model --- .../models/api/collection/create_request.dart | 2 +- mobile/lib/models/api/metadata.dart | 54 ++++++++++++++++++ mobile/lib/services/collections_service.dart | 3 +- mobile/lib/services/file_magic_service.dart | 56 +------------------ mobile/lib/services/hidden_service.dart | 2 +- mobile/lib/utils/file_uploader.dart | 5 +- mobile/lib/utils/file_uploader_util.dart | 2 +- mobile/lib/utils/lat_lon_util.dart | 14 ----- 8 files changed, 63 insertions(+), 75 deletions(-) delete mode 100644 mobile/lib/utils/lat_lon_util.dart diff --git a/mobile/lib/models/api/collection/create_request.dart b/mobile/lib/models/api/collection/create_request.dart index 0fc5866383..2cc3954bae 100644 --- a/mobile/lib/models/api/collection/create_request.dart +++ b/mobile/lib/models/api/collection/create_request.dart @@ -1,5 +1,5 @@ +import "package:photos/models/api/metadata.dart"; import 'package:photos/models/collection/collection.dart'; -import 'package:photos/services/file_magic_service.dart'; class CreateRequest { String encryptedKey; diff --git a/mobile/lib/models/api/metadata.dart b/mobile/lib/models/api/metadata.dart index e69de29bb2..dbb3a479b0 100644 --- a/mobile/lib/models/api/metadata.dart +++ b/mobile/lib/models/api/metadata.dart @@ -0,0 +1,54 @@ +class UpdateMagicMetadataRequest { + final int id; + final MetadataRequest? magicMetadata; + + UpdateMagicMetadataRequest({required this.id, required this.magicMetadata}); + + factory UpdateMagicMetadataRequest.fromJson(dynamic json) { + return UpdateMagicMetadataRequest( + id: json['id'], + magicMetadata: json['magicMetadata'] != null + ? MetadataRequest.fromJson(json['magicMetadata']) + : null, + ); + } + + Map toJson() { + final map = {}; + map['id'] = id; + if (magicMetadata != null) { + map['magicMetadata'] = magicMetadata!.toJson(); + } + return map; + } +} + +class MetadataRequest { + int? version; + int? count; + String? data; + String? header; + + MetadataRequest({ + required this.version, + required this.count, + required this.data, + required this.header, + }); + + MetadataRequest.fromJson(dynamic json) { + version = json['version']; + count = json['count']; + data = json['data']; + header = json['header']; + } + + Map toJson() { + final map = {}; + map['version'] = version; + map['count'] = count; + map['data'] = data; + map['header'] = header; + return map; + } +} diff --git a/mobile/lib/services/collections_service.dart b/mobile/lib/services/collections_service.dart index aacec742bd..b2e3c8e73e 100644 --- a/mobile/lib/services/collections_service.dart +++ b/mobile/lib/services/collections_service.dart @@ -29,6 +29,7 @@ import 'package:photos/models/api/collection/collection_file_item.dart'; import 'package:photos/models/api/collection/create_request.dart'; import "package:photos/models/api/collection/public_url.dart"; import "package:photos/models/api/collection/user.dart"; +import "package:photos/models/api/metadata.dart"; import 'package:photos/models/collection/collection.dart'; import 'package:photos/models/collection/collection_items.dart'; import 'package:photos/models/file/file.dart'; @@ -37,7 +38,6 @@ import "package:photos/models/metadata/collection_magic.dart"; import "package:photos/service_locator.dart"; import 'package:photos/services/app_lifecycle_service.dart'; import "package:photos/services/favorites_service.dart"; -import 'package:photos/services/file_magic_service.dart'; import 'package:photos/services/local_sync_service.dart'; import 'package:photos/services/remote_sync_service.dart'; import "package:photos/utils/dialog_util.dart"; @@ -666,6 +666,7 @@ class CollectionsService { ), ); sync().ignore(); + // not required once remote & local world are separate LocalSyncService.instance.syncAll().ignore(); } diff --git a/mobile/lib/services/file_magic_service.dart b/mobile/lib/services/file_magic_service.dart index 5d0c2721c4..b012cb49f4 100644 --- a/mobile/lib/services/file_magic_service.dart +++ b/mobile/lib/services/file_magic_service.dart @@ -12,6 +12,7 @@ import 'package:photos/events/files_updated_event.dart'; import 'package:photos/events/force_reload_home_gallery_event.dart'; import 'package:photos/events/local_photos_updated_event.dart'; import 'package:photos/extensions/list.dart'; +import "package:photos/models/api/metadata.dart"; import 'package:photos/models/file/file.dart'; import "package:photos/models/metadata/common_keys.dart"; import "package:photos/models/metadata/file_magic.dart"; @@ -195,58 +196,3 @@ class FileMagicService { } } } - -class UpdateMagicMetadataRequest { - final int id; - final MetadataRequest? magicMetadata; - - UpdateMagicMetadataRequest({required this.id, required this.magicMetadata}); - - factory UpdateMagicMetadataRequest.fromJson(dynamic json) { - return UpdateMagicMetadataRequest( - id: json['id'], - magicMetadata: json['magicMetadata'] != null - ? MetadataRequest.fromJson(json['magicMetadata']) - : null, - ); - } - - Map toJson() { - final map = {}; - map['id'] = id; - if (magicMetadata != null) { - map['magicMetadata'] = magicMetadata!.toJson(); - } - return map; - } -} - -class MetadataRequest { - int? version; - int? count; - String? data; - String? header; - - MetadataRequest({ - required this.version, - required this.count, - required this.data, - required this.header, - }); - - MetadataRequest.fromJson(dynamic json) { - version = json['version']; - count = json['count']; - data = json['data']; - header = json['header']; - } - - Map toJson() { - final map = {}; - map['version'] = version; - map['count'] = count; - map['data'] = data; - map['header'] = header; - return map; - } -} diff --git a/mobile/lib/services/hidden_service.dart b/mobile/lib/services/hidden_service.dart index 90d65559c7..b97695a62d 100644 --- a/mobile/lib/services/hidden_service.dart +++ b/mobile/lib/services/hidden_service.dart @@ -12,12 +12,12 @@ import 'package:photos/events/files_updated_event.dart'; import 'package:photos/events/local_photos_updated_event.dart'; import "package:photos/generated/l10n.dart"; import 'package:photos/models/api/collection/create_request.dart'; +import "package:photos/models/api/metadata.dart"; import 'package:photos/models/collection/collection.dart'; import 'package:photos/models/file/file.dart'; import "package:photos/models/metadata/collection_magic.dart"; import "package:photos/models/metadata/common_keys.dart"; import 'package:photos/services/collections_service.dart'; -import 'package:photos/services/file_magic_service.dart'; import 'package:photos/utils/dialog_util.dart'; extension HiddenService on CollectionsService { diff --git a/mobile/lib/utils/file_uploader.dart b/mobile/lib/utils/file_uploader.dart index 98c9d49fc2..9929628e09 100644 --- a/mobile/lib/utils/file_uploader.dart +++ b/mobile/lib/utils/file_uploader.dart @@ -24,6 +24,7 @@ import 'package:photos/events/files_updated_event.dart'; import 'package:photos/events/local_photos_updated_event.dart'; import 'package:photos/events/subscription_purchased_event.dart'; import 'package:photos/main.dart'; +import "package:photos/models/api/metadata.dart"; import "package:photos/models/backup/backup_item.dart"; import "package:photos/models/backup/backup_item_status.dart"; import 'package:photos/models/file/file.dart'; @@ -34,7 +35,6 @@ import 'package:photos/module/upload/model/upload_url.dart'; import "package:photos/module/upload/service/multipart.dart"; import "package:photos/service_locator.dart"; import 'package:photos/services/collections_service.dart'; -import "package:photos/services/file_magic_service.dart"; import 'package:photos/services/local_sync_service.dart'; import "package:photos/services/preview_video_store.dart"; import 'package:photos/services/sync_service.dart'; @@ -751,7 +751,8 @@ class FileUploader { if (SyncService.instance.shouldStopSync()) { throw SyncStopRequestedError(); } - final stillLocked = await _uploadLocks.isLocked(lockKey, _processType.toString()); + final stillLocked = + await _uploadLocks.isLocked(lockKey, _processType.toString()); if (!stillLocked) { _logger.warning('file ${file.tag} report paused is missing'); throw LockFreedError(); diff --git a/mobile/lib/utils/file_uploader_util.dart b/mobile/lib/utils/file_uploader_util.dart index 7bbdbd0bb2..07c3084b20 100644 --- a/mobile/lib/utils/file_uploader_util.dart +++ b/mobile/lib/utils/file_uploader_util.dart @@ -17,13 +17,13 @@ import 'package:photo_manager/photo_manager.dart'; import 'package:photos/core/configuration.dart'; import 'package:photos/core/constants.dart'; import 'package:photos/core/errors.dart'; +import "package:photos/models/api/metadata.dart"; import "package:photos/models/ffmpeg/ffprobe_props.dart"; import "package:photos/models/file/extensions/file_props.dart"; import 'package:photos/models/file/file.dart'; import 'package:photos/models/file/file_type.dart'; import "package:photos/models/location/location.dart"; import "package:photos/models/metadata/file_magic.dart"; -import "package:photos/services/file_magic_service.dart"; import "package:photos/utils/exif_util.dart"; import 'package:photos/utils/file_util.dart'; import "package:uuid/uuid.dart"; diff --git a/mobile/lib/utils/lat_lon_util.dart b/mobile/lib/utils/lat_lon_util.dart deleted file mode 100644 index a270e39024..0000000000 --- a/mobile/lib/utils/lat_lon_util.dart +++ /dev/null @@ -1,14 +0,0 @@ -String convertLatLng(double decimal, bool isLat) { - final degree = "${decimal.toString().split(".")[0]}°"; - final minutesBeforeConversion = - double.parse("0.${decimal.toString().split(".")[1]}"); - final minutes = "${(minutesBeforeConversion * 60).toString().split('.')[0]}'"; - final secondsBeforeConversion = double.parse( - "0.${(minutesBeforeConversion * 60).toString().split('.')[1]}", - ); - final seconds = - '${double.parse((secondsBeforeConversion * 60).toString()).toStringAsFixed(0)}" '; - final dmsOutput = - "$degree$minutes$seconds${isLat ? decimal > 0 ? 'N' : 'S' : decimal > 0 ? 'E' : 'W'}"; - return dmsOutput; -} From 8516d8364cde2ca5ceb0ac3492170aadf4afb463 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 1 Mar 2025 10:48:03 +0530 Subject: [PATCH 3/3] [mob] Remove unused file --- .../lib/ui/settings/storage_error_widget.dart | 32 ---- .../file_details/objects_item_widget.dart | 66 ------- .../viewer/people/new_person_item_widget.dart | 74 -------- mobile/lib/utils/face/face_util.dart | 175 ------------------ mobile/lib/utils/multipart_upload_util.dart | 157 ---------------- 5 files changed, 504 deletions(-) delete mode 100644 mobile/lib/ui/settings/storage_error_widget.dart delete mode 100644 mobile/lib/ui/viewer/file_details/objects_item_widget.dart delete mode 100644 mobile/lib/ui/viewer/people/new_person_item_widget.dart delete mode 100644 mobile/lib/utils/face/face_util.dart delete mode 100644 mobile/lib/utils/multipart_upload_util.dart diff --git a/mobile/lib/ui/settings/storage_error_widget.dart b/mobile/lib/ui/settings/storage_error_widget.dart deleted file mode 100644 index 8ccf6de0f5..0000000000 --- a/mobile/lib/ui/settings/storage_error_widget.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import "package:photos/generated/l10n.dart"; -import 'package:photos/theme/colors.dart'; -import 'package:photos/theme/ente_theme.dart'; - -class StorageErrorWidget extends StatelessWidget { - const StorageErrorWidget({super.key}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Icon( - Icons.error_outline_outlined, - color: strokeBaseDark, - ), - const SizedBox(height: 8), - Text( - S.of(context).yourStorageDetailsCouldNotBeFetched, - style: getEnteTextTheme(context).small.copyWith( - color: textMutedDark, - ), - ), - ], - ), - ); - } -} diff --git a/mobile/lib/ui/viewer/file_details/objects_item_widget.dart b/mobile/lib/ui/viewer/file_details/objects_item_widget.dart deleted file mode 100644 index c02576c116..0000000000 --- a/mobile/lib/ui/viewer/file_details/objects_item_widget.dart +++ /dev/null @@ -1,66 +0,0 @@ -import "package:flutter/foundation.dart"; -import "package:flutter/material.dart"; -import "package:logging/logging.dart"; -import "package:photos/generated/l10n.dart"; -import 'package:photos/models/file/file.dart'; -import "package:photos/ui/components/buttons/chip_button_widget.dart"; -import "package:photos/ui/components/info_item_widget.dart"; - -class ObjectsItemWidget extends StatelessWidget { - final EnteFile file; - const ObjectsItemWidget(this.file, {super.key}); - - @override - Widget build(BuildContext context) { - return InfoItemWidget( - key: const ValueKey("Objects"), - leadingIcon: Icons.image_search_outlined, - subtitleSection: _objectTags(context, file), - hasChipButtons: true, - ); - } - - Future> _objectTags( - BuildContext context, - EnteFile file, - ) async { - try { - final chipButtons = []; - var objectTags = {}; - - // final thumbnail = await getThumbnail(file); - // if (thumbnail != null) { - // objectTags = await ObjectDetectionService.instance.predict(thumbnail); - // } - if (objectTags.isEmpty) { - return [ - ChipButtonWidget( - S.of(context).noResults, - noChips: true, - ), - ]; - } - // sort by values - objectTags = Map.fromEntries( - objectTags.entries.toList() - ..sort((e1, e2) => e2.value.compareTo(e1.value)), - ); - - for (MapEntry entry in objectTags.entries) { - chipButtons.add( - ChipButtonWidget( - entry.key + - (kDebugMode - ? "-" + (entry.value * 100).round().toString() - : ""), - ), - ); - } - - return chipButtons; - } catch (e, s) { - Logger("ObjctsItemWidget").info(e, s); - return []; - } - } -} diff --git a/mobile/lib/ui/viewer/people/new_person_item_widget.dart b/mobile/lib/ui/viewer/people/new_person_item_widget.dart deleted file mode 100644 index 88fa0fa3de..0000000000 --- a/mobile/lib/ui/viewer/people/new_person_item_widget.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:dotted_border/dotted_border.dart'; -import 'package:flutter/material.dart'; -import "package:photos/generated/l10n.dart"; -import 'package:photos/theme/ente_theme.dart'; - -///https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design?node-id=10854%3A57947&t=H5AvR79OYDnB9ekw-4 -class NewPersonItemWidget extends StatelessWidget { - const NewPersonItemWidget({ - super.key, - }); - - @override - Widget build(BuildContext context) { - final textTheme = getEnteTextTheme(context); - final colorScheme = getEnteColorScheme(context); - const sideOfThumbnail = 60.0; - return LayoutBuilder( - builder: (context, constraints) { - return Stack( - alignment: Alignment.center, - children: [ - Row( - children: [ - ClipRRect( - borderRadius: const BorderRadius.horizontal( - left: Radius.circular(4), - ), - child: SizedBox( - height: sideOfThumbnail, - width: sideOfThumbnail, - child: Icon( - Icons.add_outlined, - color: colorScheme.strokeMuted, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 12), - child: Text( - S.of(context).addNewPerson, - style: - textTheme.body.copyWith(color: colorScheme.textMuted), - ), - ), - ], - ), - IgnorePointer( - child: DottedBorder( - dashPattern: const [4], - color: colorScheme.strokeFainter, - strokeWidth: 1, - padding: const EdgeInsets.all(0), - borderType: BorderType.RRect, - radius: const Radius.circular(4), - child: SizedBox( - //Have to decrease the height and width by 1 pt as the stroke - //dotted border gives is of strokeAlign.center, so 0.5 inside and - // outside. Here for the row, stroke should be inside so we - //decrease the size of this sizedBox by 1 (so it shrinks 0.5 from - //every side) so that the strokeAlign.center of this sizedBox - //looks like a strokeAlign.inside in the row. - height: sideOfThumbnail - 1, - //This width will work for this only if the row widget takes up the - //full size it's parent (stack). - width: constraints.maxWidth - 1, - ), - ), - ), - ], - ); - }, - ); - } -} diff --git a/mobile/lib/utils/face/face_util.dart b/mobile/lib/utils/face/face_util.dart deleted file mode 100644 index cb5b186b04..0000000000 --- a/mobile/lib/utils/face/face_util.dart +++ /dev/null @@ -1,175 +0,0 @@ -import "dart:math"; -import "dart:typed_data"; - -import "package:computer/computer.dart"; -import "package:flutter_image_compress/flutter_image_compress.dart"; -import "package:image/image.dart" as img; -import "package:logging/logging.dart"; -import "package:photos/models/ml/face/box.dart"; - -/// Bounding box of a face. -/// -/// [xMin] and [yMin] are the coordinates of the top left corner of the box, and -/// [width] and [height] are the width and height of the box. -/// -/// One unit is equal to one pixel in the original image. -class FaceBoxImage { - final int xMin; - final int yMin; - final int width; - final int height; - - FaceBoxImage({ - required this.xMin, - required this.yMin, - required this.width, - required this.height, - }); -} - -final _logger = Logger("FaceUtil"); -final _computer = Computer.shared(); -const _faceImageBufferFactor = 0.2; - -///Convert img.Image to ui.Image and use RawImage to display. -Future> generateImgFaceThumbnails( - String imagePath, - List faceBoxes, -) async { - final faceThumbnails = []; - - final image = await decodeToImgImage(imagePath); - - for (FaceBox faceBox in faceBoxes) { - final croppedImage = cropFaceBoxFromImage(image, faceBox); - faceThumbnails.add(croppedImage); - } - - return faceThumbnails; -} - -Future> generateJpgFaceThumbnails( - String imagePath, - List faceBoxes, -) async { - final image = await decodeToImgImage(imagePath); - final croppedImages = []; - for (FaceBox faceBox in faceBoxes) { - final croppedImage = cropFaceBoxFromImage(image, faceBox); - croppedImages.add(croppedImage); - } - - return await _computer - .compute(_encodeImagesToJpg, param: {"images": croppedImages}); -} - -Future decodeToImgImage(String imagePath) async { - img.Image? image = - await _computer.compute(_decodeImageFile, param: {"filePath": imagePath}); - - if (image == null) { - _logger.info( - "Failed to decode image. Compressing to jpg and decoding", - ); - final compressedJPGImage = - await FlutterImageCompress.compressWithFile(imagePath); - image = await _computer.compute( - _decodeJpg, - param: {"image": compressedJPGImage}, - ); - - if (image == null) { - throw Exception("Failed to decode image"); - } else { - return image; - } - } else { - return image; - } -} - -/// Returns an Image from 'package:image/image.dart' -img.Image cropFaceBoxFromImage(img.Image image, FaceBox faceBox) { - final squareFaceBox = _getSquareFaceBoxImage(image, faceBox); - final squareFaceBoxWithBuffer = - _addBufferAroundFaceBox(squareFaceBox, _faceImageBufferFactor); - return img.copyCrop( - image, - x: squareFaceBoxWithBuffer.xMin, - y: squareFaceBoxWithBuffer.yMin, - width: squareFaceBoxWithBuffer.width, - height: squareFaceBoxWithBuffer.height, - antialias: false, - ); -} - -/// Returns a square face box image from the original image with -/// side length equal to the maximum of the width and height of the face box in -/// the OG image. -FaceBoxImage _getSquareFaceBoxImage(img.Image image, FaceBox faceBox) { - final width = (image.width * faceBox.width).round(); - final height = (image.height * faceBox.height).round(); - final side = max(width, height); - final xImage = (image.width * faceBox.x).round(); - final yImage = (image.height * faceBox.y).round(); - - if (height >= width) { - final xImageAdj = (xImage - (height - width) / 2).round(); - return FaceBoxImage( - xMin: xImageAdj, - yMin: yImage, - width: side, - height: side, - ); - } else { - final yImageAdj = (yImage - (width - height) / 2).round(); - return FaceBoxImage( - xMin: xImage, - yMin: yImageAdj, - width: side, - height: side, - ); - } -} - -///To add some buffer around the face box so that the face isn't cropped -///too close to the face. -FaceBoxImage _addBufferAroundFaceBox( - FaceBoxImage faceBoxImage, - double bufferFactor, -) { - final heightBuffer = faceBoxImage.height * bufferFactor; - final widthBuffer = faceBoxImage.width * bufferFactor; - final xMinWithBuffer = faceBoxImage.xMin - widthBuffer; - final yMinWithBuffer = faceBoxImage.yMin - heightBuffer; - final widthWithBuffer = faceBoxImage.width + 2 * widthBuffer; - final heightWithBuffer = faceBoxImage.height + 2 * heightBuffer; - //Do not add buffer if the top left edge of the image is out of bounds - //after adding the buffer. - if (xMinWithBuffer < 0 || yMinWithBuffer < 0) { - return faceBoxImage; - } - //Another similar case that can be handled is when the bottom right edge - //of the image is out of bounds after adding the buffer. But the - //the visual difference is not as significant as when the top left edge - //is out of bounds, so we are not handling that case. - return FaceBoxImage( - xMin: xMinWithBuffer.round(), - yMin: yMinWithBuffer.round(), - width: widthWithBuffer.round(), - height: heightWithBuffer.round(), - ); -} - -List _encodeImagesToJpg(Map args) { - final images = args["images"] as List; - return images.map((img.Image image) => img.encodeJpg(image)).toList(); -} - -Future _decodeImageFile(Map args) async { - return await img.decodeImageFile(args["filePath"]); -} - -img.Image? _decodeJpg(Map args) { - return img.decodeJpg(args["image"])!; -} diff --git a/mobile/lib/utils/multipart_upload_util.dart b/mobile/lib/utils/multipart_upload_util.dart deleted file mode 100644 index 6b9ccafb97..0000000000 --- a/mobile/lib/utils/multipart_upload_util.dart +++ /dev/null @@ -1,157 +0,0 @@ -// 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; - } -}