1
0
forked from Cutlery/immich

check import paths when testing offline

This commit is contained in:
Jonathan Jogenfors 2024-03-20 08:17:22 +01:00
parent 9a707874e4
commit f5073a1a7a
3 changed files with 50 additions and 11 deletions

View File

@ -21,6 +21,10 @@ export interface ILibraryRefreshJob extends IEntityJob {
refreshAllFiles: boolean; refreshAllFiles: boolean;
} }
export interface ILibraryOfflineJob extends IEntityJob {
importPaths: string[];
}
export interface IBulkEntityJob extends IBaseJob { export interface IBulkEntityJob extends IBaseJob {
ids: string[]; ids: string[];
} }

View File

@ -20,7 +20,7 @@ import {
import { when } from 'jest-when'; import { when } from 'jest-when';
import { R_OK } from 'node:constants'; import { R_OK } from 'node:constants';
import { Stats } from 'node:fs'; import { Stats } from 'node:fs';
import { IEntityJob, ILibraryFileJob, ILibraryRefreshJob, JobName } from '../job'; import { IEntityJob, ILibraryFileJob, ILibraryOfflineJob, ILibraryRefreshJob, JobName } from '../job';
import { import {
IAssetRepository, IAssetRepository,
ICryptoRepository, ICryptoRepository,
@ -277,8 +277,9 @@ describe(LibraryService.name, () => {
describe('handleOfflineCheck', () => { describe('handleOfflineCheck', () => {
it('should set missing assets offline', async () => { it('should set missing assets offline', async () => {
const mockAssetJob: IEntityJob = { const mockAssetJob: ILibraryOfflineJob = {
id: assetStub.external.id, id: assetStub.external.id,
importPaths: ['/'],
}; };
assetMock.getById.mockResolvedValue(assetStub.external); assetMock.getById.mockResolvedValue(assetStub.external);
@ -290,9 +291,25 @@ describe(LibraryService.name, () => {
expect(assetMock.update).toHaveBeenCalledWith({ id: assetStub.external.id, isOffline: true }); expect(assetMock.update).toHaveBeenCalledWith({ id: assetStub.external.id, isOffline: true });
}); });
it('should skip an offline asset', async () => { it('should set an asset outside of import paths as offline', async () => {
const mockAssetJob: IEntityJob = { const mockAssetJob: ILibraryOfflineJob = {
id: assetStub.external.id, id: assetStub.external.id,
importPaths: ['/data/user2'],
};
assetMock.getById.mockResolvedValue(assetStub.external);
storageMock.checkFileExists.mockResolvedValue(true);
await sut.handleOfflineCheck(mockAssetJob);
expect(assetMock.update).toHaveBeenCalledWith({ id: assetStub.external.id, isOffline: true });
});
it('should skip an offline asset', async () => {
const mockAssetJob: ILibraryOfflineJob = {
id: assetStub.external.id,
importPaths: ['/'],
}; };
assetMock.getById.mockResolvedValue(assetStub.offline); assetMock.getById.mockResolvedValue(assetStub.offline);
@ -306,8 +323,9 @@ describe(LibraryService.name, () => {
}); });
it('should skip a nonexistent asset id', async () => { it('should skip a nonexistent asset id', async () => {
const mockAssetJob: IEntityJob = { const mockAssetJob: ILibraryOfflineJob = {
id: assetStub.external.id, id: assetStub.external.id,
importPaths: ['/'],
}; };
assetMock.getById.mockImplementation(() => Promise.resolve(null)); assetMock.getById.mockImplementation(() => Promise.resolve(null));

View File

@ -1,4 +1,4 @@
import { AssetType, LibraryType } from '@app/infra/entities'; import { AssetEntity, AssetType, LibraryType } from '@app/infra/entities';
import { ImmichLogger } from '@app/infra/logger'; import { ImmichLogger } from '@app/infra/logger';
import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
@ -10,7 +10,15 @@ import picomatch from 'picomatch';
import { AccessCore } from '../access'; import { AccessCore } from '../access';
import { mimeTypes } from '../domain.constant'; import { mimeTypes } from '../domain.constant';
import { handlePromiseError, usePagination, validateCronExpression } from '../domain.util'; import { handlePromiseError, usePagination, validateCronExpression } from '../domain.util';
import { IBaseJob, IEntityJob, ILibraryFileJob, ILibraryRefreshJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job'; import {
IBaseJob,
IEntityJob,
ILibraryFileJob,
ILibraryOfflineJob,
ILibraryRefreshJob,
JOBS_ASSET_PAGINATION_SIZE,
JobName,
} from '../job';
import { import {
DatabaseLock, DatabaseLock,
IAccessRepository, IAccessRepository,
@ -575,7 +583,7 @@ export class LibraryService extends EventEmitter {
} }
// Check if an asset is has no file or is outside of import paths, marking it as offline // Check if an asset is has no file or is outside of import paths, marking it as offline
async handleOfflineCheck(job: IEntityJob): Promise<JobStatus> { async handleOfflineCheck(job: ILibraryOfflineJob): Promise<JobStatus> {
const asset = await this.assetRepository.getById(job.id); const asset = await this.assetRepository.getById(job.id);
if (!asset || asset.isOffline) { if (!asset || asset.isOffline) {
@ -585,7 +593,16 @@ export class LibraryService extends EventEmitter {
const exists = await this.storageRepository.checkFileExists(asset.originalPath, R_OK); const exists = await this.storageRepository.checkFileExists(asset.originalPath, R_OK);
if (exists) { let existsInImportPath = false;
for (const importPath of job.importPaths) {
if (asset.originalPath.startsWith(importPath)) {
existsInImportPath = true;
break;
}
}
if (exists && existsInImportPath) {
this.logger.verbose(`Asset is still online: ${asset.originalPath}`); this.logger.verbose(`Asset is still online: ${asset.originalPath}`);
} else { } else {
this.logger.debug(`Marking asset as offline: ${asset.originalPath}`); this.logger.debug(`Marking asset as offline: ${asset.originalPath}`);
@ -657,9 +674,9 @@ export class LibraryService extends EventEmitter {
`Queuing online check of ${existingAssetPage.value.length} asset(s) in library ${library.id}...`, `Queuing online check of ${existingAssetPage.value.length} asset(s) in library ${library.id}...`,
); );
await this.jobRepository.queueAll( await this.jobRepository.queueAll(
existingAssetPage.value.map((asset) => ({ existingAssetPage.value.map((asset: AssetEntity) => ({
name: JobName.LIBRARY_CHECK_OFFLINE, name: JobName.LIBRARY_CHECK_OFFLINE,
data: { id: asset.id }, data: { id: asset.id, importPaths: validImportPaths },
})), })),
); );
} }