diff --git a/cli/src/commands/upload.ts b/cli/src/commands/upload.ts index b0f192dca2..da3146f95e 100644 --- a/cli/src/commands/upload.ts +++ b/cli/src/commands/upload.ts @@ -62,6 +62,10 @@ export default class Upload extends BaseCommand { // Compute total size first await asset.process(); totalSize += asset.fileSize; + + if (options.albumName) { + asset.albumName = options.albumName; + } } const existingAlbums = (await this.immichApi.albumApi.getAllAlbums()).data; @@ -76,6 +80,10 @@ export default class Upload extends BaseCommand { }); let skipUpload = false; + + let skipAsset = false; + let existingAssetId: string | undefined = undefined; + if (!options.skipHash) { const assetBulkUploadCheckDto = { assets: [{ id: asset.path, checksum: await asset.hash() }] }; @@ -84,14 +92,24 @@ export default class Upload extends BaseCommand { }); skipUpload = checkResponse.data.results[0].action === 'reject'; + + const isDuplicate = checkResponse.data.results[0].reason === 'duplicate'; + if (isDuplicate) { + existingAssetId = checkResponse.data.results[0].assetId; + } + + skipAsset = skipUpload && !isDuplicate; } - if (!skipUpload) { + if (!skipAsset) { if (!options.dryRun) { - const formData = asset.getUploadFormData(); - const res = await this.uploadAsset(formData); + if (!skipUpload) { + const formData = asset.getUploadFormData(); + const res = await this.uploadAsset(formData); + existingAssetId = res.data.id; + } - if (options.album && asset.albumName) { + if ((options.album || options.albumName) && asset.albumName !== undefined) { let album = existingAlbums.find((album) => album.albumName === asset.albumName); if (!album) { const res = await this.immichApi.albumApi.createAlbum({ @@ -101,7 +119,12 @@ export default class Upload extends BaseCommand { existingAlbums.push(album); } - await this.immichApi.albumApi.addAssetsToAlbum({ id: album.id, bulkIdsDto: { ids: [res.data.id] } }); + if (existingAssetId) { + await this.immichApi.albumApi.addAssetsToAlbum({ + id: album.id, + bulkIdsDto: { ids: [existingAssetId] }, + }); + } } } diff --git a/cli/src/cores/dto/upload-options-dto.ts b/cli/src/cores/dto/upload-options-dto.ts index 8b5fbdc4a7..943f321a24 100644 --- a/cli/src/cores/dto/upload-options-dto.ts +++ b/cli/src/cores/dto/upload-options-dto.ts @@ -5,5 +5,6 @@ export class UploadOptionsDto { skipHash? = false; delete? = false; album? = false; + albumName? = ''; includeHidden? = false; } diff --git a/cli/src/index.ts b/cli/src/index.ts index 3e5b74c47d..e27c379158 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -32,6 +32,11 @@ program .env('IMMICH_AUTO_CREATE_ALBUM') .default(false), ) + .addOption( + new Option('-A, --album-name ', 'Add all assets to specified album') + .env('IMMICH_ALBUM_NAME') + .conflicts('album'), + ) .addOption( new Option('-n, --dry-run', "Don't perform any actions, just show what will be done") .env('IMMICH_DRY_RUN') diff --git a/cli/test/e2e/upload.e2e-spec.ts b/cli/test/e2e/upload.e2e-spec.ts index 738d9125d1..04b005f47c 100644 --- a/cli/test/e2e/upload.e2e-spec.ts +++ b/cli/test/e2e/upload.e2e-spec.ts @@ -35,6 +35,12 @@ describe(`upload (e2e)`, () => { expect(assets.length).toBeGreaterThan(4); }); + it('should not create a new album', async () => { + await new Upload(CLI_BASE_OPTIONS).run([`${IMMICH_TEST_ASSET_PATH}/albums/nature/`], { recursive: true }); + const albums = await api.albumApi.getAllAlbums(server, admin.accessToken); + expect(albums.length).toEqual(0); + }); + it('should create album from folder name', async () => { await new Upload(CLI_BASE_OPTIONS).run([`${IMMICH_TEST_ASSET_PATH}/albums/nature/`], { recursive: true, @@ -46,4 +52,33 @@ describe(`upload (e2e)`, () => { const natureAlbum = albums[0]; expect(natureAlbum.albumName).toEqual('nature'); }); + + it('should add existing assets to album', async () => { + await new Upload(CLI_BASE_OPTIONS).run([`${IMMICH_TEST_ASSET_PATH}/albums/nature/`], { + recursive: true, + }); + + // Upload again, but this time add to album + await new Upload(CLI_BASE_OPTIONS).run([`${IMMICH_TEST_ASSET_PATH}/albums/nature/`], { + recursive: true, + album: true, + }); + + const albums = await api.albumApi.getAllAlbums(server, admin.accessToken); + expect(albums.length).toEqual(1); + const natureAlbum = albums[0]; + expect(natureAlbum.albumName).toEqual('nature'); + }); + + it('should upload to the specified album name', async () => { + await new Upload(CLI_BASE_OPTIONS).run([`${IMMICH_TEST_ASSET_PATH}/albums/nature/`], { + recursive: true, + albumName: 'testAlbum', + }); + + const albums = await api.albumApi.getAllAlbums(server, admin.accessToken); + expect(albums.length).toEqual(1); + const testAlbum = albums[0]; + expect(testAlbum.albumName).toEqual('testAlbum'); + }); });