feat: add config options & cron entries for checks

This commit is contained in:
izzy
2025-11-27 16:05:26 +00:00
parent 1744237aeb
commit 93860238af
13 changed files with 583 additions and 23 deletions

View File

@@ -569,6 +569,9 @@ Class | Method | HTTP request | Description
- [SystemConfigGeneratedFullsizeImageDto](doc//SystemConfigGeneratedFullsizeImageDto.md) - [SystemConfigGeneratedFullsizeImageDto](doc//SystemConfigGeneratedFullsizeImageDto.md)
- [SystemConfigGeneratedImageDto](doc//SystemConfigGeneratedImageDto.md) - [SystemConfigGeneratedImageDto](doc//SystemConfigGeneratedImageDto.md)
- [SystemConfigImageDto](doc//SystemConfigImageDto.md) - [SystemConfigImageDto](doc//SystemConfigImageDto.md)
- [SystemConfigIntegrityChecks](doc//SystemConfigIntegrityChecks.md)
- [SystemConfigIntegrityChecksumJob](doc//SystemConfigIntegrityChecksumJob.md)
- [SystemConfigIntegrityJob](doc//SystemConfigIntegrityJob.md)
- [SystemConfigJobDto](doc//SystemConfigJobDto.md) - [SystemConfigJobDto](doc//SystemConfigJobDto.md)
- [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md) - [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md)
- [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md) - [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md)

View File

@@ -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_fullsize_image_dto.dart';
part 'model/system_config_generated_image_dto.dart'; part 'model/system_config_generated_image_dto.dart';
part 'model/system_config_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_job_dto.dart';
part 'model/system_config_library_dto.dart'; part 'model/system_config_library_dto.dart';
part 'model/system_config_library_scan_dto.dart'; part 'model/system_config_library_scan_dto.dart';

View File

@@ -690,6 +690,12 @@ class ApiClient {
return SystemConfigGeneratedImageDto.fromJson(value); return SystemConfigGeneratedImageDto.fromJson(value);
case 'SystemConfigImageDto': case 'SystemConfigImageDto':
return SystemConfigImageDto.fromJson(value); return SystemConfigImageDto.fromJson(value);
case 'SystemConfigIntegrityChecks':
return SystemConfigIntegrityChecks.fromJson(value);
case 'SystemConfigIntegrityChecksumJob':
return SystemConfigIntegrityChecksumJob.fromJson(value);
case 'SystemConfigIntegrityJob':
return SystemConfigIntegrityJob.fromJson(value);
case 'SystemConfigJobDto': case 'SystemConfigJobDto':
return SystemConfigJobDto.fromJson(value); return SystemConfigJobDto.fromJson(value);
case 'SystemConfigLibraryDto': case 'SystemConfigLibraryDto':

View File

@@ -16,6 +16,7 @@ class SystemConfigDto {
required this.backup, required this.backup,
required this.ffmpeg, required this.ffmpeg,
required this.image, required this.image,
required this.integrityChecks,
required this.job, required this.job,
required this.library_, required this.library_,
required this.logging, required this.logging,
@@ -42,6 +43,8 @@ class SystemConfigDto {
SystemConfigImageDto image; SystemConfigImageDto image;
SystemConfigIntegrityChecks integrityChecks;
SystemConfigJobDto job; SystemConfigJobDto job;
SystemConfigLibraryDto library_; SystemConfigLibraryDto library_;
@@ -83,6 +86,7 @@ class SystemConfigDto {
other.backup == backup && other.backup == backup &&
other.ffmpeg == ffmpeg && other.ffmpeg == ffmpeg &&
other.image == image && other.image == image &&
other.integrityChecks == integrityChecks &&
other.job == job && other.job == job &&
other.library_ == library_ && other.library_ == library_ &&
other.logging == logging && other.logging == logging &&
@@ -108,6 +112,7 @@ class SystemConfigDto {
(backup.hashCode) + (backup.hashCode) +
(ffmpeg.hashCode) + (ffmpeg.hashCode) +
(image.hashCode) + (image.hashCode) +
(integrityChecks.hashCode) +
(job.hashCode) + (job.hashCode) +
(library_.hashCode) + (library_.hashCode) +
(logging.hashCode) + (logging.hashCode) +
@@ -128,13 +133,14 @@ class SystemConfigDto {
(user.hashCode); (user.hashCode);
@override @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<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
json[r'backup'] = this.backup; json[r'backup'] = this.backup;
json[r'ffmpeg'] = this.ffmpeg; json[r'ffmpeg'] = this.ffmpeg;
json[r'image'] = this.image; json[r'image'] = this.image;
json[r'integrityChecks'] = this.integrityChecks;
json[r'job'] = this.job; json[r'job'] = this.job;
json[r'library'] = this.library_; json[r'library'] = this.library_;
json[r'logging'] = this.logging; json[r'logging'] = this.logging;
@@ -168,6 +174,7 @@ class SystemConfigDto {
backup: SystemConfigBackupsDto.fromJson(json[r'backup'])!, backup: SystemConfigBackupsDto.fromJson(json[r'backup'])!,
ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!, ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!,
image: SystemConfigImageDto.fromJson(json[r'image'])!, image: SystemConfigImageDto.fromJson(json[r'image'])!,
integrityChecks: SystemConfigIntegrityChecks.fromJson(json[r'integrityChecks'])!,
job: SystemConfigJobDto.fromJson(json[r'job'])!, job: SystemConfigJobDto.fromJson(json[r'job'])!,
library_: SystemConfigLibraryDto.fromJson(json[r'library'])!, library_: SystemConfigLibraryDto.fromJson(json[r'library'])!,
logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!, logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!,
@@ -236,6 +243,7 @@ class SystemConfigDto {
'backup', 'backup',
'ffmpeg', 'ffmpeg',
'image', 'image',
'integrityChecks',
'job', 'job',
'library', 'library',
'logging', 'logging',

View File

@@ -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<String, dynamic> toJson() {
final json = <String, dynamic>{};
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<String, dynamic>();
return SystemConfigIntegrityChecks(
checksumFiles: SystemConfigIntegrityChecksumJob.fromJson(json[r'checksumFiles'])!,
missingFiles: SystemConfigIntegrityJob.fromJson(json[r'missingFiles'])!,
orphanedFiles: SystemConfigIntegrityJob.fromJson(json[r'orphanedFiles'])!,
);
}
return null;
}
static List<SystemConfigIntegrityChecks> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigIntegrityChecks>[];
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<String, SystemConfigIntegrityChecks> mapFromJson(dynamic json) {
final map = <String, SystemConfigIntegrityChecks>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // 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<String, List<SystemConfigIntegrityChecks>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigIntegrityChecks>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
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 = <String>{
'checksumFiles',
'missingFiles',
'orphanedFiles',
};
}

View File

@@ -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<String, dynamic> toJson() {
final json = <String, dynamic>{};
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<String, dynamic>();
return SystemConfigIntegrityChecksumJob(
cronExpression: mapValueOfType<String>(json, r'cronExpression')!,
enabled: mapValueOfType<bool>(json, r'enabled')!,
percentageLimit: num.parse('${json[r'percentageLimit']}'),
timeLimit: num.parse('${json[r'timeLimit']}'),
);
}
return null;
}
static List<SystemConfigIntegrityChecksumJob> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigIntegrityChecksumJob>[];
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<String, SystemConfigIntegrityChecksumJob> mapFromJson(dynamic json) {
final map = <String, SystemConfigIntegrityChecksumJob>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // 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<String, List<SystemConfigIntegrityChecksumJob>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigIntegrityChecksumJob>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
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 = <String>{
'cronExpression',
'enabled',
'percentageLimit',
'timeLimit',
};
}

View File

@@ -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<String, dynamic> toJson() {
final json = <String, dynamic>{};
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<String, dynamic>();
return SystemConfigIntegrityJob(
cronExpression: mapValueOfType<String>(json, r'cronExpression')!,
enabled: mapValueOfType<bool>(json, r'enabled')!,
);
}
return null;
}
static List<SystemConfigIntegrityJob> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SystemConfigIntegrityJob>[];
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<String, SystemConfigIntegrityJob> mapFromJson(dynamic json) {
final map = <String, SystemConfigIntegrityJob>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // 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<String, List<SystemConfigIntegrityJob>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SystemConfigIntegrityJob>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
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 = <String>{
'cronExpression',
'enabled',
};
}

View File

@@ -21237,6 +21237,9 @@
"image": { "image": {
"$ref": "#/components/schemas/SystemConfigImageDto" "$ref": "#/components/schemas/SystemConfigImageDto"
}, },
"integrityChecks": {
"$ref": "#/components/schemas/SystemConfigIntegrityChecks"
},
"job": { "job": {
"$ref": "#/components/schemas/SystemConfigJobDto" "$ref": "#/components/schemas/SystemConfigJobDto"
}, },
@@ -21296,6 +21299,7 @@
"backup", "backup",
"ffmpeg", "ffmpeg",
"image", "image",
"integrityChecks",
"job", "job",
"library", "library",
"logging", "logging",
@@ -21542,6 +21546,63 @@
], ],
"type": "object" "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": { "SystemConfigJobDto": {
"properties": { "properties": {
"backgroundTask": { "backgroundTask": {

View File

@@ -1454,6 +1454,21 @@ export type SystemConfigImageDto = {
preview: SystemConfigGeneratedImageDto; preview: SystemConfigGeneratedImageDto;
thumbnail: 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 = { export type JobSettingsDto = {
concurrency: number; concurrency: number;
}; };
@@ -1606,6 +1621,7 @@ export type SystemConfigDto = {
backup: SystemConfigBackupsDto; backup: SystemConfigBackupsDto;
ffmpeg: SystemConfigFFmpegDto; ffmpeg: SystemConfigFFmpegDto;
image: SystemConfigImageDto; image: SystemConfigImageDto;
integrityChecks: SystemConfigIntegrityChecks;
job: SystemConfigJobDto; job: SystemConfigJobDto;
library: SystemConfigLibraryDto; library: SystemConfigLibraryDto;
logging: SystemConfigLoggingDto; logging: SystemConfigLoggingDto;

View File

@@ -46,6 +46,22 @@ export interface SystemConfig {
accelDecode: boolean; accelDecode: boolean;
tonemap: ToneMapping; 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 }>; job: Record<ConcurrentQueueName, { concurrency: number }>;
logging: { logging: {
enabled: boolean; enabled: boolean;
@@ -222,6 +238,22 @@ export const defaults = Object.freeze<SystemConfig>({
accel: TranscodeHardwareAcceleration.Disabled, accel: TranscodeHardwareAcceleration.Disabled,
accelDecode: false, 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: { job: {
[QueueName.BackgroundTask]: { concurrency: 5 }, [QueueName.BackgroundTask]: { concurrency: 5 },
[QueueName.SmartSearch]: { concurrency: 2 }, [QueueName.SmartSearch]: { concurrency: 2 },

View File

@@ -38,6 +38,7 @@ const isOAuthEnabled = (config: SystemConfigOAuthDto) => config.enabled;
const isOAuthOverrideEnabled = (config: SystemConfigOAuthDto) => config.mobileOverrideEnabled; const isOAuthOverrideEnabled = (config: SystemConfigOAuthDto) => config.mobileOverrideEnabled;
const isEmailNotificationEnabled = (config: SystemConfigSmtpDto) => config.enabled; const isEmailNotificationEnabled = (config: SystemConfigSmtpDto) => config.enabled;
const isDatabaseBackupEnabled = (config: DatabaseBackupConfig) => config.enabled; const isDatabaseBackupEnabled = (config: DatabaseBackupConfig) => config.enabled;
const isEnabledProperty = (config: { enabled: boolean }) => config.enabled;
export class DatabaseBackupConfig { export class DatabaseBackupConfig {
@ValidateBoolean() @ValidateBoolean()
@@ -145,6 +146,42 @@ export class SystemConfigFFmpegDto {
tonemap!: ToneMapping; 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 { class JobSettingsDto {
@IsInt() @IsInt()
@IsPositive() @IsPositive()
@@ -649,6 +686,11 @@ export class SystemConfigDto implements SystemConfig {
@IsObject() @IsObject()
ffmpeg!: SystemConfigFFmpegDto; ffmpeg!: SystemConfigFFmpegDto;
@Type(() => SystemConfigIntegrityChecks)
@ValidateNested()
@IsObject()
integrityChecks!: SystemConfigIntegrityChecks;
@Type(() => SystemConfigLoggingDto) @Type(() => SystemConfigLoggingDto)
@ValidateNested() @ValidateNested()
@IsObject() @IsObject()

View File

@@ -694,6 +694,7 @@ export enum DatabaseLock {
GetSystemConfig = 69, GetSystemConfig = 69,
BackupDatabase = 42, BackupDatabase = 42,
MemoryCreation = 777, MemoryCreation = 777,
IntegrityCheck = 67,
} }
export enum MaintenanceAction { export enum MaintenanceAction {

View File

@@ -8,6 +8,7 @@ import { JOBS_LIBRARY_PAGINATION_SIZE } from 'src/constants';
import { StorageCore } from 'src/cores/storage.core'; import { StorageCore } from 'src/cores/storage.core';
import { OnEvent, OnJob } from 'src/decorators'; import { OnEvent, OnJob } from 'src/decorators';
import { import {
DatabaseLock,
ImmichWorker, ImmichWorker,
IntegrityReportType, IntegrityReportType,
JobName, JobName,
@@ -19,6 +20,7 @@ import {
import { ArgOf } from 'src/repositories/event.repository'; import { ArgOf } from 'src/repositories/event.repository';
import { BaseService } from 'src/services/base.service'; import { BaseService } from 'src/services/base.service';
import { IIntegrityOrphanedFilesJob, IIntegrityPathWithReportJob } from 'src/types'; import { IIntegrityOrphanedFilesJob, IIntegrityPathWithReportJob } from 'src/types';
import { handlePromiseError } from 'src/utils/misc';
async function* chunk<T>(generator: AsyncIterableIterator<T>, n: number) { async function* chunk<T>(generator: AsyncIterableIterator<T>, n: number) {
let chunk: T[] = []; let chunk: T[] = [];
@@ -38,25 +40,49 @@ async function* chunk<T>(generator: AsyncIterableIterator<T>, n: number) {
@Injectable() @Injectable()
export class IntegrityService extends BaseService { export class IntegrityService extends BaseService {
// private backupLock = false; private integrityLock = false;
@OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.Microservices] }) @OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.Microservices] })
async onConfigInit({ async onConfigInit({
newConfig: { newConfig: {
backup: { database }, integrityChecks: { orphanedFiles, missingFiles, checksumFiles },
}, },
}: ArgOf<'ConfigInit'>) { }: ArgOf<'ConfigInit'>) {
// this.backupLock = await this.databaseRepository.tryLock(DatabaseLock.BackupDatabase); this.integrityLock = await this.databaseRepository.tryLock(DatabaseLock.IntegrityCheck);
// if (this.backupLock) { if (this.integrityLock) {
// this.cronRepository.create({ this.cronRepository.create({
// name: 'backupDatabase', name: 'integrityOrphanedFiles',
// expression: database.cronExpression, expression: orphanedFiles.cronExpression,
// onTick: () => handlePromiseError(this.jobRepository.queue({ name: JobName.DatabaseBackup }), this.logger), onTick: () =>
// start: database.enabled, 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({ this.jobRepository.queue({
name: JobName.IntegrityOrphanedFilesQueueAll, name: JobName.IntegrityOrphanedFilesQueueAll,
data: {}, data: {},
@@ -71,19 +97,36 @@ export class IntegrityService extends BaseService {
name: JobName.IntegrityChecksumFiles, name: JobName.IntegrityChecksumFiles,
data: {}, data: {},
}); });
}, 1000); });
} }
@OnEvent({ name: 'ConfigUpdate', server: true }) @OnEvent({ name: 'ConfigUpdate', server: true })
async onConfigUpdate({ newConfig: { backup } }: ArgOf<'ConfigUpdate'>) { async onConfigUpdate({
// if (!this.backupLock) { newConfig: {
// return; integrityChecks: { orphanedFiles, missingFiles, checksumFiles },
// } },
// this.cronRepository.update({ }: ArgOf<'ConfigUpdate'>) {
// name: 'backupDatabase', if (!this.integrityLock) {
// expression: backup.database.cronExpression, return;
// start: backup.database.enabled, }
// });
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 }) @OnJob({ name: JobName.IntegrityOrphanedFilesQueueAll, queue: QueueName.BackgroundTask })