feat(mobile): new upload (#18726)

This commit is contained in:
Alex
2025-07-18 23:58:53 -05:00
committed by GitHub
parent f929dc0816
commit fafb88d31c
27 changed files with 1733 additions and 102 deletions
@@ -0,0 +1,154 @@
import 'package:drift/drift.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import "package:immich_mobile/utils/database.utils.dart";
final backupRepositoryProvider = Provider<DriftBackupRepository>(
(ref) => DriftBackupRepository(ref.watch(driftProvider)),
);
class DriftBackupRepository extends DriftDatabaseRepository {
final Drift _db;
const DriftBackupRepository(this._db) : super(_db);
_getExcludedSubquery() {
return _db.localAlbumAssetEntity.selectOnly()
..addColumns([_db.localAlbumAssetEntity.assetId])
..join([
innerJoin(
_db.localAlbumEntity,
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
useColumns: false,
),
])
..where(
_db.localAlbumEntity.backupSelection
.equalsValue(BackupSelection.excluded),
);
}
Future<int> getTotalCount() async {
final query = _db.localAlbumAssetEntity.selectOnly(distinct: true)
..addColumns([_db.localAlbumAssetEntity.assetId])
..join([
innerJoin(
_db.localAlbumEntity,
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
useColumns: false,
),
])
..where(
_db.localAlbumEntity.backupSelection
.equalsValue(BackupSelection.selected) &
_db.localAlbumAssetEntity.assetId
.isNotInQuery(_getExcludedSubquery()),
);
return query.get().then((rows) => rows.length);
}
Future<int> getRemainderCount() async {
final query = _db.localAlbumAssetEntity.selectOnly(distinct: true)
..addColumns(
[_db.localAlbumAssetEntity.assetId],
)
..join([
innerJoin(
_db.localAlbumEntity,
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
useColumns: false,
),
innerJoin(
_db.localAssetEntity,
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id),
useColumns: false,
),
leftOuterJoin(
_db.remoteAssetEntity,
_db.localAssetEntity.checksum
.equalsExp(_db.remoteAssetEntity.checksum),
useColumns: false,
),
])
..where(
_db.localAlbumEntity.backupSelection
.equalsValue(BackupSelection.selected) &
_db.remoteAssetEntity.id.isNull() &
_db.localAlbumAssetEntity.assetId
.isNotInQuery(_getExcludedSubquery()),
);
return query.get().then((rows) => rows.length);
}
Future<int> getBackupCount() async {
final query = _db.localAlbumAssetEntity.selectOnly(distinct: true)
..addColumns(
[_db.localAlbumAssetEntity.assetId],
)
..join([
innerJoin(
_db.localAlbumEntity,
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
useColumns: false,
),
innerJoin(
_db.localAssetEntity,
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id),
useColumns: false,
),
innerJoin(
_db.remoteAssetEntity,
_db.localAssetEntity.checksum
.equalsExp(_db.remoteAssetEntity.checksum),
useColumns: false,
),
])
..where(
_db.localAlbumEntity.backupSelection
.equalsValue(BackupSelection.selected) &
_db.remoteAssetEntity.id.isNotNull() &
_db.localAlbumAssetEntity.assetId
.isNotInQuery(_getExcludedSubquery()),
);
return query.get().then((rows) => rows.length);
}
Future<List<LocalAsset>> getCandidates() async {
final selectedAlbumIds = _db.localAlbumEntity.selectOnly(distinct: true)
..addColumns([_db.localAlbumEntity.id])
..where(
_db.localAlbumEntity.backupSelection
.equalsValue(BackupSelection.selected),
);
final query = _db.localAssetEntity.select()
..where(
(lae) =>
existsQuery(
_db.localAlbumAssetEntity.selectOnly()
..addColumns([_db.localAlbumAssetEntity.assetId])
..where(
_db.localAlbumAssetEntity.albumId
.isInQuery(selectedAlbumIds) &
_db.localAlbumAssetEntity.assetId.equalsExp(lae.id),
),
) &
notExistsQuery(
_db.remoteAssetEntity.selectOnly()
..addColumns([_db.remoteAssetEntity.checksum])
..where(
_db.remoteAssetEntity.checksum.equalsExp(lae.checksum) &
lae.checksum.isNotNull(),
),
) &
lae.id.isNotInQuery(_getExcludedSubquery()),
);
return query.map((localAsset) => localAsset.toDto()).get();
}
}
@@ -5,6 +5,7 @@ import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.d
import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/utils/database.utils.dart';
import 'package:platform/platform.dart';
enum SortLocalAlbumsBy { id, backupSelection, isIosSharedAlbum }
@@ -381,30 +382,3 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
return results.isNotEmpty ? results.first : null;
}
}
extension on LocalAlbumEntityData {
LocalAlbum toDto({int assetCount = 0}) {
return LocalAlbum(
id: id,
name: name,
updatedAt: updatedAt,
assetCount: assetCount,
backupSelection: backupSelection,
);
}
}
extension on LocalAssetEntityData {
LocalAsset toDto() {
return LocalAsset(
id: id,
name: name,
checksum: checksum,
type: type,
createdAt: createdAt,
updatedAt: updatedAt,
durationInSeconds: durationInSeconds,
isFavorite: isFavorite,
);
}
}
@@ -56,4 +56,11 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
}
});
}
Future<LocalAsset?> getById(String id) {
final query = _db.localAssetEntity.select()
..where((lae) => lae.id.equals(id));
return query.map((row) => row.toDto()).getSingleOrNull();
}
}
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:logging/logging.dart';
import 'package:photo_manager/photo_manager.dart';
@@ -7,8 +8,9 @@ class StorageRepository {
const StorageRepository();
Future<File?> getFileForAsset(String assetId) async {
final log = Logger('StorageRepository');
File? file;
final log = Logger('StorageRepository');
try {
final entity = await AssetEntity.fromId(assetId);
file = await entity?.originFile;
@@ -20,4 +22,48 @@ class StorageRepository {
}
return file;
}
Future<File?> getMotionFileForAsset(LocalAsset asset) async {
File? file;
final log = Logger('StorageRepository');
try {
final entity = await AssetEntity.fromId(asset.id);
file = await entity?.originFileWithSubtype;
if (file == null) {
log.warning(
"Cannot get motion file for asset ${asset.id}, name: ${asset.name}, created on: ${asset.createdAt}",
);
}
} catch (error, stackTrace) {
log.warning(
"Error getting motion file for asset ${asset.id}, name: ${asset.name}, created on: ${asset.createdAt}",
error,
stackTrace,
);
}
return file;
}
Future<AssetEntity?> getAssetEntityForAsset(LocalAsset asset) async {
final log = Logger('StorageRepository');
AssetEntity? entity;
try {
entity = await AssetEntity.fromId(asset.id);
if (entity == null) {
log.warning(
"Cannot get AssetEntity for asset ${asset.id}, name: ${asset.name}, created on: ${asset.createdAt}",
);
}
} catch (error, stackTrace) {
log.warning(
"Error getting AssetEntity for asset ${asset.id}, name: ${asset.name}, created on: ${asset.createdAt}",
error,
stackTrace,
);
}
return entity;
}
}