1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

fix(server): prevent feedback loop during library scan (#7944)

* prevent feedback loop

* add nesting

* made nesting less ugly

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Mert 2024-03-15 18:01:58 -04:00 committed by GitHub
parent eea0a98090
commit a9438a9c2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 60 additions and 4 deletions

View File

@ -89,7 +89,7 @@ export class ValidateLibraryResponseDto {
export class ValidateLibraryImportPathResponseDto {
importPath!: string;
isValid?: boolean = false;
isValid: boolean = false;
message?: string;
}

View File

@ -18,6 +18,7 @@ import {
userStub,
} from '@test';
import { when } from 'jest-when';
import { R_OK } from 'node:constants';
import { Stats } from 'node:fs';
import { ILibraryFileJob, ILibraryRefreshJob, JobName } from '../job';
import {
@ -1632,5 +1633,32 @@ describe(LibraryService.name, () => {
},
]);
});
it('should detect when import path is in immich media folder', async () => {
storageMock.stat.mockResolvedValue({ isDirectory: () => true } as Stats);
const validImport = libraryStub.hasImmichPaths.importPaths[1];
when(storageMock.checkFileExists).calledWith(validImport, R_OK).mockResolvedValue(true);
const result = await sut.validate(authStub.external1, libraryStub.hasImmichPaths.id, {
importPaths: libraryStub.hasImmichPaths.importPaths,
});
expect(result.importPaths).toEqual([
{
importPath: libraryStub.hasImmichPaths.importPaths[0],
isValid: false,
message: 'Cannot use media upload folder for external libraries',
},
{
importPath: validImport,
isValid: true,
},
{
importPath: libraryStub.hasImmichPaths.importPaths[2],
isValid: false,
message: 'Cannot use media upload folder for external libraries',
},
]);
});
});
});

View File

@ -26,6 +26,7 @@ import {
StorageEventType,
WithProperty,
} from '../repositories';
import { StorageCore } from '../storage';
import { SystemConfigCore } from '../system-config';
import {
CreateLibraryDto,
@ -327,9 +328,13 @@ export class LibraryService extends EventEmitter {
const validation = new ValidateLibraryImportPathResponseDto();
validation.importPath = importPath;
if (StorageCore.isImmichPath(importPath)) {
validation.message = 'Cannot use media upload folder for external libraries';
return validation;
}
try {
const stat = await this.storageRepository.stat(importPath);
if (!stat.isDirectory()) {
validation.message = 'Not a directory';
return validation;
@ -678,13 +683,13 @@ export class LibraryService extends EventEmitter {
this.logger.debug(`Will import ${crawledAssetPaths.size} new asset(s)`);
}
const batch = [];
let batch = [];
for (const assetPath of crawledAssetPaths) {
batch.push(assetPath);
if (batch.length >= LIBRARY_SCAN_BATCH_SIZE) {
await this.scanAssets(job.id, batch, library.ownerId, job.refreshAllFiles ?? false);
batch.length = 0;
batch = [];
}
}

View File

@ -20,6 +20,9 @@ export enum StorageFolder {
THUMBNAILS = 'thumbs',
}
export const THUMBNAIL_DIR = resolve(join(APP_MEDIA_LOCATION, StorageFolder.THUMBNAILS));
export const ENCODED_VIDEO_DIR = resolve(join(APP_MEDIA_LOCATION, StorageFolder.ENCODED_VIDEO));
export interface MoveRequest {
entityId: string;
pathType: PathType;
@ -115,6 +118,10 @@ export class StorageCore {
return resolve(path).startsWith(resolve(APP_MEDIA_LOCATION));
}
static isGeneratedAsset(path: string) {
return path.startsWith(THUMBNAIL_DIR) || path.startsWith(ENCODED_VIDEO_DIR);
}
async moveAssetFile(asset: AssetEntity, pathType: GeneratedAssetPath) {
const { id: entityId, resizePath, webpPath, encodedVideoPath } = asset;
switch (pathType) {

View File

@ -1,4 +1,6 @@
import { APP_MEDIA_LOCATION, THUMBNAIL_DIR } from '@app/domain';
import { LibraryEntity, LibraryType } from '@app/infra/entities';
import { join } from 'node:path';
import { userStub } from './user.stub';
export const libraryStub = {
@ -100,4 +102,18 @@ export const libraryStub = {
isVisible: true,
exclusionPatterns: ['**/dir1/**'],
}),
hasImmichPaths: Object.freeze<LibraryEntity>({
id: 'library-id1337',
name: 'importpath-exclusion-library1',
assets: [],
owner: userStub.admin,
ownerId: 'user-id',
type: LibraryType.EXTERNAL,
importPaths: [join(THUMBNAIL_DIR, 'library'), '/xyz', join(APP_MEDIA_LOCATION, 'library')],
createdAt: new Date('2023-01-01'),
updatedAt: new Date('2023-01-01'),
refreshedAt: null,
isVisible: true,
exclusionPatterns: ['**/dir1/**'],
}),
};