backup.service.dart 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'package:dio/dio.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:hive/hive.dart';
  7. import 'package:hooks_riverpod/hooks_riverpod.dart';
  8. import 'package:immich_mobile/constants/hive_box.dart';
  9. import 'package:immich_mobile/modules/backup/models/check_duplicate_asset_response.model.dart';
  10. import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
  11. import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
  12. import 'package:immich_mobile/shared/services/network.service.dart';
  13. import 'package:immich_mobile/shared/models/device_info.model.dart';
  14. import 'package:immich_mobile/utils/files_helper.dart';
  15. import 'package:photo_manager/photo_manager.dart';
  16. import 'package:http_parser/http_parser.dart';
  17. import 'package:path/path.dart' as p;
  18. import 'package:cancellation_token_http/http.dart' as http;
  19. final backupServiceProvider =
  20. Provider((ref) => BackupService(ref.watch(networkServiceProvider)));
  21. class BackupService {
  22. final NetworkService _networkService;
  23. BackupService(this._networkService);
  24. Future<List<String>> getDeviceBackupAsset() async {
  25. String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
  26. Response response =
  27. await _networkService.getRequest(url: "asset/$deviceId");
  28. List<dynamic> result = jsonDecode(response.toString());
  29. return result.cast<String>();
  30. }
  31. Future<bool> checkDuplicateAsset(String deviceAssetId) async {
  32. String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
  33. try {
  34. Response response =
  35. await _networkService.postRequest(url: "asset/check", data: {
  36. "deviceId": deviceId,
  37. "deviceAssetId": deviceAssetId,
  38. });
  39. if (response.statusCode == 200) {
  40. var result = CheckDuplicateAssetResponse.fromJson(response.toString());
  41. return result.isExist;
  42. } else {
  43. return false;
  44. }
  45. } catch (e) {
  46. return false;
  47. }
  48. }
  49. backupAsset(
  50. Set<AssetEntity> assetList,
  51. http.CancellationToken cancelToken,
  52. Function(String, String) singleAssetDoneCb,
  53. Function(int, int) uploadProgressCb,
  54. Function(CurrentUploadAsset) setCurrentUploadAssetCb,
  55. Function(ErrorUploadAsset) errorCb,
  56. ) async {
  57. String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
  58. String savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
  59. File? file;
  60. for (var entity in assetList) {
  61. try {
  62. if (entity.type == AssetType.video) {
  63. file = await entity.originFile;
  64. } else {
  65. file = await entity.originFile.timeout(const Duration(seconds: 5));
  66. }
  67. if (file != null) {
  68. String originalFileName = await entity.titleAsync;
  69. String fileNameWithoutPath =
  70. originalFileName.toString().split(".")[0];
  71. var fileExtension = p.extension(file.path);
  72. var mimeType = FileHelper.getMimeType(file.path);
  73. var fileStream = file.openRead();
  74. var assetRawUploadData = http.MultipartFile(
  75. "assetData",
  76. fileStream,
  77. file.lengthSync(),
  78. filename: fileNameWithoutPath,
  79. contentType: MediaType(
  80. mimeType["type"],
  81. mimeType["subType"],
  82. ),
  83. );
  84. var box = Hive.box(userInfoBox);
  85. var req = MultipartRequest(
  86. 'POST', Uri.parse('$savedEndpoint/asset/upload'),
  87. onProgress: ((bytes, totalBytes) =>
  88. uploadProgressCb(bytes, totalBytes)));
  89. req.headers["Authorization"] = "Bearer ${box.get(accessTokenKey)}";
  90. req.fields['deviceAssetId'] = entity.id;
  91. req.fields['deviceId'] = deviceId;
  92. req.fields['assetType'] = _getAssetType(entity.type);
  93. req.fields['createdAt'] = entity.createDateTime.toIso8601String();
  94. req.fields['modifiedAt'] = entity.modifiedDateTime.toIso8601String();
  95. req.fields['isFavorite'] = entity.isFavorite.toString();
  96. req.fields['fileExtension'] = fileExtension;
  97. req.fields['duration'] = entity.videoDuration.toString();
  98. req.files.add(assetRawUploadData);
  99. setCurrentUploadAssetCb(
  100. CurrentUploadAsset(
  101. id: entity.id,
  102. createdAt: entity.createDateTime,
  103. fileName: originalFileName,
  104. fileType: _getAssetType(entity.type),
  105. ),
  106. );
  107. var response = await req.send(cancellationToken: cancelToken);
  108. if (response.statusCode == 201) {
  109. singleAssetDoneCb(entity.id, deviceId);
  110. } else {
  111. var data = await response.stream.bytesToString();
  112. var error = jsonDecode(data);
  113. debugPrint(
  114. "Error(${error['statusCode']}) uploading ${entity.id} | $originalFileName | Created on ${entity.createDateTime} | ${error['error']}");
  115. errorCb(ErrorUploadAsset(
  116. asset: entity,
  117. id: entity.id,
  118. createdAt: entity.createDateTime,
  119. fileName: originalFileName,
  120. fileType: _getAssetType(entity.type),
  121. errorMessage: error['error'],
  122. ));
  123. continue;
  124. }
  125. }
  126. } on http.CancelledException {
  127. debugPrint("Backup was cancelled by the user");
  128. return;
  129. } catch (e) {
  130. debugPrint("ERROR backupAsset: ${e.toString()}");
  131. continue;
  132. } finally {
  133. if (Platform.isIOS) {
  134. file?.deleteSync();
  135. }
  136. }
  137. }
  138. }
  139. void sendBackupRequest(AssetEntity entity) {}
  140. String _getAssetType(AssetType assetType) {
  141. switch (assetType) {
  142. case AssetType.audio:
  143. return "AUDIO";
  144. case AssetType.image:
  145. return "IMAGE";
  146. case AssetType.video:
  147. return "VIDEO";
  148. case AssetType.other:
  149. return "OTHER";
  150. }
  151. }
  152. Future<DeviceInfoRemote> setAutoBackup(
  153. bool status, String deviceId, String deviceType) async {
  154. var res = await _networkService.patchRequest(url: 'device-info', data: {
  155. "isAutoBackup": status,
  156. "deviceId": deviceId,
  157. "deviceType": deviceType,
  158. });
  159. return DeviceInfoRemote.fromJson(res.toString());
  160. }
  161. }
  162. class MultipartRequest extends http.MultipartRequest {
  163. /// Creates a new [MultipartRequest].
  164. MultipartRequest(
  165. String method,
  166. Uri url, {
  167. required this.onProgress,
  168. }) : super(method, url);
  169. final void Function(int bytes, int totalBytes) onProgress;
  170. /// Freezes all mutable fields and returns a
  171. /// single-subscription [http.ByteStream]
  172. /// that will emit the request body.
  173. @override
  174. http.ByteStream finalize() {
  175. final byteStream = super.finalize();
  176. final total = contentLength;
  177. var bytes = 0;
  178. final t = StreamTransformer.fromHandlers(
  179. handleData: (List<int> data, EventSink<List<int>> sink) {
  180. bytes += data.length;
  181. onProgress.call(bytes, total);
  182. sink.add(data);
  183. },
  184. );
  185. final stream = byteStream.transform(t);
  186. return http.ByteStream(stream);
  187. }
  188. }