file_magic_service.dart 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import 'dart:convert';
  2. import 'package:dio/dio.dart';
  3. import 'package:flutter_sodium/flutter_sodium.dart';
  4. import 'package:logging/logging.dart';
  5. import 'package:photos/core/event_bus.dart';
  6. import 'package:photos/core/network.dart';
  7. import 'package:photos/db/files_db.dart';
  8. import 'package:photos/events/files_updated_event.dart';
  9. import 'package:photos/models/file.dart';
  10. import 'package:photos/core/configuration.dart';
  11. import 'package:photos/models/magic_metadata.dart';
  12. import 'package:photos/services/remote_sync_service.dart';
  13. import 'package:photos/utils/crypto_util.dart';
  14. import 'package:photos/utils/file_download_util.dart';
  15. class FileMagicService {
  16. final _logger = Logger("FileMagicService");
  17. Dio _dio;
  18. FilesDB _filesDB;
  19. FileMagicService._privateConstructor() {
  20. _filesDB = FilesDB.instance;
  21. _dio = Network.instance.getDio();
  22. }
  23. static final FileMagicService instance =
  24. FileMagicService._privateConstructor();
  25. Future<void> changeVisibility(List<File> files, int visibility) async {
  26. Map<String, dynamic> update = {};
  27. update[kMagicKeyVisibility] = visibility;
  28. return _updateMagicData(files, update);
  29. }
  30. Future<void> _updateMagicData(
  31. List<File> files, Map<String, dynamic> newMetadataUpdate) async {
  32. final params = <String, dynamic>{};
  33. params['metadataList'] = [];
  34. final int ownerID = Configuration.instance.getUserID();
  35. try {
  36. for (final file in files) {
  37. if (file.uploadedFileID == null) {
  38. throw AssertionError(
  39. "operation is only supported on backed up files");
  40. } else if (file.ownerID != ownerID) {
  41. throw AssertionError("cannot modify memories not owned by you");
  42. }
  43. // read the existing magic metadata and apply new updates to existing data
  44. // current update is simple replace. This will be enhanced in the future,
  45. // as required.
  46. Map<String, dynamic> jsonToUpdate = jsonDecode(file.mMdEncodedJson);
  47. newMetadataUpdate.forEach((key, value) {
  48. jsonToUpdate[key] = value;
  49. });
  50. // update the local information so that it's reflected on UI
  51. file.mMdEncodedJson = jsonEncode(jsonToUpdate);
  52. file.magicMetadata = MagicMetadata.fromJson(jsonToUpdate);
  53. final fileKey = decryptFileKey(file);
  54. final encryptedMMd = await CryptoUtil.encryptChaCha(
  55. utf8.encode(jsonEncode(jsonToUpdate)), fileKey);
  56. params['metadataList'].add(UpdateMagicMetadataRequest(
  57. id: file.uploadedFileID,
  58. magicMetadata: MetadataRequest(
  59. version: file.mMdVersion,
  60. count: jsonToUpdate.length,
  61. data: Sodium.bin2base64(encryptedMMd.encryptedData),
  62. header: Sodium.bin2base64(encryptedMMd.header),
  63. )));
  64. }
  65. await _dio.put(
  66. Configuration.instance.getHttpEndpoint() +
  67. "/files/magic-metadata",
  68. data: params,
  69. options: Options(
  70. headers: {"X-Auth-Token": Configuration.instance.getToken()}),
  71. );
  72. // update the state of the selected file. Same file in other collection
  73. // should be eventually synced after remote sync has completed
  74. await _filesDB.insertMultiple(files);
  75. Bus.instance.fire(FilesUpdatedEvent(files));
  76. RemoteSyncService.instance.sync(silently: true);
  77. } on DioError catch (e) {
  78. if (e.response != null && e.response.statusCode == 409) {
  79. RemoteSyncService.instance.sync(silently: true);
  80. }
  81. rethrow;
  82. } catch (e, s) {
  83. _logger.severe("failed to sync magic metadata", e, s);
  84. rethrow;
  85. }
  86. }
  87. }
  88. class UpdateMagicMetadataRequest {
  89. final int id;
  90. final MetadataRequest magicMetadata;
  91. UpdateMagicMetadataRequest({this.id, this.magicMetadata});
  92. factory UpdateMagicMetadataRequest.fromJson(dynamic json) {
  93. return UpdateMagicMetadataRequest(
  94. id: json['id'],
  95. magicMetadata: json['magicMetadata'] != null
  96. ? MetadataRequest.fromJson(json['magicMetadata'])
  97. : null);
  98. }
  99. Map<String, dynamic> toJson() {
  100. final map = <String, dynamic>{};
  101. map['id'] = id;
  102. if (magicMetadata != null) {
  103. map['magicMetadata'] = magicMetadata.toJson();
  104. }
  105. return map;
  106. }
  107. }
  108. class MetadataRequest {
  109. int version;
  110. int count;
  111. String data;
  112. String header;
  113. MetadataRequest({this.version, this.count, this.data, this.header});
  114. MetadataRequest.fromJson(dynamic json) {
  115. version = json['version'];
  116. count = json['count'];
  117. data = json['data'];
  118. header = json['header'];
  119. }
  120. Map<String, dynamic> toJson() {
  121. var map = <String, dynamic>{};
  122. map['version'] = version;
  123. map['count'] = count;
  124. map['data'] = data;
  125. map['header'] = header;
  126. return map;
  127. }
  128. }