diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 268c4849c5..391c7c3759 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -569,6 +569,9 @@ Class | Method | HTTP request | Description - [SystemConfigGeneratedFullsizeImageDto](doc//SystemConfigGeneratedFullsizeImageDto.md) - [SystemConfigGeneratedImageDto](doc//SystemConfigGeneratedImageDto.md) - [SystemConfigImageDto](doc//SystemConfigImageDto.md) + - [SystemConfigIntegrityChecks](doc//SystemConfigIntegrityChecks.md) + - [SystemConfigIntegrityChecksumJob](doc//SystemConfigIntegrityChecksumJob.md) + - [SystemConfigIntegrityJob](doc//SystemConfigIntegrityJob.md) - [SystemConfigJobDto](doc//SystemConfigJobDto.md) - [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md) - [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 21730074aa..641000daf7 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -321,6 +321,9 @@ part 'model/system_config_faces_dto.dart'; part 'model/system_config_generated_fullsize_image_dto.dart'; part 'model/system_config_generated_image_dto.dart'; part 'model/system_config_image_dto.dart'; +part 'model/system_config_integrity_checks.dart'; +part 'model/system_config_integrity_checksum_job.dart'; +part 'model/system_config_integrity_job.dart'; part 'model/system_config_job_dto.dart'; part 'model/system_config_library_dto.dart'; part 'model/system_config_library_scan_dto.dart'; diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 041be67015..ae2fa85a93 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -690,6 +690,12 @@ class ApiClient { return SystemConfigGeneratedImageDto.fromJson(value); case 'SystemConfigImageDto': return SystemConfigImageDto.fromJson(value); + case 'SystemConfigIntegrityChecks': + return SystemConfigIntegrityChecks.fromJson(value); + case 'SystemConfigIntegrityChecksumJob': + return SystemConfigIntegrityChecksumJob.fromJson(value); + case 'SystemConfigIntegrityJob': + return SystemConfigIntegrityJob.fromJson(value); case 'SystemConfigJobDto': return SystemConfigJobDto.fromJson(value); case 'SystemConfigLibraryDto': diff --git a/mobile/openapi/lib/model/system_config_dto.dart b/mobile/openapi/lib/model/system_config_dto.dart index 38dbb30f0c..3a5e15b030 100644 --- a/mobile/openapi/lib/model/system_config_dto.dart +++ b/mobile/openapi/lib/model/system_config_dto.dart @@ -16,6 +16,7 @@ class SystemConfigDto { required this.backup, required this.ffmpeg, required this.image, + required this.integrityChecks, required this.job, required this.library_, required this.logging, @@ -42,6 +43,8 @@ class SystemConfigDto { SystemConfigImageDto image; + SystemConfigIntegrityChecks integrityChecks; + SystemConfigJobDto job; SystemConfigLibraryDto library_; @@ -83,6 +86,7 @@ class SystemConfigDto { other.backup == backup && other.ffmpeg == ffmpeg && other.image == image && + other.integrityChecks == integrityChecks && other.job == job && other.library_ == library_ && other.logging == logging && @@ -108,6 +112,7 @@ class SystemConfigDto { (backup.hashCode) + (ffmpeg.hashCode) + (image.hashCode) + + (integrityChecks.hashCode) + (job.hashCode) + (library_.hashCode) + (logging.hashCode) + @@ -128,13 +133,14 @@ class SystemConfigDto { (user.hashCode); @override - String toString() => 'SystemConfigDto[backup=$backup, ffmpeg=$ffmpeg, image=$image, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, metadata=$metadata, newVersionCheck=$newVersionCheck, nightlyTasks=$nightlyTasks, notifications=$notifications, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, templates=$templates, theme=$theme, trash=$trash, user=$user]'; + String toString() => 'SystemConfigDto[backup=$backup, ffmpeg=$ffmpeg, image=$image, integrityChecks=$integrityChecks, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, metadata=$metadata, newVersionCheck=$newVersionCheck, nightlyTasks=$nightlyTasks, notifications=$notifications, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, templates=$templates, theme=$theme, trash=$trash, user=$user]'; Map toJson() { final json = {}; json[r'backup'] = this.backup; json[r'ffmpeg'] = this.ffmpeg; json[r'image'] = this.image; + json[r'integrityChecks'] = this.integrityChecks; json[r'job'] = this.job; json[r'library'] = this.library_; json[r'logging'] = this.logging; @@ -168,6 +174,7 @@ class SystemConfigDto { backup: SystemConfigBackupsDto.fromJson(json[r'backup'])!, ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!, image: SystemConfigImageDto.fromJson(json[r'image'])!, + integrityChecks: SystemConfigIntegrityChecks.fromJson(json[r'integrityChecks'])!, job: SystemConfigJobDto.fromJson(json[r'job'])!, library_: SystemConfigLibraryDto.fromJson(json[r'library'])!, logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!, @@ -236,6 +243,7 @@ class SystemConfigDto { 'backup', 'ffmpeg', 'image', + 'integrityChecks', 'job', 'library', 'logging', diff --git a/mobile/openapi/lib/model/system_config_integrity_checks.dart b/mobile/openapi/lib/model/system_config_integrity_checks.dart new file mode 100644 index 0000000000..37a75ed3d7 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_integrity_checks.dart @@ -0,0 +1,115 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigIntegrityChecks { + /// Returns a new [SystemConfigIntegrityChecks] instance. + SystemConfigIntegrityChecks({ + required this.checksumFiles, + required this.missingFiles, + required this.orphanedFiles, + }); + + SystemConfigIntegrityChecksumJob checksumFiles; + + SystemConfigIntegrityJob missingFiles; + + SystemConfigIntegrityJob orphanedFiles; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigIntegrityChecks && + other.checksumFiles == checksumFiles && + other.missingFiles == missingFiles && + other.orphanedFiles == orphanedFiles; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (checksumFiles.hashCode) + + (missingFiles.hashCode) + + (orphanedFiles.hashCode); + + @override + String toString() => 'SystemConfigIntegrityChecks[checksumFiles=$checksumFiles, missingFiles=$missingFiles, orphanedFiles=$orphanedFiles]'; + + Map toJson() { + final json = {}; + json[r'checksumFiles'] = this.checksumFiles; + json[r'missingFiles'] = this.missingFiles; + json[r'orphanedFiles'] = this.orphanedFiles; + return json; + } + + /// Returns a new [SystemConfigIntegrityChecks] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigIntegrityChecks? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigIntegrityChecks"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigIntegrityChecks( + checksumFiles: SystemConfigIntegrityChecksumJob.fromJson(json[r'checksumFiles'])!, + missingFiles: SystemConfigIntegrityJob.fromJson(json[r'missingFiles'])!, + orphanedFiles: SystemConfigIntegrityJob.fromJson(json[r'orphanedFiles'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigIntegrityChecks.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigIntegrityChecks.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigIntegrityChecks-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigIntegrityChecks.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checksumFiles', + 'missingFiles', + 'orphanedFiles', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_integrity_checksum_job.dart b/mobile/openapi/lib/model/system_config_integrity_checksum_job.dart new file mode 100644 index 0000000000..16b64bf2b9 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_integrity_checksum_job.dart @@ -0,0 +1,123 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigIntegrityChecksumJob { + /// Returns a new [SystemConfigIntegrityChecksumJob] instance. + SystemConfigIntegrityChecksumJob({ + required this.cronExpression, + required this.enabled, + required this.percentageLimit, + required this.timeLimit, + }); + + String cronExpression; + + bool enabled; + + num percentageLimit; + + num timeLimit; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigIntegrityChecksumJob && + other.cronExpression == cronExpression && + other.enabled == enabled && + other.percentageLimit == percentageLimit && + other.timeLimit == timeLimit; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (cronExpression.hashCode) + + (enabled.hashCode) + + (percentageLimit.hashCode) + + (timeLimit.hashCode); + + @override + String toString() => 'SystemConfigIntegrityChecksumJob[cronExpression=$cronExpression, enabled=$enabled, percentageLimit=$percentageLimit, timeLimit=$timeLimit]'; + + Map toJson() { + final json = {}; + json[r'cronExpression'] = this.cronExpression; + json[r'enabled'] = this.enabled; + json[r'percentageLimit'] = this.percentageLimit; + json[r'timeLimit'] = this.timeLimit; + return json; + } + + /// Returns a new [SystemConfigIntegrityChecksumJob] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigIntegrityChecksumJob? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigIntegrityChecksumJob"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigIntegrityChecksumJob( + cronExpression: mapValueOfType(json, r'cronExpression')!, + enabled: mapValueOfType(json, r'enabled')!, + percentageLimit: num.parse('${json[r'percentageLimit']}'), + timeLimit: num.parse('${json[r'timeLimit']}'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigIntegrityChecksumJob.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigIntegrityChecksumJob.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigIntegrityChecksumJob-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigIntegrityChecksumJob.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'cronExpression', + 'enabled', + 'percentageLimit', + 'timeLimit', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_integrity_job.dart b/mobile/openapi/lib/model/system_config_integrity_job.dart new file mode 100644 index 0000000000..08eb559c7f --- /dev/null +++ b/mobile/openapi/lib/model/system_config_integrity_job.dart @@ -0,0 +1,107 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigIntegrityJob { + /// Returns a new [SystemConfigIntegrityJob] instance. + SystemConfigIntegrityJob({ + required this.cronExpression, + required this.enabled, + }); + + String cronExpression; + + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigIntegrityJob && + other.cronExpression == cronExpression && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (cronExpression.hashCode) + + (enabled.hashCode); + + @override + String toString() => 'SystemConfigIntegrityJob[cronExpression=$cronExpression, enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'cronExpression'] = this.cronExpression; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [SystemConfigIntegrityJob] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigIntegrityJob? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigIntegrityJob"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigIntegrityJob( + cronExpression: mapValueOfType(json, r'cronExpression')!, + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigIntegrityJob.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigIntegrityJob.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigIntegrityJob-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigIntegrityJob.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'cronExpression', + 'enabled', + }; +} + diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 9fe4664352..f1a61bc4ea 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -21237,6 +21237,9 @@ "image": { "$ref": "#/components/schemas/SystemConfigImageDto" }, + "integrityChecks": { + "$ref": "#/components/schemas/SystemConfigIntegrityChecks" + }, "job": { "$ref": "#/components/schemas/SystemConfigJobDto" }, @@ -21296,6 +21299,7 @@ "backup", "ffmpeg", "image", + "integrityChecks", "job", "library", "logging", @@ -21542,6 +21546,63 @@ ], "type": "object" }, + "SystemConfigIntegrityChecks": { + "properties": { + "checksumFiles": { + "$ref": "#/components/schemas/SystemConfigIntegrityChecksumJob" + }, + "missingFiles": { + "$ref": "#/components/schemas/SystemConfigIntegrityJob" + }, + "orphanedFiles": { + "$ref": "#/components/schemas/SystemConfigIntegrityJob" + } + }, + "required": [ + "checksumFiles", + "missingFiles", + "orphanedFiles" + ], + "type": "object" + }, + "SystemConfigIntegrityChecksumJob": { + "properties": { + "cronExpression": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "percentageLimit": { + "type": "number" + }, + "timeLimit": { + "type": "number" + } + }, + "required": [ + "cronExpression", + "enabled", + "percentageLimit", + "timeLimit" + ], + "type": "object" + }, + "SystemConfigIntegrityJob": { + "properties": { + "cronExpression": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + }, + "required": [ + "cronExpression", + "enabled" + ], + "type": "object" + }, "SystemConfigJobDto": { "properties": { "backgroundTask": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 3fe3d3a51c..b33276a90b 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -1454,6 +1454,21 @@ export type SystemConfigImageDto = { preview: SystemConfigGeneratedImageDto; thumbnail: SystemConfigGeneratedImageDto; }; +export type SystemConfigIntegrityChecksumJob = { + cronExpression: string; + enabled: boolean; + percentageLimit: number; + timeLimit: number; +}; +export type SystemConfigIntegrityJob = { + cronExpression: string; + enabled: boolean; +}; +export type SystemConfigIntegrityChecks = { + checksumFiles: SystemConfigIntegrityChecksumJob; + missingFiles: SystemConfigIntegrityJob; + orphanedFiles: SystemConfigIntegrityJob; +}; export type JobSettingsDto = { concurrency: number; }; @@ -1606,6 +1621,7 @@ export type SystemConfigDto = { backup: SystemConfigBackupsDto; ffmpeg: SystemConfigFFmpegDto; image: SystemConfigImageDto; + integrityChecks: SystemConfigIntegrityChecks; job: SystemConfigJobDto; library: SystemConfigLibraryDto; logging: SystemConfigLoggingDto; diff --git a/server/src/config.ts b/server/src/config.ts index c18acd79f8..bd3c745daf 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -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; logging: { enabled: boolean; @@ -222,6 +238,22 @@ export const defaults = Object.freeze({ 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 }, diff --git a/server/src/dtos/system-config.dto.ts b/server/src/dtos/system-config.dto.ts index c835073c31..5701f7eeba 100644 --- a/server/src/dtos/system-config.dto.ts +++ b/server/src/dtos/system-config.dto.ts @@ -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() diff --git a/server/src/enum.ts b/server/src/enum.ts index 40f7f45495..0ee2765741 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -694,6 +694,7 @@ export enum DatabaseLock { GetSystemConfig = 69, BackupDatabase = 42, MemoryCreation = 777, + IntegrityCheck = 67, } export enum MaintenanceAction { diff --git a/server/src/services/integrity.service.ts b/server/src/services/integrity.service.ts index 924957f52a..b876004385 100644 --- a/server/src/services/integrity.service.ts +++ b/server/src/services/integrity.service.ts @@ -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(generator: AsyncIterableIterator, n: number) { let chunk: T[] = []; @@ -38,25 +40,49 @@ async function* chunk(generator: AsyncIterableIterator, 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 })