feat: manually trigger integrity jobs

feat: update summary after job runs
This commit is contained in:
izzy
2025-11-28 15:27:12 +00:00
parent 13e9cf0ed9
commit 2779fce7d0
10 changed files with 224 additions and 27 deletions

View File

@@ -345,6 +345,12 @@ export enum SourceType {
Manual = 'manual',
}
export enum IntegrityReportType {
OrphanFile = 'orphan_file',
MissingFile = 'missing_file',
ChecksumFail = 'checksum_mismatch',
}
export enum ManualJobName {
PersonCleanup = 'person-cleanup',
TagCleanup = 'tag-cleanup',
@@ -352,6 +358,12 @@ export enum ManualJobName {
MemoryCleanup = 'memory-cleanup',
MemoryCreate = 'memory-create',
BackupDatabase = 'backup-database',
IntegrityMissingFiles = `integrity-missing-files`,
IntegrityOrphanFiles = `integrity-orphan-files`,
IntegrityChecksumFiles = `integrity-checksum-mismatch`,
IntegrityMissingFilesRefresh = `integrity-missing-files-refresh`,
IntegrityOrphanFilesRefresh = `integrity-orphan-files-refresh`,
IntegrityChecksumFilesRefresh = `integrity-checksum-mismatch-refresh`,
}
export enum AssetPathType {
@@ -482,12 +494,6 @@ export enum CacheControl {
None = 'none',
}
export enum IntegrityReportType {
OrphanFile = 'orphan_file',
MissingFile = 'missing_file',
ChecksumFail = 'checksum_mismatch',
}
export enum ImmichEnvironment {
Development = 'development',
Testing = 'testing',

View File

@@ -19,7 +19,7 @@ import {
} from 'src/enum';
import { ArgOf } from 'src/repositories/event.repository';
import { BaseService } from 'src/services/base.service';
import { IIntegrityOrphanedFilesJob, IIntegrityPathWithReportJob } from 'src/types';
import { IIntegrityJob, IIntegrityOrphanedFilesJob, IIntegrityPathWithReportJob } from 'src/types';
import { handlePromiseError } from 'src/utils/misc';
async function* chunk<T>(generator: AsyncIterableIterator<T>, n: number) {
@@ -130,7 +130,7 @@ export class IntegrityService extends BaseService {
}
@OnJob({ name: JobName.IntegrityOrphanedFilesQueueAll, queue: QueueName.BackgroundTask })
async handleOrphanedFilesQueueAll(): Promise<JobStatus> {
async handleOrphanedFilesQueueAll({ refreshOnly }: IIntegrityJob = {}): Promise<JobStatus> {
this.logger.log(`Checking for out of date orphaned file reports...`);
const reports = this.assetJobRepository.streamIntegrityReports(IntegrityReportType.OrphanFile);
@@ -148,6 +148,11 @@ export class IntegrityService extends BaseService {
this.logger.log(`Queued report check of ${batchReports.length} report(s) (${total} so far)`);
}
if (refreshOnly) {
this.logger.log('Refresh complete.');
return JobStatus.Success;
}
this.logger.log(`Scanning for orphaned files...`);
const assetPaths = this.storageRepository.walk({
@@ -232,8 +237,8 @@ export class IntegrityService extends BaseService {
const results = await Promise.all(
paths.map(({ reportId, path }) =>
stat(path)
.then(() => reportId)
.catch(() => void 0),
.then(() => void 0)
.catch(() => reportId),
),
);
@@ -243,12 +248,18 @@ export class IntegrityService extends BaseService {
await this.integrityReportRepository.deleteByIds(reportIds);
}
this.logger.log(`Processed ${paths.length} and found ${reportIds.length} orphaned file(s).`);
this.logger.log(`Processed ${paths.length} paths and found ${reportIds.length} report(s) out of date.`);
return JobStatus.Success;
}
@OnJob({ name: JobName.IntegrityMissingFilesQueueAll, queue: QueueName.BackgroundTask })
async handleMissingFilesQueueAll(): Promise<JobStatus> {
async handleMissingFilesQueueAll({ refreshOnly }: IIntegrityJob = {}): Promise<JobStatus> {
if (refreshOnly) {
// TODO
this.logger.log('Refresh complete.');
return JobStatus.Success;
}
this.logger.log(`Scanning for missing files...`);
const assetPaths = this.assetJobRepository.streamAssetPaths();
@@ -304,7 +315,13 @@ export class IntegrityService extends BaseService {
}
@OnJob({ name: JobName.IntegrityChecksumFiles, queue: QueueName.BackgroundTask })
async handleChecksumFiles(): Promise<JobStatus> {
async handleChecksumFiles({ refreshOnly }: IIntegrityJob = {}): Promise<JobStatus> {
if (refreshOnly) {
// TODO
this.logger.log('Refresh complete.');
return JobStatus.Success;
}
const timeLimit = 60 * 60 * 1000; // 1000;
const percentageLimit = 1; // 0.25;

View File

@@ -34,6 +34,30 @@ const asJobItem = (dto: JobCreateDto): JobItem => {
return { name: JobName.DatabaseBackup };
}
case ManualJobName.IntegrityMissingFiles: {
return { name: JobName.IntegrityMissingFilesQueueAll };
}
case ManualJobName.IntegrityOrphanFiles: {
return { name: JobName.IntegrityOrphanedFilesQueueAll };
}
case ManualJobName.IntegrityChecksumFiles: {
return { name: JobName.IntegrityChecksumFiles };
}
case ManualJobName.IntegrityMissingFilesRefresh: {
return { name: JobName.IntegrityMissingFilesQueueAll, data: { refreshOnly: true } };
}
case ManualJobName.IntegrityOrphanFilesRefresh: {
return { name: JobName.IntegrityOrphanedFilesQueueAll, data: { refreshOnly: true } };
}
case ManualJobName.IntegrityChecksumFilesRefresh: {
return { name: JobName.IntegrityChecksumFiles, data: { refreshOnly: true } };
}
default: {
throw new BadRequestException('Invalid job name');
}

View File

@@ -282,6 +282,10 @@ export interface IWorkflowJob<T extends PluginTriggerType = PluginTriggerType> {
event: WorkflowData[T];
}
export interface IIntegrityJob {
refreshOnly?: boolean;
}
export interface IIntegrityOrphanedFilesJob {
type: 'asset' | 'asset_file';
paths: string[];
@@ -403,12 +407,12 @@ export type JobItem =
| { name: JobName.WorkflowRun; data: IWorkflowJob }
// Integrity
| { name: JobName.IntegrityOrphanedFilesQueueAll; data: IBaseJob }
| { name: JobName.IntegrityOrphanedFilesQueueAll; data?: IIntegrityJob }
| { name: JobName.IntegrityOrphanedFiles; data: IIntegrityOrphanedFilesJob }
| { name: JobName.IntegrityOrphanedCheckReports; data: IIntegrityPathWithReportJob }
| { name: JobName.IntegrityMissingFilesQueueAll; data: IBaseJob }
| { name: JobName.IntegrityMissingFilesQueueAll; data?: IIntegrityJob }
| { name: JobName.IntegrityMissingFiles; data: IIntegrityPathWithReportJob }
| { name: JobName.IntegrityChecksumFiles; data: IBaseJob };
| { name: JobName.IntegrityChecksumFiles; data?: IIntegrityJob };
export type VectorExtension = (typeof VECTOR_EXTENSIONS)[number];