2022-09-07 08:30:09 +00:00
|
|
|
// @dart=2.9
|
|
|
|
|
2021-09-16 17:21:27 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
import 'package:dio/dio.dart';
|
|
|
|
import 'package:flutter_sodium/flutter_sodium.dart';
|
2021-09-18 04:53:27 +00:00
|
|
|
import 'package:logging/logging.dart';
|
2021-10-26 14:46:58 +00:00
|
|
|
import 'package:photos/core/configuration.dart';
|
2021-09-16 17:21:27 +00:00
|
|
|
import 'package:photos/core/event_bus.dart';
|
|
|
|
import 'package:photos/core/network.dart';
|
|
|
|
import 'package:photos/db/files_db.dart';
|
|
|
|
import 'package:photos/events/files_updated_event.dart';
|
2021-10-26 14:46:58 +00:00
|
|
|
import 'package:photos/events/force_reload_home_gallery_event.dart';
|
2021-09-23 11:14:15 +00:00
|
|
|
import 'package:photos/events/local_photos_updated_event.dart';
|
2021-09-16 17:21:27 +00:00
|
|
|
import 'package:photos/models/file.dart';
|
2021-09-20 06:41:38 +00:00
|
|
|
import 'package:photos/models/magic_metadata.dart';
|
2021-09-16 17:21:27 +00:00
|
|
|
import 'package:photos/services/remote_sync_service.dart';
|
2021-09-20 06:54:11 +00:00
|
|
|
import 'package:photos/utils/crypto_util.dart';
|
|
|
|
import 'package:photos/utils/file_download_util.dart';
|
2021-09-16 17:21:27 +00:00
|
|
|
|
2021-09-18 03:43:13 +00:00
|
|
|
class FileMagicService {
|
2021-09-18 04:53:27 +00:00
|
|
|
final _logger = Logger("FileMagicService");
|
2022-10-14 15:03:55 +00:00
|
|
|
Dio _enteDio;
|
2021-09-18 03:43:13 +00:00
|
|
|
FilesDB _filesDB;
|
|
|
|
|
|
|
|
FileMagicService._privateConstructor() {
|
|
|
|
_filesDB = FilesDB.instance;
|
2022-10-14 15:03:55 +00:00
|
|
|
_enteDio = Network.instance.enteDio;
|
2021-09-18 03:43:13 +00:00
|
|
|
}
|
2021-09-16 17:21:27 +00:00
|
|
|
|
2021-09-18 03:43:13 +00:00
|
|
|
static final FileMagicService instance =
|
|
|
|
FileMagicService._privateConstructor();
|
|
|
|
|
|
|
|
Future<void> changeVisibility(List<File> files, int visibility) async {
|
2022-09-19 11:52:20 +00:00
|
|
|
final Map<String, dynamic> update = {magicKeyVisibility: visibility};
|
2021-09-23 11:14:15 +00:00
|
|
|
await _updateMagicData(files, update);
|
2022-09-19 11:52:20 +00:00
|
|
|
if (visibility == visibilityVisible) {
|
2021-09-23 11:14:15 +00:00
|
|
|
// Force reload home gallery to pull in the now unarchived files
|
2022-11-11 10:23:35 +00:00
|
|
|
Bus.instance.fire(ForceReloadHomeGalleryEvent("unarchivedFiles"));
|
2022-11-11 13:09:22 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
LocalPhotosUpdatedEvent(
|
|
|
|
files,
|
|
|
|
type: EventType.unarchived,
|
|
|
|
source: "vizChange",
|
|
|
|
),
|
|
|
|
);
|
2021-10-29 23:55:29 +00:00
|
|
|
} else {
|
2022-11-11 13:09:22 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
LocalPhotosUpdatedEvent(
|
|
|
|
files,
|
|
|
|
type: EventType.archived,
|
|
|
|
source: "vizChange",
|
|
|
|
),
|
|
|
|
);
|
2021-09-23 11:14:15 +00:00
|
|
|
}
|
2021-09-18 03:54:32 +00:00
|
|
|
}
|
|
|
|
|
2021-10-26 14:46:58 +00:00
|
|
|
Future<void> updatePublicMagicMetadata(
|
2022-06-11 08:23:52 +00:00
|
|
|
List<File> files,
|
|
|
|
Map<String, dynamic> newMetadataUpdate,
|
|
|
|
) async {
|
2021-10-26 10:37:14 +00:00
|
|
|
final params = <String, dynamic>{};
|
|
|
|
params['metadataList'] = [];
|
|
|
|
final int ownerID = Configuration.instance.getUserID();
|
|
|
|
try {
|
|
|
|
for (final file in files) {
|
|
|
|
if (file.uploadedFileID == null) {
|
|
|
|
throw AssertionError(
|
2022-06-11 08:23:52 +00:00
|
|
|
"operation is only supported on backed up files",
|
|
|
|
);
|
2021-10-26 10:37:14 +00:00
|
|
|
} else if (file.ownerID != ownerID) {
|
|
|
|
throw AssertionError("cannot modify memories not owned by you");
|
|
|
|
}
|
|
|
|
// read the existing magic metadata and apply new updates to existing data
|
|
|
|
// current update is simple replace. This will be enhanced in the future,
|
|
|
|
// as required.
|
2022-09-19 11:52:20 +00:00
|
|
|
final Map<String, dynamic> jsonToUpdate =
|
|
|
|
jsonDecode(file.pubMmdEncodedJson);
|
2021-10-26 10:37:14 +00:00
|
|
|
newMetadataUpdate.forEach((key, value) {
|
|
|
|
jsonToUpdate[key] = value;
|
|
|
|
});
|
|
|
|
|
|
|
|
// update the local information so that it's reflected on UI
|
|
|
|
file.pubMmdEncodedJson = jsonEncode(jsonToUpdate);
|
|
|
|
file.pubMagicMetadata = PubMagicMetadata.fromJson(jsonToUpdate);
|
|
|
|
|
|
|
|
final fileKey = decryptFileKey(file);
|
|
|
|
final encryptedMMd = await CryptoUtil.encryptChaCha(
|
2022-06-11 08:23:52 +00:00
|
|
|
utf8.encode(jsonEncode(jsonToUpdate)),
|
|
|
|
fileKey,
|
|
|
|
);
|
|
|
|
params['metadataList'].add(
|
|
|
|
UpdateMagicMetadataRequest(
|
2021-10-26 10:37:14 +00:00
|
|
|
id: file.uploadedFileID,
|
|
|
|
magicMetadata: MetadataRequest(
|
|
|
|
version: file.pubMmdVersion,
|
|
|
|
count: jsonToUpdate.length,
|
|
|
|
data: Sodium.bin2base64(encryptedMMd.encryptedData),
|
|
|
|
header: Sodium.bin2base64(encryptedMMd.header),
|
2022-06-11 08:23:52 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
2021-10-26 10:37:14 +00:00
|
|
|
file.pubMmdVersion = file.pubMmdVersion + 1;
|
|
|
|
}
|
|
|
|
|
2022-10-14 15:03:55 +00:00
|
|
|
await _enteDio.put("/files/public-magic-metadata", data: params);
|
2021-10-26 10:37:14 +00:00
|
|
|
// update the state of the selected file. Same file in other collection
|
|
|
|
// should be eventually synced after remote sync has completed
|
|
|
|
await _filesDB.insertMultiple(files);
|
|
|
|
RemoteSyncService.instance.sync(silently: true);
|
|
|
|
} on DioError catch (e) {
|
|
|
|
if (e.response != null && e.response.statusCode == 409) {
|
|
|
|
RemoteSyncService.instance.sync(silently: true);
|
|
|
|
}
|
|
|
|
rethrow;
|
|
|
|
} catch (e, s) {
|
|
|
|
_logger.severe("failed to sync magic metadata", e, s);
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
}
|
2021-10-26 14:46:58 +00:00
|
|
|
|
2021-09-18 03:54:32 +00:00
|
|
|
Future<void> _updateMagicData(
|
2022-06-11 08:23:52 +00:00
|
|
|
List<File> files,
|
|
|
|
Map<String, dynamic> newMetadataUpdate,
|
|
|
|
) async {
|
2021-09-18 03:43:13 +00:00
|
|
|
final params = <String, dynamic>{};
|
|
|
|
params['metadataList'] = [];
|
2021-09-20 06:41:38 +00:00
|
|
|
final int ownerID = Configuration.instance.getUserID();
|
2021-09-18 04:53:27 +00:00
|
|
|
try {
|
|
|
|
for (final file in files) {
|
|
|
|
if (file.uploadedFileID == null) {
|
|
|
|
throw AssertionError(
|
2022-06-11 08:23:52 +00:00
|
|
|
"operation is only supported on backed up files",
|
|
|
|
);
|
2021-09-18 04:53:27 +00:00
|
|
|
} else if (file.ownerID != ownerID) {
|
2021-09-20 06:41:38 +00:00
|
|
|
throw AssertionError("cannot modify memories not owned by you");
|
2021-09-18 04:53:27 +00:00
|
|
|
}
|
|
|
|
// read the existing magic metadata and apply new updates to existing data
|
|
|
|
// current update is simple replace. This will be enhanced in the future,
|
|
|
|
// as required.
|
2022-09-19 11:52:20 +00:00
|
|
|
final Map<String, dynamic> jsonToUpdate =
|
|
|
|
jsonDecode(file.mMdEncodedJson);
|
2021-09-18 04:53:27 +00:00
|
|
|
newMetadataUpdate.forEach((key, value) {
|
|
|
|
jsonToUpdate[key] = value;
|
|
|
|
});
|
2021-09-18 03:43:13 +00:00
|
|
|
|
2021-09-18 04:53:27 +00:00
|
|
|
// update the local information so that it's reflected on UI
|
|
|
|
file.mMdEncodedJson = jsonEncode(jsonToUpdate);
|
2021-09-20 06:41:38 +00:00
|
|
|
file.magicMetadata = MagicMetadata.fromJson(jsonToUpdate);
|
2021-09-18 03:43:13 +00:00
|
|
|
|
2021-09-18 04:53:27 +00:00
|
|
|
final fileKey = decryptFileKey(file);
|
|
|
|
final encryptedMMd = await CryptoUtil.encryptChaCha(
|
2022-06-11 08:23:52 +00:00
|
|
|
utf8.encode(jsonEncode(jsonToUpdate)),
|
|
|
|
fileKey,
|
|
|
|
);
|
|
|
|
params['metadataList'].add(
|
|
|
|
UpdateMagicMetadataRequest(
|
2021-09-18 04:53:27 +00:00
|
|
|
id: file.uploadedFileID,
|
2021-09-20 07:26:25 +00:00
|
|
|
magicMetadata: MetadataRequest(
|
2021-09-18 04:53:27 +00:00
|
|
|
version: file.mMdVersion,
|
|
|
|
count: jsonToUpdate.length,
|
|
|
|
data: Sodium.bin2base64(encryptedMMd.encryptedData),
|
|
|
|
header: Sodium.bin2base64(encryptedMMd.header),
|
2022-06-11 08:23:52 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
2021-09-23 12:00:55 +00:00
|
|
|
file.mMdVersion = file.mMdVersion + 1;
|
2021-09-18 04:53:27 +00:00
|
|
|
}
|
2021-09-18 03:54:32 +00:00
|
|
|
|
2022-10-14 15:03:55 +00:00
|
|
|
await _enteDio.put("/files/magic-metadata", data: params);
|
2021-09-18 03:43:13 +00:00
|
|
|
// update the state of the selected file. Same file in other collection
|
|
|
|
// should be eventually synced after remote sync has completed
|
|
|
|
await _filesDB.insertMultiple(files);
|
|
|
|
RemoteSyncService.instance.sync(silently: true);
|
2021-09-18 04:53:27 +00:00
|
|
|
} on DioError catch (e) {
|
|
|
|
if (e.response != null && e.response.statusCode == 409) {
|
|
|
|
RemoteSyncService.instance.sync(silently: true);
|
|
|
|
}
|
|
|
|
rethrow;
|
|
|
|
} catch (e, s) {
|
|
|
|
_logger.severe("failed to sync magic metadata", e, s);
|
|
|
|
rethrow;
|
|
|
|
}
|
2021-09-16 17:21:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-20 06:54:11 +00:00
|
|
|
class UpdateMagicMetadataRequest {
|
|
|
|
final int id;
|
2021-09-20 07:26:25 +00:00
|
|
|
final MetadataRequest magicMetadata;
|
2021-09-16 17:21:27 +00:00
|
|
|
|
2021-09-20 06:54:11 +00:00
|
|
|
UpdateMagicMetadataRequest({this.id, this.magicMetadata});
|
2021-09-16 17:21:27 +00:00
|
|
|
|
2021-09-20 06:54:11 +00:00
|
|
|
factory UpdateMagicMetadataRequest.fromJson(dynamic json) {
|
|
|
|
return UpdateMagicMetadataRequest(
|
2022-06-11 08:23:52 +00:00
|
|
|
id: json['id'],
|
|
|
|
magicMetadata: json['magicMetadata'] != null
|
|
|
|
? MetadataRequest.fromJson(json['magicMetadata'])
|
|
|
|
: null,
|
|
|
|
);
|
2021-09-16 17:21:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
2021-09-20 06:41:38 +00:00
|
|
|
final map = <String, dynamic>{};
|
2021-09-16 17:21:27 +00:00
|
|
|
map['id'] = id;
|
2021-09-18 03:43:13 +00:00
|
|
|
if (magicMetadata != null) {
|
|
|
|
map['magicMetadata'] = magicMetadata.toJson();
|
2021-09-16 17:21:27 +00:00
|
|
|
}
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-20 07:26:25 +00:00
|
|
|
class MetadataRequest {
|
2021-09-16 17:21:27 +00:00
|
|
|
int version;
|
|
|
|
int count;
|
|
|
|
String data;
|
|
|
|
String header;
|
|
|
|
|
2021-09-20 07:26:25 +00:00
|
|
|
MetadataRequest({this.version, this.count, this.data, this.header});
|
2021-09-16 17:21:27 +00:00
|
|
|
|
2021-09-20 07:26:25 +00:00
|
|
|
MetadataRequest.fromJson(dynamic json) {
|
2021-09-16 17:21:27 +00:00
|
|
|
version = json['version'];
|
|
|
|
count = json['count'];
|
|
|
|
data = json['data'];
|
|
|
|
header = json['header'];
|
|
|
|
}
|
|
|
|
|
|
|
|
Map<String, dynamic> toJson() {
|
2022-08-29 14:43:31 +00:00
|
|
|
final map = <String, dynamic>{};
|
2021-09-16 17:21:27 +00:00
|
|
|
map['version'] = version;
|
|
|
|
map['count'] = count;
|
|
|
|
map['data'] = data;
|
|
|
|
map['header'] = header;
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
}
|