Skip to content

Commit 5b00bc4

Browse files
authored
fix(server): Allow commas and braces in import paths (immich-app#13259)
fix commas and braces in paths
1 parent 94d213b commit 5b00bc4

File tree

2 files changed

+65
-6
lines changed

2 files changed

+65
-6
lines changed

e2e/src/api/specs/library.e2e-spec.ts

+56
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,62 @@ describe('/libraries', () => {
347347
expect(assets.items.find((asset) => asset.originalPath.includes('directoryB'))).toBeDefined();
348348
});
349349

350+
it('should scan multiple import paths with commas', async () => {
351+
// https://github.com/immich-app/immich/issues/10699
352+
const library = await utils.createLibrary(admin.accessToken, {
353+
ownerId: admin.userId,
354+
importPaths: [`${testAssetDirInternal}/temp/folder, a`, `${testAssetDirInternal}/temp/folder, b`],
355+
});
356+
357+
utils.createImageFile(`${testAssetDir}/temp/folder, a/assetA.png`);
358+
utils.createImageFile(`${testAssetDir}/temp/folder, b/assetB.png`);
359+
360+
const { status } = await request(app)
361+
.post(`/libraries/${library.id}/scan`)
362+
.set('Authorization', `Bearer ${admin.accessToken}`)
363+
.send();
364+
expect(status).toBe(204);
365+
366+
await utils.waitForQueueFinish(admin.accessToken, 'library');
367+
368+
const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
369+
370+
expect(assets.count).toBe(2);
371+
expect(assets.items.find((asset) => asset.originalPath.includes('folder, a'))).toBeDefined();
372+
expect(assets.items.find((asset) => asset.originalPath.includes('folder, b'))).toBeDefined();
373+
374+
utils.removeImageFile(`${testAssetDir}/temp/folder, a/assetA.png`);
375+
utils.removeImageFile(`${testAssetDir}/temp/folder, b/assetB.png`);
376+
});
377+
378+
it('should scan multiple import paths with braces', async () => {
379+
// https://github.com/immich-app/immich/issues/10699
380+
const library = await utils.createLibrary(admin.accessToken, {
381+
ownerId: admin.userId,
382+
importPaths: [`${testAssetDirInternal}/temp/folder{ a`, `${testAssetDirInternal}/temp/folder} b`],
383+
});
384+
385+
utils.createImageFile(`${testAssetDir}/temp/folder{ a/assetA.png`);
386+
utils.createImageFile(`${testAssetDir}/temp/folder} b/assetB.png`);
387+
388+
const { status } = await request(app)
389+
.post(`/libraries/${library.id}/scan`)
390+
.set('Authorization', `Bearer ${admin.accessToken}`)
391+
.send();
392+
expect(status).toBe(204);
393+
394+
await utils.waitForQueueFinish(admin.accessToken, 'library');
395+
396+
const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
397+
398+
expect(assets.count).toBe(2);
399+
expect(assets.items.find((asset) => asset.originalPath.includes('folder{ a'))).toBeDefined();
400+
expect(assets.items.find((asset) => asset.originalPath.includes('folder} b'))).toBeDefined();
401+
402+
utils.removeImageFile(`${testAssetDir}/temp/folder{ a/assetA.png`);
403+
utils.removeImageFile(`${testAssetDir}/temp/folder} b/assetB.png`);
404+
});
405+
350406
it('should reimport a modified file', async () => {
351407
const library = await utils.createLibrary(admin.accessToken, {
352408
ownerId: admin.userId,

server/src/repositories/storage.repository.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,9 @@ export class StorageRepository implements IStorageRepository {
156156
return Promise.resolve([]);
157157
}
158158

159-
return glob(this.asGlob(pathsToCrawl), {
159+
const globbedPaths = pathsToCrawl.map((path) => this.asGlob(path));
160+
161+
return glob(globbedPaths, {
160162
absolute: true,
161163
caseSensitiveMatch: false,
162164
onlyFiles: true,
@@ -172,7 +174,9 @@ export class StorageRepository implements IStorageRepository {
172174
return emptyGenerator();
173175
}
174176

175-
const stream = globStream(this.asGlob(pathsToCrawl), {
177+
const globbedPaths = pathsToCrawl.map((path) => this.asGlob(path));
178+
179+
const stream = globStream(globbedPaths, {
176180
absolute: true,
177181
caseSensitiveMatch: false,
178182
onlyFiles: true,
@@ -206,10 +210,9 @@ export class StorageRepository implements IStorageRepository {
206210
return () => watcher.close();
207211
}
208212

209-
private asGlob(pathsToCrawl: string[]): string {
210-
const escapedPaths = pathsToCrawl.map((path) => escapePath(path));
211-
const base = escapedPaths.length === 1 ? escapedPaths[0] : `{${escapedPaths.join(',')}}`;
213+
private asGlob(pathToCrawl: string): string {
214+
const escapedPath = escapePath(pathToCrawl);
212215
const extensions = `*{${mimeTypes.getSupportedFileExtensions().join(',')}}`;
213-
return `${base}/**/${extensions}`;
216+
return `${escapedPath}/**/${extensions}`;
214217
}
215218
}

0 commit comments

Comments
 (0)