mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
* Allow building and installing cli * feat: add format fix * docs: remove cli folder * feat: use immich scoped package * feat: rewrite cli readme * docs: add info on running without building * cleanup * chore: remove import functionality from cli * feat: add logout to cli * docs: add todo for file format from server * docs: add compilation step to cli * fix: success message spacing * feat: can create albums * fix: add check step to cli * fix: typos * feat: pull file formats from server * chore: use crawl service from server * chore: fix lint * docs: add cli documentation * chore: rename ignore pattern * chore: add version number to cli * feat: use sdk * fix: cleanup * feat: album name on windows * chore: remove skipped asset field * feat: add more info to server-info command * chore: cleanup * chore: remove unneeded packages * chore: fix docs links * feat: add cli v2 milestone * fix: set correct cli date --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
137 lines
4.3 KiB
TypeScript
137 lines
4.3 KiB
TypeScript
import { Asset } from '../cores/models/asset';
|
|
import { CrawlService } from '../services';
|
|
import { UploadOptionsDto } from '../cores/dto/upload-options-dto';
|
|
import { CrawlOptionsDto } from '../cores/dto/crawl-options-dto';
|
|
|
|
import cliProgress from 'cli-progress';
|
|
import byteSize from 'byte-size';
|
|
import { BaseCommand } from '../cli/base-command';
|
|
|
|
export default class Upload extends BaseCommand {
|
|
uploadLength!: number;
|
|
|
|
public async run(paths: string[], options: UploadOptionsDto): Promise<void> {
|
|
await this.connect();
|
|
|
|
const deviceId = 'CLI';
|
|
|
|
const formatResponse = await this.immichApi.serverInfoApi.getSupportedMediaTypes();
|
|
const crawlService = new CrawlService(formatResponse.data.image, formatResponse.data.video);
|
|
|
|
const crawlOptions = new CrawlOptionsDto();
|
|
crawlOptions.pathsToCrawl = paths;
|
|
crawlOptions.recursive = options.recursive;
|
|
crawlOptions.exclusionPatterns = options.exclusionPatterns;
|
|
|
|
const crawledFiles: string[] = await crawlService.crawl(crawlOptions);
|
|
|
|
if (crawledFiles.length === 0) {
|
|
console.log('No assets found, exiting');
|
|
return;
|
|
}
|
|
|
|
const assetsToUpload = crawledFiles.map((path) => new Asset(path, deviceId));
|
|
|
|
const uploadProgress = new cliProgress.SingleBar(
|
|
{
|
|
format: '{bar} | {percentage}% | ETA: {eta_formatted} | {value_formatted}/{total_formatted}: {filename}',
|
|
},
|
|
cliProgress.Presets.shades_classic,
|
|
);
|
|
|
|
let totalSize = 0;
|
|
let sizeSoFar = 0;
|
|
|
|
let totalSizeUploaded = 0;
|
|
let uploadCounter = 0;
|
|
|
|
for (const asset of assetsToUpload) {
|
|
// Compute total size first
|
|
await asset.process();
|
|
totalSize += asset.fileSize;
|
|
}
|
|
|
|
const existingAlbums = (await this.immichApi.albumApi.getAllAlbums()).data;
|
|
|
|
uploadProgress.start(totalSize, 0);
|
|
uploadProgress.update({ value_formatted: 0, total_formatted: byteSize(totalSize) });
|
|
|
|
try {
|
|
for (const asset of assetsToUpload) {
|
|
uploadProgress.update({
|
|
filename: asset.path,
|
|
});
|
|
|
|
let skipUpload = false;
|
|
if (!options.skipHash) {
|
|
const assetBulkUploadCheckDto = { assets: [{ id: asset.path, checksum: await asset.hash() }] };
|
|
|
|
const checkResponse = await this.immichApi.assetApi.checkBulkUpload({
|
|
assetBulkUploadCheckDto,
|
|
});
|
|
|
|
skipUpload = checkResponse.data.results[0].action === 'reject';
|
|
}
|
|
|
|
if (!skipUpload) {
|
|
if (!options.dryRun) {
|
|
const res = await this.immichApi.assetApi.uploadFile(asset.getUploadFileRequest());
|
|
|
|
if (options.album && asset.albumName) {
|
|
let album = existingAlbums.find((album) => album.albumName === asset.albumName);
|
|
if (!album) {
|
|
const res = await this.immichApi.albumApi.createAlbum({
|
|
createAlbumDto: { albumName: asset.albumName },
|
|
});
|
|
album = res.data;
|
|
existingAlbums.push(album);
|
|
}
|
|
|
|
await this.immichApi.albumApi.addAssetsToAlbum({ id: album.id, bulkIdsDto: { ids: [res.data.id] } });
|
|
}
|
|
}
|
|
|
|
totalSizeUploaded += asset.fileSize;
|
|
uploadCounter++;
|
|
}
|
|
|
|
sizeSoFar += asset.fileSize;
|
|
|
|
uploadProgress.update(sizeSoFar, { value_formatted: byteSize(sizeSoFar) });
|
|
}
|
|
} finally {
|
|
uploadProgress.stop();
|
|
}
|
|
|
|
let messageStart;
|
|
if (options.dryRun) {
|
|
messageStart = 'Would have';
|
|
} else {
|
|
messageStart = 'Successfully';
|
|
}
|
|
|
|
if (uploadCounter === 0) {
|
|
console.log('All assets were already uploaded, nothing to do.');
|
|
} else {
|
|
console.log(`${messageStart} uploaded ${uploadCounter} assets (${byteSize(totalSizeUploaded)})`);
|
|
}
|
|
if (options.delete) {
|
|
if (options.dryRun) {
|
|
console.log(`Would now have deleted assets, but skipped due to dry run`);
|
|
} else {
|
|
console.log('Deleting assets that have been uploaded...');
|
|
const deletionProgress = new cliProgress.SingleBar(cliProgress.Presets.shades_classic);
|
|
deletionProgress.start(crawledFiles.length, 0);
|
|
|
|
for (const asset of assetsToUpload) {
|
|
if (!options.dryRun) {
|
|
await asset.delete();
|
|
}
|
|
deletionProgress.increment();
|
|
}
|
|
deletionProgress.stop();
|
|
console.log('Deletion complete');
|
|
}
|
|
}
|
|
}
|
|
}
|