backup.service.dart 6.6 KB

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