mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat: add e2e tests for library watcher
This commit is contained in:
@@ -95,6 +95,10 @@ type EventMap = {
|
||||
|
||||
// websocket events
|
||||
WebsocketConnect: [{ userId: string }];
|
||||
|
||||
// library events
|
||||
LibraryWatchEnabled: [{ id: string }];
|
||||
LibraryWatchFired: [{ libraryId: string; event: 'add' | 'change' | 'unlink'; path: string; ignored: boolean }];
|
||||
};
|
||||
|
||||
export type AppRestartEvent = {
|
||||
|
||||
@@ -35,6 +35,9 @@ export interface ClientEventMap {
|
||||
on_notification: [NotificationDto];
|
||||
on_session_delete: [string];
|
||||
|
||||
on_library_watch_enabled: [{ libraryId: string }];
|
||||
on_library_watch_fired: [{ libraryId: string; event: 'add' | 'change' | 'unlink'; path: string; ignored: boolean }];
|
||||
|
||||
AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }];
|
||||
AppRestartV1: [AppRestartEvent];
|
||||
AssetEditReadyV1: [{ asset: SyncAssetV1; edit: SyncAssetEditV1[] }];
|
||||
|
||||
@@ -90,27 +90,50 @@ export class LibraryService extends BaseService {
|
||||
|
||||
this.logger.log(`Starting to watch library ${library.id} with import path(s) ${library.importPaths}`);
|
||||
|
||||
const matcher = picomatch(`**/*{${mimeTypes.getSupportedFileExtensions().join(',')}}`, {
|
||||
nocase: true,
|
||||
ignore: library.exclusionPatterns,
|
||||
});
|
||||
const supportedExtensions = mimeTypes.getSupportedFileExtensions().map((extension) => extension.toLowerCase());
|
||||
const exclusionPatterns = library.exclusionPatterns.flatMap((pattern) =>
|
||||
pattern.endsWith('/**') ? [pattern, pattern.slice(0, -3)] : [pattern],
|
||||
);
|
||||
const excludeMatcher = picomatch(exclusionPatterns, { nocase: true });
|
||||
const isExcluded = (path: string) => excludeMatcher(path.replaceAll('\\', '/'));
|
||||
const isSupportedFile = (path: string) => {
|
||||
const normalizedPath = path.toLowerCase();
|
||||
return supportedExtensions.some((extension) => normalizedPath.endsWith(extension));
|
||||
};
|
||||
|
||||
let _resolve: () => void;
|
||||
const ready$ = new Promise<void>((resolve) => (_resolve = resolve));
|
||||
|
||||
const handler = async (event: string, path: string) => {
|
||||
if (matcher(path)) {
|
||||
this.logger.debug(`File ${event} event received for ${path} in library ${library.id}}`);
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.LibrarySyncFiles,
|
||||
data: { libraryId: library.id, paths: [path] },
|
||||
});
|
||||
} else {
|
||||
const ignored = !isSupportedFile(path);
|
||||
|
||||
await this.eventRepository.emit('LibraryWatchFired', {
|
||||
libraryId: library.id,
|
||||
event: event as 'add' | 'change',
|
||||
path,
|
||||
ignored,
|
||||
});
|
||||
|
||||
if (ignored) {
|
||||
this.logger.verbose(`Ignoring file ${event} event for ${path} in library ${library.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(`File ${event} event received for ${path} in library ${library.id}}`);
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.LibrarySyncFiles,
|
||||
data: { libraryId: library.id, paths: [path] },
|
||||
});
|
||||
};
|
||||
|
||||
const deletionHandler = async (path: string) => {
|
||||
await this.eventRepository.emit('LibraryWatchFired', {
|
||||
libraryId: library.id,
|
||||
event: 'unlink',
|
||||
path,
|
||||
ignored: false,
|
||||
});
|
||||
|
||||
this.logger.debug(`File unlink event received for ${path} in library ${library.id}}`);
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.LibraryRemoveAsset,
|
||||
@@ -123,6 +146,7 @@ export class LibraryService extends BaseService {
|
||||
{
|
||||
usePolling: false,
|
||||
ignoreInitial: true,
|
||||
ignored: isExcluded,
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 5000,
|
||||
pollInterval: 1000,
|
||||
@@ -148,6 +172,8 @@ export class LibraryService extends BaseService {
|
||||
// Wait for the watcher to initialize before returning
|
||||
await ready$;
|
||||
|
||||
await this.eventRepository.emit('LibraryWatchEnabled', { id });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,16 @@ export class NotificationService extends BaseService {
|
||||
this.websocketRepository.serverSend('ConfigUpdate', { oldConfig, newConfig });
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'LibraryWatchEnabled' })
|
||||
onLibraryWatchEnabled({ id }: ArgOf<'LibraryWatchEnabled'>) {
|
||||
this.websocketRepository.clientBroadcast('on_library_watch_enabled', { libraryId: id });
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'LibraryWatchFired' })
|
||||
onLibraryWatchFired(event: ArgOf<'LibraryWatchFired'>) {
|
||||
this.websocketRepository.clientBroadcast('on_library_watch_fired', event);
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'AppRestart' })
|
||||
onAppRestart(state: ArgOf<'AppRestart'>) {
|
||||
this.websocketRepository.clientBroadcast('AppRestartV1', {
|
||||
|
||||
Reference in New Issue
Block a user