1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-16 00:36:47 +01:00

Merge branch 'feat/inline-offline-check' of https://github.com/immich-app/immich into feat/inline-offline-check

This commit is contained in:
Jonathan Jogenfors 2024-12-13 10:54:36 +01:00
commit 745958e5d2
4 changed files with 63 additions and 39 deletions

View file

@ -178,9 +178,9 @@ export interface IAssetRepository {
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
getRandom(userIds: string[], count: number): Promise<AssetEntity[]>;
getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise<AssetEntity | null>;
deleteAll(ownerId: string): Promise<void>;
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
getAllInLibrary(pagination: PaginationOptions, libraryId: string): Paginated<AssetEntity>;
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
getLivePhotoCount(motionId: string): Promise<number>;
updateAll(ids: string[], options: Partial<AssetUpdateAllOptions>): Promise<void>;
@ -201,4 +201,5 @@ export interface IAssetRepository {
upsertFiles(files: UpsertFileOptions[]): Promise<void>;
updateOffline(library: LibraryEntity): Promise<UpdateResult>;
getNewPaths(libraryId: string, paths: string[]): Promise<string[]>;
getAssetCount(id: string, options: AssetSearchOptions): Promise<number | undefined>;
}

View file

@ -197,14 +197,6 @@ export class AssetRepository implements IAssetRepository {
return this.getAll(pagination, { ...options, userIds: [userId] });
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise<AssetEntity | null> {
return this.repository.findOne({
where: { library: { id: libraryId }, originalPath },
withDeleted: true,
});
}
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.STRING]] })
@ChunkedArray({ paramIndex: 1 })
async getPathsNotInLibrary(libraryId: string, originalPaths: string[]): Promise<string[]> {
@ -231,6 +223,20 @@ export class AssetRepository implements IAssetRepository {
});
}
getAllInLibrary(pagination: PaginationOptions, libraryId: string): Paginated<AssetEntity> {
const builder = this.repository
.createQueryBuilder('asset')
.select('asset.id')
.where('asset.libraryId = :libraryId', { libraryId })
.withDeleted();
return paginatedBuilder<AssetEntity>(builder, {
mode: PaginationMode.SKIP_TAKE,
skip: pagination.skip,
take: pagination.take,
});
}
/**
* Get assets by device's Id on the database
* @param ownerId
@ -779,4 +785,10 @@ export class AssetRepository implements IAssetRepository {
.query(rawSql, [paths, libraryId])
.then((result) => result.map((row: { path: string }) => row.path));
}
async getAssetCount(id: string, options: AssetSearchOptions = {}): Promise<number | undefined> {
let builder = this.repository.createQueryBuilder('asset').leftJoinAndSelect('asset.files', 'files');
builder = searchAssetBuilder(builder, options);
return builder.getCount();
}
}

View file

@ -14,6 +14,7 @@ export class TrashRepository implements ITrashRepository {
.createQueryBuilder('asset')
.select('asset.id')
.where({ status: AssetStatus.DELETED })
.orWhere({ isOffline: true })
.withDeleted(),
pagination,
);
@ -34,10 +35,13 @@ export class TrashRepository implements ITrashRepository {
}
async empty(userId: string): Promise<number> {
const result = await this.assetRepository.update(
{ ownerId: userId, status: AssetStatus.TRASHED },
{ status: AssetStatus.DELETED },
);
const result = await this.assetRepository
.createQueryBuilder()
.update(AssetEntity)
.set({ status: AssetStatus.DELETED })
.where({ ownerId: userId, status: AssetStatus.TRASHED })
.orWhere({ ownerId: userId, isOffline: true })
.execute();
return result.affected || 0;
}

View file

@ -388,15 +388,14 @@ export class LibraryService extends BaseService {
this.logger.log(`Starting to scan library ${id}`);
await this.jobRepository.queueAll([
{
name: JobName.LIBRARY_QUEUE_SYNC_FILES,
data: {
id,
},
await this.jobRepository.queue({
name: JobName.LIBRARY_QUEUE_SYNC_FILES,
data: {
id,
},
{ name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, data: { id } },
]);
});
await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, data: { id } });
}
@OnJob({ name: JobName.LIBRARY_QUEUE_SYNC_ALL, queue: QueueName.LIBRARY })
@ -434,6 +433,8 @@ export class LibraryService extends BaseService {
const assetIdsToOffline: string[] = [];
const assetIdsToUpdate: string[] = [];
this.logger.debug(`Checking batch of ${assets.length} existing asset(s) in library ${job.libraryId}`);
for (const asset of assets) {
const action = await this.handleSyncAsset(asset);
switch (action) {
@ -447,30 +448,28 @@ export class LibraryService extends BaseService {
}
if (assetIdsToOffline.length) {
await this.assetRepository.updateAll(assetIdsToOffline, { isOffline: true, deletedAt: new Date() });
this.logger.log(
`Originals are missing for ${assetIdsToOffline.length} asset(s) in library ${job.libraryId}, marked offline`,
);
await this.assetRepository.updateAll(assetIdsToOffline, {
isOffline: true,
status: AssetStatus.TRASHED,
deletedAt: new Date(),
});
}
if (assetIdsToUpdate.length) {
//TODO: When we have asset status, we need to leave deletedAt as is when status is trashed
await this.assetRepository.updateAll(assetIdsToUpdate, {
isOffline: false,
status: AssetStatus.ACTIVE,
deletedAt: null,
});
this.logger.log(
`Found ${assetIdsToOffline.length} asset(s) with modified files for library ${job.libraryId}, queuing refresh...`,
);
await this.queuePostSyncJobs(assetIdsToUpdate);
}
const remainingCount = assets.length - assetIdsToOffline.length - assetIdsToUpdate.length;
if (remainingCount > 0) {
this.logger.log(`${remainingCount} asset(s) are unchanged in library ${job.libraryId}, no action required`);
}
this.logger.log(
`Checked existing asset(s): ${assetIdsToOffline.length} offlined, ${assetIdsToUpdate.length} updated, ${remainingCount} unchanged of batch of ${assets.length} in library ${job.libraryId}.`,
);
return JobStatus.SUCCESS;
}
@ -606,7 +605,7 @@ export class LibraryService extends BaseService {
`Finished crawling ${crawlCount} file(s) of which ${importCount} file(s) are queued for import for library ${library.id}`,
);
} else {
this.logger.log(`All ${crawlCount} file(s) on disk are already in ${library.id}`);
this.logger.log(`All ${crawlCount} file(s) on disk are already in library ${library.id}`);
}
await this.libraryRepository.update({ id: job.id, refreshedAt: new Date() });
@ -621,11 +620,17 @@ export class LibraryService extends BaseService {
return JobStatus.SKIPPED;
}
const assetCount = (await this.getStatistics(library.id)).total;
const assetCount = await this.assetRepository.getAssetCount(library.id, { withDeleted: true });
if (!assetCount) {
this.logger.log(`Library ${library.id} is empty, no need to check assets`);
return JobStatus.SUCCESS;
}
this.logger.log(
`Scanning library ${library.id} for assets outside of import paths and/or matching an exclusion pattern...`,
`${assetCount} asset(s) in library ${library.id} will be checked against import paths and exclusion patterns...`,
);
const offlineResult = await this.assetRepository.updateOffline(library);
const affectedAssetCount = offlineResult.affected;
@ -638,6 +643,8 @@ export class LibraryService extends BaseService {
this.logger.log(
`All ${assetCount} asset(s) in ${library.id} are outside of import paths and/or match an exclusion pattern, marked as offline`,
);
return JobStatus.SUCCESS;
} else if (affectedAssetCount !== assetCount && affectedAssetCount > 0) {
this.logger.log(
`${offlineResult.affected} asset(s) out of ${assetCount} were marked offline due to import paths and/or exclusion patterns for library ${library.id}`,
@ -651,7 +658,7 @@ export class LibraryService extends BaseService {
this.logger.log(`Scanning library ${library.id} for assets missing from disk...`);
const existingAssets = usePagination(JOBS_LIBRARY_PAGINATION_SIZE, (pagination) =>
this.assetRepository.getAll(pagination, { libraryId: job.id, withDeleted: true }),
this.assetRepository.getAllInLibrary(pagination, job.id),
);
let currentAssetCount = 0;
@ -667,12 +674,12 @@ export class LibraryService extends BaseService {
});
this.logger.log(
`Queued check of ${assets.length} existing asset(s) in library ${library.id}, ${currentAssetCount} of ${assetCount} queued in total`,
`Queued check of ${currentAssetCount} of ${assetCount} existing asset(s) so far in library ${library.id}`,
);
}
if (currentAssetCount) {
this.logger.log(`Finished queuing ${currentAssetCount} file checks for library ${library.id}`);
this.logger.log(`Finished queuing ${currentAssetCount} asset check(s) for library ${library.id}`);
}
return JobStatus.SUCCESS;