123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- import 'dart:async';
- import 'dart:core';
- import 'dart:io';
- import 'package:logging/logging.dart';
- import "package:photos/core/configuration.dart";
- import 'package:photos/core/errors.dart';
- import 'package:photos/db/file_updation_db.dart';
- import 'package:photos/db/files_db.dart';
- import 'package:photos/models/file.dart';
- import "package:photos/models/file_type.dart";
- import 'package:photos/utils/file_uploader_util.dart';
- import 'package:photos/utils/file_util.dart';
- import 'package:shared_preferences/shared_preferences.dart';
- // LocalFileUpdateService tracks all the potential local file IDs which have
- // changed/modified on the device and needed to be uploaded again.
- class LocalFileUpdateService {
- late FileUpdationDB _fileUpdationDB;
- late SharedPreferences _prefs;
- late Logger _logger;
- final List<String> _oldMigrationKeys = [
- 'fm_badCreationTime',
- 'fm_badCreationTimeCompleted',
- 'fm_missingLocationV2ImportDone',
- 'fm_missingLocationV2MigrationDone',
- 'fm_badLocationImportDone',
- 'fm_badLocationMigrationDone',
- ];
- Completer<void>? _existingMigration;
- LocalFileUpdateService._privateConstructor() {
- _logger = Logger((LocalFileUpdateService).toString());
- _fileUpdationDB = FileUpdationDB.instance;
- }
- void init(SharedPreferences preferences) {
- _prefs = preferences;
- }
- static LocalFileUpdateService instance =
- LocalFileUpdateService._privateConstructor();
- Future<void> markUpdatedFilesForReUpload() async {
- if (_existingMigration != null) {
- _logger.info("migration is already in progress, skipping");
- return _existingMigration!.future;
- }
- _existingMigration = Completer<void>();
- try {
- await _markFilesWhichAreActuallyUpdated();
- if (Platform.isAndroid) {
- _cleanUpOlderMigration().ignore();
- }
- } catch (e, s) {
- _logger.severe('failed to perform migration', e, s);
- } finally {
- _existingMigration?.complete();
- _existingMigration = null;
- }
- }
- Future<void> _cleanUpOlderMigration() async {
- // check if any old_migration_keys are present in shared preferences
- bool hasOldMigrationKey = false;
- for (String key in _oldMigrationKeys) {
- if (_prefs.containsKey(key)) {
- hasOldMigrationKey = true;
- break;
- }
- }
- if (hasOldMigrationKey) {
- for (var element in _oldMigrationKeys) {
- _prefs.remove(element);
- }
- await _fileUpdationDB.deleteByReasons([
- 'missing_location',
- 'badCreationTime',
- 'missingLocationV2',
- 'badLocationCord',
- ]);
- }
- }
- // This method analyses all of local files for which the file
- // modification/update time was changed. It checks if the existing fileHash
- // is different from the hash of uploaded file. If fileHash are different,
- // then it marks the file for file update.
- Future<void> _markFilesWhichAreActuallyUpdated() async {
- final sTime = DateTime.now().microsecondsSinceEpoch;
- // singleRunLimit indicates number of files to check during single
- // invocation of this method. The limit act as a crude way to limit the
- // resource consumed by the method
- const int singleRunLimit = 10;
- final localIDsToProcess =
- await _fileUpdationDB.getLocalIDsForPotentialReUpload(
- singleRunLimit,
- FileUpdationDB.modificationTimeUpdated,
- );
- if (localIDsToProcess.isNotEmpty) {
- await _checkAndMarkFilesWithDifferentHashForFileUpdate(
- localIDsToProcess,
- );
- final eTime = DateTime.now().microsecondsSinceEpoch;
- final d = Duration(microseconds: eTime - sTime);
- _logger.info(
- 'Performed hashCheck for ${localIDsToProcess.length} updated files '
- 'completed in ${d.inSeconds.toString()} secs',
- );
- }
- }
- Future<void> _checkAndMarkFilesWithDifferentHashForFileUpdate(
- List<String> localIDsToProcess,
- ) async {
- _logger.info("files to process ${localIDsToProcess.length} for reupload");
- final int userID = Configuration.instance.getUserID()!;
- final List<EnteFile> result =
- await FilesDB.instance.getLocalFiles(localIDsToProcess);
- final List<EnteFile> localFilesForUser = [];
- for (EnteFile file in result) {
- if (file.ownerID == null || file.ownerID == userID) {
- localFilesForUser.add(file);
- }
- }
- final Set<String> processedIDs = {};
- for (EnteFile file in localFilesForUser) {
- if (processedIDs.contains(file.localID)) {
- continue;
- }
- MediaUploadData uploadData;
- try {
- uploadData = await getUploadData(file);
- if (uploadData.hashData != null &&
- file.hash != null &&
- (file.hash == uploadData.hashData!.fileHash ||
- file.hash == uploadData.hashData!.zipHash)) {
- _logger.info("Skip file update as hash matched ${file.tag}");
- } else {
- _logger.info(
- "Marking for file update as hash did not match ${file.tag}",
- );
- await clearCache(file);
- await FilesDB.instance.markFilesForReUpload(
- userID,
- file.localID!,
- file.title,
- file.location,
- file.creationTime!,
- file.modificationTime!,
- file.fileType,
- );
- }
- processedIDs.add(file.localID!);
- } on InvalidFileError catch (e) {
- if (e.reason == InvalidReason.livePhotoToImageTypeChanged ||
- e.reason == InvalidReason.imageToLivePhotoTypeChanged) {
- late FileType fileType;
- if (e.reason == InvalidReason.livePhotoToImageTypeChanged) {
- fileType = FileType.image;
- } else if (e.reason == InvalidReason.imageToLivePhotoTypeChanged) {
- fileType = FileType.livePhoto;
- }
- final int count = await FilesDB.instance.markFilesForReUpload(
- userID,
- file.localID!,
- file.title,
- file.location,
- file.creationTime!,
- file.modificationTime!,
- fileType,
- );
- _logger.fine('fileType changed for ${file.tag} to ${e.reason} for '
- '$count files');
- } else {
- _logger.severe("failed to check hash: invalid file ${file.tag}", e);
- }
- processedIDs.add(file.localID!);
- } catch (e) {
- _logger.severe("Failed to check hash", e);
- } finally {}
- }
- await _fileUpdationDB.deleteByLocalIDs(
- processedIDs.toList(),
- FileUpdationDB.modificationTimeUpdated,
- );
- }
- Future<MediaUploadData> getUploadData(EnteFile file) async {
- final mediaUploadData = await getUploadDataFromEnteFile(file);
- // delete the file from app's internal cache if it was copied to app
- // for upload. Shared Media should only be cleared when the upload
- // succeeds.
- if (Platform.isIOS && mediaUploadData.sourceFile != null) {
- await mediaUploadData.sourceFile?.delete();
- }
- return mediaUploadData;
- }
- }
|