mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat: add config options & cron entries for checks
This commit is contained in:
@@ -46,6 +46,22 @@ export interface SystemConfig {
|
||||
accelDecode: boolean;
|
||||
tonemap: ToneMapping;
|
||||
};
|
||||
integrityChecks: {
|
||||
missingFiles: {
|
||||
enabled: boolean;
|
||||
cronExpression: string;
|
||||
};
|
||||
orphanedFiles: {
|
||||
enabled: boolean;
|
||||
cronExpression: string;
|
||||
};
|
||||
checksumFiles: {
|
||||
enabled: boolean;
|
||||
cronExpression: string;
|
||||
timeLimit: number;
|
||||
percentageLimit: number;
|
||||
};
|
||||
};
|
||||
job: Record<ConcurrentQueueName, { concurrency: number }>;
|
||||
logging: {
|
||||
enabled: boolean;
|
||||
@@ -222,6 +238,22 @@ export const defaults = Object.freeze<SystemConfig>({
|
||||
accel: TranscodeHardwareAcceleration.Disabled,
|
||||
accelDecode: false,
|
||||
},
|
||||
integrityChecks: {
|
||||
missingFiles: {
|
||||
enabled: true,
|
||||
cronExpression: CronExpression.EVERY_DAY_AT_3AM,
|
||||
},
|
||||
orphanedFiles: {
|
||||
enabled: true,
|
||||
cronExpression: CronExpression.EVERY_DAY_AT_3AM,
|
||||
},
|
||||
checksumFiles: {
|
||||
enabled: true,
|
||||
cronExpression: CronExpression.EVERY_DAY_AT_3AM,
|
||||
timeLimit: 60 * 60 * 1000, // 1 hour
|
||||
percentageLimit: 1.0, // 100% of assets
|
||||
},
|
||||
},
|
||||
job: {
|
||||
[QueueName.BackgroundTask]: { concurrency: 5 },
|
||||
[QueueName.SmartSearch]: { concurrency: 2 },
|
||||
|
||||
@@ -38,6 +38,7 @@ const isOAuthEnabled = (config: SystemConfigOAuthDto) => config.enabled;
|
||||
const isOAuthOverrideEnabled = (config: SystemConfigOAuthDto) => config.mobileOverrideEnabled;
|
||||
const isEmailNotificationEnabled = (config: SystemConfigSmtpDto) => config.enabled;
|
||||
const isDatabaseBackupEnabled = (config: DatabaseBackupConfig) => config.enabled;
|
||||
const isEnabledProperty = (config: { enabled: boolean }) => config.enabled;
|
||||
|
||||
export class DatabaseBackupConfig {
|
||||
@ValidateBoolean()
|
||||
@@ -145,6 +146,42 @@ export class SystemConfigFFmpegDto {
|
||||
tonemap!: ToneMapping;
|
||||
}
|
||||
|
||||
class SystemConfigIntegrityJob {
|
||||
@ValidateBoolean()
|
||||
enabled!: boolean;
|
||||
|
||||
@ValidateIf(isEnabledProperty)
|
||||
@IsNotEmpty()
|
||||
@IsCronExpression()
|
||||
@IsString()
|
||||
cronExpression!: string;
|
||||
}
|
||||
|
||||
class SystemConfigIntegrityChecksumJob extends SystemConfigIntegrityJob {
|
||||
@IsInt()
|
||||
timeLimit!: number;
|
||||
|
||||
@IsNumber()
|
||||
percentageLimit!: number;
|
||||
}
|
||||
|
||||
class SystemConfigIntegrityChecks {
|
||||
@Type(() => SystemConfigIntegrityJob)
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
missingFiles!: SystemConfigIntegrityJob;
|
||||
|
||||
@Type(() => SystemConfigIntegrityJob)
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
orphanedFiles!: SystemConfigIntegrityJob;
|
||||
|
||||
@Type(() => SystemConfigIntegrityChecksumJob)
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
checksumFiles!: SystemConfigIntegrityChecksumJob;
|
||||
}
|
||||
|
||||
class JobSettingsDto {
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
@@ -649,6 +686,11 @@ export class SystemConfigDto implements SystemConfig {
|
||||
@IsObject()
|
||||
ffmpeg!: SystemConfigFFmpegDto;
|
||||
|
||||
@Type(() => SystemConfigIntegrityChecks)
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
integrityChecks!: SystemConfigIntegrityChecks;
|
||||
|
||||
@Type(() => SystemConfigLoggingDto)
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
|
||||
@@ -694,6 +694,7 @@ export enum DatabaseLock {
|
||||
GetSystemConfig = 69,
|
||||
BackupDatabase = 42,
|
||||
MemoryCreation = 777,
|
||||
IntegrityCheck = 67,
|
||||
}
|
||||
|
||||
export enum MaintenanceAction {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { JOBS_LIBRARY_PAGINATION_SIZE } from 'src/constants';
|
||||
import { StorageCore } from 'src/cores/storage.core';
|
||||
import { OnEvent, OnJob } from 'src/decorators';
|
||||
import {
|
||||
DatabaseLock,
|
||||
ImmichWorker,
|
||||
IntegrityReportType,
|
||||
JobName,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
import { ArgOf } from 'src/repositories/event.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { IIntegrityOrphanedFilesJob, IIntegrityPathWithReportJob } from 'src/types';
|
||||
import { handlePromiseError } from 'src/utils/misc';
|
||||
|
||||
async function* chunk<T>(generator: AsyncIterableIterator<T>, n: number) {
|
||||
let chunk: T[] = [];
|
||||
@@ -38,25 +40,49 @@ async function* chunk<T>(generator: AsyncIterableIterator<T>, n: number) {
|
||||
|
||||
@Injectable()
|
||||
export class IntegrityService extends BaseService {
|
||||
// private backupLock = false;
|
||||
private integrityLock = false;
|
||||
|
||||
@OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.Microservices] })
|
||||
async onConfigInit({
|
||||
newConfig: {
|
||||
backup: { database },
|
||||
integrityChecks: { orphanedFiles, missingFiles, checksumFiles },
|
||||
},
|
||||
}: ArgOf<'ConfigInit'>) {
|
||||
// this.backupLock = await this.databaseRepository.tryLock(DatabaseLock.BackupDatabase);
|
||||
// if (this.backupLock) {
|
||||
// this.cronRepository.create({
|
||||
// name: 'backupDatabase',
|
||||
// expression: database.cronExpression,
|
||||
// onTick: () => handlePromiseError(this.jobRepository.queue({ name: JobName.DatabaseBackup }), this.logger),
|
||||
// start: database.enabled,
|
||||
// });
|
||||
// }
|
||||
this.integrityLock = await this.databaseRepository.tryLock(DatabaseLock.IntegrityCheck);
|
||||
if (this.integrityLock) {
|
||||
this.cronRepository.create({
|
||||
name: 'integrityOrphanedFiles',
|
||||
expression: orphanedFiles.cronExpression,
|
||||
onTick: () =>
|
||||
handlePromiseError(
|
||||
this.jobRepository.queue({ name: JobName.IntegrityOrphanedFilesQueueAll, data: {} }),
|
||||
this.logger,
|
||||
),
|
||||
start: orphanedFiles.enabled,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.cronRepository.create({
|
||||
name: 'integrityMissingFiles',
|
||||
expression: missingFiles.cronExpression,
|
||||
onTick: () =>
|
||||
handlePromiseError(
|
||||
this.jobRepository.queue({ name: JobName.IntegrityMissingFilesQueueAll, data: {} }),
|
||||
this.logger,
|
||||
),
|
||||
start: missingFiles.enabled,
|
||||
});
|
||||
|
||||
this.cronRepository.create({
|
||||
name: 'integrityChecksumFiles',
|
||||
expression: checksumFiles.cronExpression,
|
||||
onTick: () =>
|
||||
handlePromiseError(this.jobRepository.queue({ name: JobName.IntegrityChecksumFiles, data: {} }), this.logger),
|
||||
start: checksumFiles.enabled,
|
||||
});
|
||||
}
|
||||
|
||||
// debug: run on boot
|
||||
setImmediate(() => {
|
||||
this.jobRepository.queue({
|
||||
name: JobName.IntegrityOrphanedFilesQueueAll,
|
||||
data: {},
|
||||
@@ -71,19 +97,36 @@ export class IntegrityService extends BaseService {
|
||||
name: JobName.IntegrityChecksumFiles,
|
||||
data: {},
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
@OnEvent({ name: 'ConfigUpdate', server: true })
|
||||
async onConfigUpdate({ newConfig: { backup } }: ArgOf<'ConfigUpdate'>) {
|
||||
// if (!this.backupLock) {
|
||||
// return;
|
||||
// }
|
||||
// this.cronRepository.update({
|
||||
// name: 'backupDatabase',
|
||||
// expression: backup.database.cronExpression,
|
||||
// start: backup.database.enabled,
|
||||
// });
|
||||
async onConfigUpdate({
|
||||
newConfig: {
|
||||
integrityChecks: { orphanedFiles, missingFiles, checksumFiles },
|
||||
},
|
||||
}: ArgOf<'ConfigUpdate'>) {
|
||||
if (!this.integrityLock) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cronRepository.update({
|
||||
name: 'integrityOrphanedFiles',
|
||||
expression: orphanedFiles.cronExpression,
|
||||
start: orphanedFiles.enabled,
|
||||
});
|
||||
|
||||
this.cronRepository.update({
|
||||
name: 'integrityMissingFiles',
|
||||
expression: missingFiles.cronExpression,
|
||||
start: missingFiles.enabled,
|
||||
});
|
||||
|
||||
this.cronRepository.update({
|
||||
name: 'integrityChecksumFiles',
|
||||
expression: checksumFiles.cronExpression,
|
||||
start: checksumFiles.enabled,
|
||||
});
|
||||
}
|
||||
|
||||
@OnJob({ name: JobName.IntegrityOrphanedFilesQueueAll, queue: QueueName.BackgroundTask })
|
||||
|
||||
Reference in New Issue
Block a user