You've already forked immich
mirror of
https://github.com/immich-app/immich.git
synced 2025-08-08 23:07:06 +02:00
refactor(mobile): more repositories (#12879)
* ExifInfoRepository * ActivityApiRepository * initial AssetApiRepository
This commit is contained in:
committed by
GitHub
parent
56f680ce04
commit
e0fa3cdbc7
@ -1,41 +1,31 @@
|
||||
import 'package:immich_mobile/constants/errors.dart';
|
||||
import 'package:immich_mobile/interfaces/activity_api.interface.dart';
|
||||
import 'package:immich_mobile/mixins/error_logger.mixin.dart';
|
||||
import 'package:immich_mobile/models/activities/activity.model.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class ActivityService with ErrorLoggerMixin {
|
||||
final ApiService _apiService;
|
||||
final IActivityApiRepository _activityApiRepository;
|
||||
|
||||
@override
|
||||
final Logger logger = Logger("ActivityService");
|
||||
|
||||
ActivityService(this._apiService);
|
||||
ActivityService(this._activityApiRepository);
|
||||
|
||||
Future<List<Activity>> getAllActivities(
|
||||
String albumId, {
|
||||
String? assetId,
|
||||
}) async {
|
||||
return logError(
|
||||
() async {
|
||||
final list = await _apiService.activitiesApi
|
||||
.getActivities(albumId, assetId: assetId);
|
||||
return list != null ? list.map(Activity.fromDto).toList() : [];
|
||||
},
|
||||
() => _activityApiRepository.getAll(albumId, assetId: assetId),
|
||||
defaultValue: [],
|
||||
errorMessage: "Failed to get all activities for album $albumId",
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> getStatistics(String albumId, {String? assetId}) async {
|
||||
Future<ActivityStats> getStatistics(String albumId, {String? assetId}) async {
|
||||
return logError(
|
||||
() async {
|
||||
final dto = await _apiService.activitiesApi
|
||||
.getActivityStatistics(albumId, assetId: assetId);
|
||||
return dto?.comments ?? 0;
|
||||
},
|
||||
defaultValue: 0,
|
||||
() => _activityApiRepository.getStats(albumId, assetId: assetId),
|
||||
defaultValue: const ActivityStats(comments: 0),
|
||||
errorMessage: "Failed to statistics for album $albumId",
|
||||
);
|
||||
}
|
||||
@ -43,7 +33,7 @@ class ActivityService with ErrorLoggerMixin {
|
||||
Future<bool> removeActivity(String id) async {
|
||||
return logError(
|
||||
() async {
|
||||
await _apiService.activitiesApi.deleteActivity(id);
|
||||
await _activityApiRepository.delete(id);
|
||||
return true;
|
||||
},
|
||||
defaultValue: false,
|
||||
@ -58,22 +48,12 @@ class ActivityService with ErrorLoggerMixin {
|
||||
String? comment,
|
||||
}) async {
|
||||
return guardError(
|
||||
() async {
|
||||
final dto = await _apiService.activitiesApi.createActivity(
|
||||
ActivityCreateDto(
|
||||
albumId: albumId,
|
||||
type: type == ActivityType.comment
|
||||
? ReactionType.comment
|
||||
: ReactionType.like,
|
||||
assetId: assetId,
|
||||
comment: comment,
|
||||
),
|
||||
);
|
||||
if (dto != null) {
|
||||
return Activity.fromDto(dto);
|
||||
}
|
||||
throw NoResponseDtoError();
|
||||
},
|
||||
() => _activityApiRepository.create(
|
||||
albumId,
|
||||
type,
|
||||
assetId: assetId,
|
||||
comment: comment,
|
||||
),
|
||||
errorMessage: "Failed to create $type for album $albumId",
|
||||
);
|
||||
}
|
||||
|
@ -9,9 +9,13 @@ import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/entities/etag.entity.dart';
|
||||
import 'package:immich_mobile/entities/exif_info.entity.dart';
|
||||
import 'package:immich_mobile/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/asset_api.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/exif_info.interface.dart';
|
||||
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||
import 'package:immich_mobile/repositories/exif_info.repository.dart';
|
||||
import 'package:immich_mobile/services/album.service.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/services/backup.service.dart';
|
||||
@ -24,6 +28,8 @@ import 'package:openapi/api.dart';
|
||||
|
||||
final assetServiceProvider = Provider(
|
||||
(ref) => AssetService(
|
||||
ref.watch(assetApiRepositoryProvider),
|
||||
ref.watch(exifInfoRepositoryProvider),
|
||||
ref.watch(apiServiceProvider),
|
||||
ref.watch(syncServiceProvider),
|
||||
ref.watch(userServiceProvider),
|
||||
@ -34,6 +40,8 @@ final assetServiceProvider = Provider(
|
||||
);
|
||||
|
||||
class AssetService {
|
||||
final IAssetApiRepository _assetApiRepository;
|
||||
final IExifInfoRepository _exifInfoRepository;
|
||||
final ApiService _apiService;
|
||||
final SyncService _syncService;
|
||||
final UserService _userService;
|
||||
@ -43,6 +51,8 @@ class AssetService {
|
||||
final Isar _db;
|
||||
|
||||
AssetService(
|
||||
this._assetApiRepository,
|
||||
this._exifInfoRepository,
|
||||
this._apiService,
|
||||
this._syncService,
|
||||
this._userService,
|
||||
@ -342,4 +352,46 @@ class AssetService {
|
||||
log.severe("Error while syncing uploaded asset to albums", error, stack);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setDescription(
|
||||
Asset asset,
|
||||
String newDescription,
|
||||
) async {
|
||||
final remoteAssetId = asset.remoteId;
|
||||
final localExifId = asset.exifInfo?.id;
|
||||
|
||||
// Guard [remoteAssetId] and [localExifId] null
|
||||
if (remoteAssetId == null || localExifId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await _assetApiRepository.update(
|
||||
remoteAssetId,
|
||||
description: newDescription,
|
||||
);
|
||||
|
||||
final description = result.exifInfo?.description;
|
||||
|
||||
if (description != null) {
|
||||
var exifInfo = await _exifInfoRepository.get(localExifId);
|
||||
|
||||
if (exifInfo != null) {
|
||||
exifInfo.description = description;
|
||||
await _exifInfoRepository.update(exifInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> getDescription(Asset asset) async {
|
||||
final localExifId = asset.exifInfo?.id;
|
||||
|
||||
// Guard [remoteAssetId] and [localExifId] null
|
||||
if (localExifId == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final exifInfo = await _exifInfoRepository.get(localExifId);
|
||||
|
||||
return exifInfo?.description ?? "";
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +0,0 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/entities/exif_info.entity.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
class AssetDescriptionService {
|
||||
AssetDescriptionService(this._db, this._api);
|
||||
|
||||
final Isar _db;
|
||||
final ApiService _api;
|
||||
|
||||
Future<void> setDescription(
|
||||
Asset asset,
|
||||
String newDescription,
|
||||
) async {
|
||||
final remoteAssetId = asset.remoteId;
|
||||
final localExifId = asset.exifInfo?.id;
|
||||
|
||||
// Guard [remoteAssetId] and [localExifId] null
|
||||
if (remoteAssetId == null || localExifId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await _api.assetsApi.updateAsset(
|
||||
remoteAssetId,
|
||||
UpdateAssetDto(description: newDescription),
|
||||
);
|
||||
|
||||
final description = result?.exifInfo?.description;
|
||||
|
||||
if (description != null) {
|
||||
var exifInfo = await _db.exifInfos.get(localExifId);
|
||||
|
||||
if (exifInfo != null) {
|
||||
exifInfo.description = description;
|
||||
await _db.writeTxn(
|
||||
() => _db.exifInfos.put(exifInfo),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String getAssetDescription(Asset asset) {
|
||||
final localExifId = asset.exifInfo?.id;
|
||||
|
||||
// Guard [remoteAssetId] and [localExifId] null
|
||||
if (localExifId == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final exifInfo = _db.exifInfos.getSync(localExifId);
|
||||
|
||||
return exifInfo?.description ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
final assetDescriptionServiceProvider = Provider(
|
||||
(ref) => AssetDescriptionService(
|
||||
ref.watch(dbProvider),
|
||||
ref.watch(apiServiceProvider),
|
||||
),
|
||||
);
|
@ -8,41 +8,46 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/entities/exif_info.entity.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/exif_info.interface.dart';
|
||||
import 'package:immich_mobile/interfaces/file_media.interface.dart';
|
||||
import 'package:immich_mobile/providers/db.provider.dart';
|
||||
import 'package:immich_mobile/repositories/asset.repository.dart';
|
||||
import 'package:immich_mobile/repositories/exif_info.repository.dart';
|
||||
import 'package:immich_mobile/repositories/file_media.repository.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/diff.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
/// Finds duplicates originating from missing EXIF information
|
||||
class BackupVerificationService {
|
||||
final Isar _db;
|
||||
final IFileMediaRepository _fileMediaRepository;
|
||||
final IAssetRepository _assetRepository;
|
||||
final IExifInfoRepository _exifInfoRepository;
|
||||
|
||||
BackupVerificationService(this._db, this._fileMediaRepository);
|
||||
BackupVerificationService(
|
||||
this._fileMediaRepository,
|
||||
this._assetRepository,
|
||||
this._exifInfoRepository,
|
||||
);
|
||||
|
||||
/// Returns at most [limit] assets that were backed up without exif
|
||||
Future<List<Asset>> findWronglyBackedUpAssets({int limit = 100}) async {
|
||||
final owner = Store.get(StoreKey.currentUser).isarId;
|
||||
final List<Asset> onlyLocal = await _db.assets
|
||||
.where()
|
||||
.remoteIdIsNull()
|
||||
.filter()
|
||||
.ownerIdEqualTo(owner)
|
||||
.localIdIsNotNull()
|
||||
.findAll();
|
||||
final List<Asset> remoteMatches = await _getMatches(
|
||||
_db.assets.where().localIdIsNull().filter().remoteIdIsNotNull(),
|
||||
owner,
|
||||
onlyLocal,
|
||||
limit,
|
||||
final List<Asset> onlyLocal = await _assetRepository.getAll(
|
||||
ownerId: owner,
|
||||
remote: false,
|
||||
limit: limit,
|
||||
);
|
||||
final List<Asset> localMatches = await _getMatches(
|
||||
_db.assets.where().remoteIdIsNull().filter().localIdIsNotNull(),
|
||||
owner,
|
||||
remoteMatches,
|
||||
limit,
|
||||
final List<Asset> remoteMatches = await _assetRepository.getMatches(
|
||||
assets: onlyLocal,
|
||||
ownerId: owner,
|
||||
remote: true,
|
||||
limit: limit,
|
||||
);
|
||||
final List<Asset> localMatches = await _assetRepository.getMatches(
|
||||
assets: remoteMatches,
|
||||
ownerId: owner,
|
||||
remote: false,
|
||||
limit: limit,
|
||||
);
|
||||
|
||||
final List<Asset> deleteCandidates = [], originals = [];
|
||||
@ -52,7 +57,7 @@ class BackupVerificationService {
|
||||
localMatches,
|
||||
compare: (a, b) => a.fileName.compareTo(b.fileName),
|
||||
both: (a, b) async {
|
||||
a.exifInfo = await _db.exifInfos.get(a.id);
|
||||
a.exifInfo = await _exifInfoRepository.get(a.id);
|
||||
deleteCandidates.add(a);
|
||||
originals.add(b);
|
||||
return false;
|
||||
@ -192,35 +197,6 @@ class BackupVerificationService {
|
||||
return bytes.buffer.asUint64List(start);
|
||||
}
|
||||
|
||||
static Future<List<Asset>> _getMatches(
|
||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> query,
|
||||
int ownerId,
|
||||
List<Asset> assets,
|
||||
int limit,
|
||||
) =>
|
||||
query
|
||||
.ownerIdEqualTo(ownerId)
|
||||
.anyOf(
|
||||
assets,
|
||||
(q, Asset a) => q
|
||||
.fileNameEqualTo(a.fileName)
|
||||
.and()
|
||||
.durationInSecondsEqualTo(a.durationInSeconds)
|
||||
.and()
|
||||
.fileCreatedAtBetween(
|
||||
a.fileCreatedAt.subtract(const Duration(hours: 12)),
|
||||
a.fileCreatedAt.add(const Duration(hours: 12)),
|
||||
)
|
||||
.and()
|
||||
.not()
|
||||
.checksumEqualTo(a.checksum),
|
||||
)
|
||||
.sortByFileName()
|
||||
.thenByFileCreatedAt()
|
||||
.thenByFileModifiedAt()
|
||||
.limit(limit)
|
||||
.findAll();
|
||||
|
||||
static bool _sameExceptTimeZone(DateTime a, DateTime b) {
|
||||
final ms = a.isAfter(b)
|
||||
? a.millisecondsSinceEpoch - b.millisecondsSinceEpoch
|
||||
@ -233,7 +209,8 @@ class BackupVerificationService {
|
||||
|
||||
final backupVerificationServiceProvider = Provider(
|
||||
(ref) => BackupVerificationService(
|
||||
ref.watch(dbProvider),
|
||||
ref.watch(fileMediaRepositoryProvider),
|
||||
ref.watch(assetRepositoryProvider),
|
||||
ref.watch(exifInfoRepositoryProvider),
|
||||
),
|
||||
);
|
||||
|
Reference in New Issue
Block a user