Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mobile/apps/photos/lib/core/errors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class InvalidFileError extends ArgumentError {
}
}

class SkippedQueuedFileError extends Error {}

class SubscriptionAlreadyClaimedError extends Error {}

class WiFiUnavailableError extends Error {}
Expand Down
14 changes: 14 additions & 0 deletions mobile/apps/photos/lib/db/device_files_db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,20 @@ extension DeviceFiles on FilesDB {
return result;
}

Future<Set<String>> getSelectedDevicePathIds() async {
final db = await sqliteAsyncDB;
final rows = await db.getAll(
'''
SELECT id FROM device_collections where should_backup = $_sqlBoolTrue;
''',
);
final Set<String> result = <String>{};
for (final row in rows) {
result.add(row['id'] as String);
}
return result;
}

Future<void> updateDevicePathSyncStatus(
Map<String, bool> syncStatus,
) async {
Expand Down
76 changes: 72 additions & 4 deletions mobile/apps/photos/lib/db/files_db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class FilesDB with SqlDbBase {
static const columnThumbnailDecryptionHeader = 'thumbnail_decryption_header';
static const columnMetadataDecryptionHeader = 'metadata_decryption_header';
static const columnFileSize = 'file_size';
static const columnQueueSource = 'queue_source';

// MMD -> Magic Metadata
static const columnMMdEncodedJson = 'mmd_encoded_json';
Expand Down Expand Up @@ -90,6 +91,7 @@ class FilesDB with SqlDbBase {
...updateIndexes(),
...createEntityDataTable(),
...addAddedTime(),
...addQueueSourceColumn(),
];

static const List<String> _columnNames = [
Expand All @@ -98,6 +100,7 @@ class FilesDB with SqlDbBase {
columnUploadedFileID,
columnOwnerID,
columnCollectionID,
columnQueueSource,
columnTitle,
columnDeviceFolder,
columnLatitude,
Expand Down Expand Up @@ -416,6 +419,14 @@ class FilesDB with SqlDbBase {
];
}

static List<String> addQueueSourceColumn() {
return [
'''
ALTER TABLE $filesTable ADD COLUMN $columnQueueSource TEXT;
''',
];
}

Future<void> clearTable() async {
final db = await instance.sqliteAsyncDB;
await db.execute('DELETE FROM $filesTable');
Expand Down Expand Up @@ -1073,20 +1084,75 @@ class FilesDB with SqlDbBase {
// corresponding file entries are not already mapped to some other collection
Future<void> setCollectionIDForUnMappedLocalFiles(
int collectionID,
Set<String> localIDs,
Set<String> localIDs, {
String? queueSource,
}
) async {
if (localIDs.isEmpty) {
return;
}
final db = await instance.sqliteAsyncDB;
final inParam = localIDs.map((id) => "'$id'").join(',');
final localIDList = localIDs.toList();
final placeholders = List.filled(localIDList.length, '?').join(',');
if (queueSource == null) {
await db.execute(
'''
UPDATE $filesTable
SET $columnCollectionID = $collectionID
WHERE $columnLocalID IN ($placeholders) AND ($columnCollectionID IS NULL OR
$columnCollectionID = -1);
''',
localIDList,
);
return;
}
await db.execute(
'''
UPDATE $filesTable
SET $columnCollectionID = $collectionID
WHERE $columnLocalID IN ($inParam) AND ($columnCollectionID IS NULL OR
SET $columnCollectionID = ?,
$columnQueueSource = ?
WHERE $columnLocalID IN ($placeholders) AND ($columnCollectionID IS NULL OR
$columnCollectionID = -1);
''',
[collectionID, queueSource, ...localIDList],
);
}

Future<void> cleanupQueuedEntry({
required String localID,
required int collectionID,
required String queueSource,
}) async {
final db = await instance.sqliteAsyncDB;
await db.writeTransaction((tx) async {
final hasUnmapped = await tx.getAll(
'SELECT 1 FROM $filesTable WHERE $columnLocalID = ? '
'AND ($columnCollectionID IS NULL OR $columnCollectionID = -1) '
'AND ($columnUploadedFileID IS NULL OR $columnUploadedFileID = -1) '
'LIMIT 1',
[localID],
);

if (hasUnmapped.isNotEmpty) {
await tx.execute(
'DELETE FROM $filesTable WHERE $columnLocalID = ? '
'AND $columnCollectionID = ? AND $columnQueueSource = ? '
'AND ($columnUploadedFileID IS NULL OR $columnUploadedFileID = -1)',
[localID, collectionID, queueSource],
);
} else {
await tx.execute(
'UPDATE $filesTable SET $columnCollectionID = -1, '
'$columnQueueSource = NULL '
'WHERE $columnLocalID = ? AND $columnCollectionID = ? '
'AND $columnQueueSource = ? '
'AND ($columnUploadedFileID IS NULL OR $columnUploadedFileID = -1)',
[localID, collectionID, queueSource],
);
}
});
}

Future<void> markFilesForReUpload(
int ownerID,
String localID,
Expand Down Expand Up @@ -1877,6 +1943,7 @@ class FilesDB with SqlDbBase {
file.uploadedFileID ?? -1,
file.ownerID,
file.collectionID ?? -1,
file.queueSource,
file.title,
file.deviceFolder,
latitude,
Expand Down Expand Up @@ -1960,6 +2027,7 @@ class FilesDB with SqlDbBase {
file.hash = row[columnHash];
file.metadataVersion = row[columnMetadataVersion] ?? 0;
file.fileSize = row[columnFileSize];
file.queueSource = row[columnQueueSource];

file.mMdVersion = row[columnMMdVersion] ?? 0;
file.mMdEncodedJson = row[columnMMdEncodedJson] ?? '{}';
Expand Down
3 changes: 3 additions & 0 deletions mobile/apps/photos/lib/models/file/file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class EnteFile {
String? thumbnailDecryptionHeader;
String? metadataDecryptionHeader;
int? fileSize;
String? queueSource;

String? mMdEncodedJson;
int mMdVersion = 0;
Expand Down Expand Up @@ -387,6 +388,7 @@ class EnteFile {
String? thumbnailDecryptionHeader,
String? metadataDecryptionHeader,
int? fileSize,
String? queueSource,
String? mMdEncodedJson,
int? mMdVersion,
MagicMetadata? magicMetadata,
Expand Down Expand Up @@ -421,6 +423,7 @@ class EnteFile {
..metadataDecryptionHeader =
metadataDecryptionHeader ?? this.metadataDecryptionHeader
..fileSize = fileSize ?? this.fileSize
..queueSource = queueSource ?? this.queueSource
..mMdEncodedJson = mMdEncodedJson ?? this.mMdEncodedJson
..mMdVersion = mMdVersion ?? this.mMdVersion
..magicMetadata = magicMetadata ?? this.magicMetadata
Expand Down
67 changes: 67 additions & 0 deletions mobile/apps/photos/lib/services/sync/remote_sync_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import 'package:photos/events/files_updated_event.dart';
import 'package:photos/events/force_reload_home_gallery_event.dart';
import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/events/sync_status_update_event.dart';
import 'package:photos/events/account_configured_event.dart';
import 'package:photos/events/user_logged_out_event.dart';
import "package:photos/main.dart" show isProcessBg;
import 'package:photos/models/device_collection.dart';
import "package:photos/models/file/extensions/file_props.dart";
Expand Down Expand Up @@ -48,6 +50,7 @@ class RemoteSyncService {
LocalFileUpdateService.instance;
int _completedUploads = 0;
int _ignoredUploads = 0;
Set<String>? _selectedDevicePathIdsCache;
late SharedPreferences _prefs;
Completer<void>? _existingSync;
bool _isExistingSyncSilent = false;
Expand Down Expand Up @@ -93,6 +96,16 @@ class RemoteSyncService {
}
}
});

if (flagService.queueSourceEnabled) {
Bus.instance.on<UserLoggedOutEvent>().listen((event) {
_clearSelectedPathIdsCache();
});

Bus.instance.on<AccountConfiguredEvent>().listen((event) {
_clearSelectedPathIdsCache();
});
}
}

Future<void> sync({bool silently = false}) async {
Expand Down Expand Up @@ -359,6 +372,10 @@ class RemoteSyncService {
_logger.info("Syncing device collections to be uploaded");
final int ownerID = _config.getUserID()!;

if (flagService.queueSourceEnabled) {
await _ensureSelectedPathIdsCache();
}

final deviceCollections = await _db.getDeviceCollections();
deviceCollections.removeWhere((element) => !element.shouldBackup);
// Sort by count to ensure that photos in iOS are first inserted in
Expand Down Expand Up @@ -415,6 +432,8 @@ class RemoteSyncService {
await _db.setCollectionIDForUnMappedLocalFiles(
collectionID,
localIDsToSync,
queueSource:
flagService.queueSourceEnabled ? deviceCollection.id : null,
);

// mark IDs as already synced if corresponding entry is present in
Expand Down Expand Up @@ -451,6 +470,9 @@ class RemoteSyncService {
existingFile.collectionID = collectionID;
existingFile.uploadedFileID = null;
existingFile.ownerID = null;
if (flagService.queueSourceEnabled) {
existingFile.queueSource = deviceCollection.id;
}
newFilesToInsert.add(existingFile);
fileFoundForLocalIDs.add(localID);
}
Expand Down Expand Up @@ -478,6 +500,9 @@ class RemoteSyncService {
final Set<int> oldCollectionIDsForAutoSync =
await _db.getDeviceSyncCollectionIDs();
await _db.updateDevicePathSyncStatus(syncStatusUpdate);
if (flagService.queueSourceEnabled) {
await _refreshSelectedPathIdsCacheIfChanged(syncStatusUpdate);
}
final Set<int> newCollectionIDsForAutoSync =
await _db.getDeviceSyncCollectionIDs();
SyncService.instance.onDeviceCollectionSet(newCollectionIDsForAutoSync);
Expand All @@ -493,6 +518,45 @@ class RemoteSyncService {
Bus.instance.fire(BackupFoldersUpdatedEvent());
}

Future<void> ensureSelectedPathIdsCache() async {
await _ensureSelectedPathIdsCache();
}

Future<void> _ensureSelectedPathIdsCache() async {
if (_selectedDevicePathIdsCache != null) {
return;
}
_selectedDevicePathIdsCache = await _db.getSelectedDevicePathIds();
}

void _clearSelectedPathIdsCache() {
_selectedDevicePathIdsCache = null;
}

Future<void> _refreshSelectedPathIdsCacheIfChanged(
Map<String, bool> syncStatusUpdate,
) async {
if (syncStatusUpdate.isEmpty) {
return;
}
await _ensureSelectedPathIdsCache();
final cache = _selectedDevicePathIdsCache!;
final hasChange = syncStatusUpdate.entries.any(
(entry) => cache.contains(entry.key) != entry.value,
);
if (!hasChange) {
return;
}
_selectedDevicePathIdsCache = await _db.getSelectedDevicePathIds();
}

bool isDevicePathSelected(String pathId) {
if (!flagService.queueSourceEnabled) {
return true;
}
return _selectedDevicePathIdsCache?.contains(pathId) ?? true;
}

Future<void> removeFilesQueuedForUpload(List<int> collectionIDs) async {
/*
For each collection, perform following action
Expand Down Expand Up @@ -757,6 +821,9 @@ class RemoteSyncService {
if (error is InvalidFileError) {
_ignoredUploads++;
_logger.warning("Invalid file error", error);
} else if (error is SkippedQueuedFileError) {
_ignoredUploads++;
_logger.info("Skipped queued file due to queue source");
} else {
throw error;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "package:photos/generated/l10n.dart";
import 'package:photos/models/collection/collection.dart';
import 'package:photos/models/file/file.dart';
import 'package:photos/models/selected_files.dart';
import "package:photos/service_locator.dart";
import "package:photos/services/collections_service.dart";
import 'package:photos/services/favorites_service.dart';
import "package:photos/services/hidden_service.dart";
Expand Down Expand Up @@ -144,8 +145,12 @@ extension CollectionFileActions on CollectionActions {
files.add(uploadedFile);
}
} else {
final bool queueSourceEnabled = flagService.queueSourceEnabled;
for (final file in filesPendingUpload) {
file.collectionID = collection.id;
if (queueSourceEnabled) {
file.queueSource = 'manual';
}
}
// filesPendingUpload might be getting ignored during auto-upload
// because the user deleted these files from ente in the past.
Expand Down Expand Up @@ -266,8 +271,12 @@ extension CollectionFileActions on CollectionActions {
files.add(uploadedFile);
}
} else {
final bool queueSourceEnabled = flagService.queueSourceEnabled;
for (final file in filesPendingUpload) {
file.collectionID = collectionID;
if (queueSourceEnabled) {
file.queueSource = 'manual';
}
}
// filesPendingUpload might be getting ignored during auto-upload
// because the user deleted these files from ente in the past.
Expand Down
Loading
Loading