import { BaseCommand } from '../cli/base-command'; import { CrawledAsset } from '../cores/models/crawled-asset'; import { CrawlService, UploadService } from '../services'; import * as si from 'systeminformation'; import FormData from 'form-data'; 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'; export default class Upload extends BaseCommand { private crawlService = new CrawlService(); private uploadService!: UploadService; deviceId!: string; uploadLength!: number; dryRun = false; public async run(paths: string[], options: UploadOptionsDto): Promise { await this.connect(); const uuid = await si.uuid(); this.deviceId = uuid.os || 'CLI'; this.uploadService = new UploadService(this.immichApi.apiConfiguration); this.dryRun = options.dryRun; const crawlOptions = new CrawlOptionsDto(); crawlOptions.pathsToCrawl = paths; crawlOptions.recursive = options.recursive; crawlOptions.excludePatterns = options.excludePatterns; const crawledFiles: string[] = await this.crawlService.crawl(crawlOptions); if (crawledFiles.length === 0) { console.log('No assets found, exiting'); return; } const assetsToUpload = crawledFiles.map((path) => new CrawledAsset(path)); 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; } uploadProgress.start(totalSize, 0); uploadProgress.update({ value_formatted: 0, total_formatted: byteSize(totalSize) }); for (const asset of assetsToUpload) { uploadProgress.update({ filename: asset.path, }); try { if (options.import) { const importData = { assetPath: asset.path, deviceAssetId: asset.deviceAssetId, assetType: asset.assetType, deviceId: this.deviceId, fileCreatedAt: asset.fileCreatedAt, fileModifiedAt: asset.fileModifiedAt, isFavorite: false, }; if (!this.dryRun) { await this.uploadService.import(importData); } } else { await this.uploadAsset(asset, options.skipHash); } } catch (error) { uploadProgress.stop(); throw error; } sizeSoFar += asset.fileSize; if (!asset.skipped) { totalSizeUploaded += asset.fileSize; uploadCounter++; } uploadProgress.update(sizeSoFar, { value_formatted: byteSize(sizeSoFar) }); } uploadProgress.stop(); let messageStart; if (this.dryRun) { messageStart = 'Would have '; } else { messageStart = 'Successfully '; } if (options.import) { console.log(`${messageStart} imported ${uploadCounter} assets (${byteSize(totalSizeUploaded)})`); } else { 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 (this.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 (!this.dryRun) { await asset.delete(); } deletionProgress.increment(); } deletionProgress.stop(); console.log('Deletion complete'); } } } } private async uploadAsset(asset: CrawledAsset, skipHash = false) { await asset.readData(); let skipUpload = false; if (!skipHash) { const checksum = await asset.hash(); const checkResponse = await this.uploadService.checkIfAssetAlreadyExists(asset.path, checksum); skipUpload = checkResponse.data.results[0].action === 'reject'; } if (skipUpload) { asset.skipped = true; } else { const uploadFormData = new FormData(); uploadFormData.append('deviceAssetId', asset.deviceAssetId); uploadFormData.append('deviceId', this.deviceId); uploadFormData.append('fileCreatedAt', asset.fileCreatedAt); uploadFormData.append('fileModifiedAt', asset.fileModifiedAt); uploadFormData.append('isFavorite', String(false)); uploadFormData.append('fileExtension', asset.fileExtension); uploadFormData.append('assetType', asset.assetType); uploadFormData.append('assetData', asset.assetData, { filename: asset.path }); if (asset.sidecarData) { uploadFormData.append('sidecarData', asset.sidecarData, { filename: asset.sidecarPath, contentType: 'application/xml', }); } if (!this.dryRun) { await this.uploadService.upload(uploadFormData); } } } }