mirror of
https://github.com/immich-app/immich.git
synced 2026-03-25 19:18:57 +03:00
refactor: orphan -> untracked
This commit is contained in:
@@ -100,7 +100,7 @@ describe('/admin/integrity', () => {
|
||||
describe('POST /summary (& jobs)', async () => {
|
||||
it.sequential('reports no issues', async () => {
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFiles,
|
||||
name: ManualJobName.IntegrityUntrackedFiles,
|
||||
});
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
@@ -112,7 +112,7 @@ describe('/admin/integrity', () => {
|
||||
});
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFilesDeleteAll,
|
||||
name: ManualJobName.IntegrityUntrackedFilesDeleteAll,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||
@@ -125,16 +125,16 @@ describe('/admin/integrity', () => {
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual({
|
||||
missing_file: 0,
|
||||
orphan_file: 0,
|
||||
untracked_file: 0,
|
||||
checksum_mismatch: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it.sequential('should detect an orphan file (job: check orphan files)', async () => {
|
||||
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan1.png`);
|
||||
it.sequential('should detect an untracked file (job: check untracked files)', async () => {
|
||||
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFiles,
|
||||
name: ManualJobName.IntegrityUntrackedFiles,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||
@@ -147,18 +147,18 @@ describe('/admin/integrity', () => {
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual(
|
||||
expect.objectContaining({
|
||||
orphan_file: 1,
|
||||
untracked_file: 1,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it.sequential('should detect outdated orphan file reports (job: refresh orphan files)', async () => {
|
||||
it.sequential('should detect outdated untracked file reports (job: refresh untracked files)', async () => {
|
||||
// these should not be detected:
|
||||
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan2.png`);
|
||||
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan3.png`);
|
||||
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked2.png`);
|
||||
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked3.png`);
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFilesRefresh,
|
||||
name: ManualJobName.IntegrityUntrackedFilesRefresh,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||
@@ -171,22 +171,22 @@ describe('/admin/integrity', () => {
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual(
|
||||
expect.objectContaining({
|
||||
orphan_file: 0,
|
||||
untracked_file: 0,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it.sequential('should delete orphan files (job: delete all orphan file reports)', async () => {
|
||||
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan1.png`);
|
||||
it.sequential('should delete untracked files (job: delete all untracked file reports)', async () => {
|
||||
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFiles,
|
||||
name: ManualJobName.IntegrityUntrackedFiles,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFilesDeleteAll,
|
||||
name: ManualJobName.IntegrityUntrackedFilesDeleteAll,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||
@@ -199,7 +199,7 @@ describe('/admin/integrity', () => {
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual(
|
||||
expect.objectContaining({
|
||||
orphan_file: 0,
|
||||
untracked_file: 0,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -387,11 +387,11 @@ describe('/admin/integrity', () => {
|
||||
});
|
||||
|
||||
describe('POST /report', async () => {
|
||||
it.sequential('reports orphan files', async () => {
|
||||
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan1.png`);
|
||||
it.sequential('reports untracked files', async () => {
|
||||
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFiles,
|
||||
name: ManualJobName.IntegrityUntrackedFiles,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||
@@ -399,7 +399,7 @@ describe('/admin/integrity', () => {
|
||||
const { status, body } = await request(app)
|
||||
.post('/admin/integrity/report')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({ type: 'orphan_file' });
|
||||
.send({ type: 'untracked_file' });
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toEqual({
|
||||
@@ -407,8 +407,8 @@ describe('/admin/integrity', () => {
|
||||
items: expect.arrayContaining([
|
||||
{
|
||||
id: expect.any(String),
|
||||
type: 'orphan_file',
|
||||
path: `/data/upload/${admin.userId}/orphan1.png`,
|
||||
type: 'untracked_file',
|
||||
path: `/data/upload/${admin.userId}/untracked1.png`,
|
||||
assetId: null,
|
||||
fileAssetId: null,
|
||||
},
|
||||
@@ -476,11 +476,11 @@ describe('/admin/integrity', () => {
|
||||
});
|
||||
|
||||
describe('DELETE /report/:id', async () => {
|
||||
it.sequential('delete orphan files', async () => {
|
||||
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan1.png`);
|
||||
it.sequential('delete untracked files', async () => {
|
||||
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFiles,
|
||||
name: ManualJobName.IntegrityUntrackedFiles,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||
@@ -488,12 +488,12 @@ describe('/admin/integrity', () => {
|
||||
const { status: listStatus, body: listBody } = await request(app)
|
||||
.post('/admin/integrity/report')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({ type: 'orphan_file' });
|
||||
.send({ type: 'untracked_file' });
|
||||
|
||||
expect(listStatus).toBe(200);
|
||||
|
||||
const report = (listBody as IntegrityReportResponseDto).items.find(
|
||||
(item) => item.path === `/data/upload/${admin.userId}/orphan1.png`,
|
||||
(item) => item.path === `/data/upload/${admin.userId}/untracked1.png`,
|
||||
)!;
|
||||
|
||||
const { status } = await request(app)
|
||||
@@ -504,7 +504,7 @@ describe('/admin/integrity', () => {
|
||||
expect(status).toBe(200);
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFiles,
|
||||
name: ManualJobName.IntegrityUntrackedFiles,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||
@@ -512,7 +512,7 @@ describe('/admin/integrity', () => {
|
||||
const { status: listStatus2, body: listBody2 } = await request(app)
|
||||
.post('/admin/integrity/report')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({ type: 'orphan_file' });
|
||||
.send({ type: 'untracked_file' });
|
||||
|
||||
expect(listStatus2).toBe(200);
|
||||
expect(listBody2).not.toBe(
|
||||
@@ -610,17 +610,17 @@ describe('/admin/integrity', () => {
|
||||
});
|
||||
|
||||
describe('GET /report/:type/csv', () => {
|
||||
it.sequential('exports orphan files as csv', async () => {
|
||||
await utils.putTextFile('orphan', `/data/upload/${admin.userId}/orphan1.png`);
|
||||
it.sequential('exports untracked files as csv', async () => {
|
||||
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFiles,
|
||||
name: ManualJobName.IntegrityUntrackedFiles,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||
|
||||
const { status, headers, text } = await request(app)
|
||||
.get('/admin/integrity/report/orphan_file/csv')
|
||||
.get('/admin/integrity/report/untracked_file/csv')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send();
|
||||
|
||||
@@ -628,17 +628,17 @@ describe('/admin/integrity', () => {
|
||||
expect(headers['content-type']).toContain('text/csv');
|
||||
expect(headers['content-disposition']).toContain('.csv');
|
||||
expect(text).toContain('id,type,assetId,fileAssetId,path');
|
||||
expect(text).toContain(`orphan_file`);
|
||||
expect(text).toContain(`/data/upload/${admin.userId}/orphan1.png`);
|
||||
expect(text).toContain(`untracked_file`);
|
||||
expect(text).toContain(`/data/upload/${admin.userId}/untracked1.png`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /report/:id/file', () => {
|
||||
it.sequential('downloads orphan file', async () => {
|
||||
await utils.putTextFile('orphan-content', `/data/upload/${admin.userId}/orphan1.png`);
|
||||
it.sequential('downloads untracked file', async () => {
|
||||
await utils.putTextFile('untracked-content', `/data/upload/${admin.userId}/untracked1.png`);
|
||||
|
||||
await utils.createJob(admin.accessToken, {
|
||||
name: ManualJobName.IntegrityOrphanFiles,
|
||||
name: ManualJobName.IntegrityUntrackedFiles,
|
||||
});
|
||||
|
||||
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
|
||||
@@ -646,10 +646,10 @@ describe('/admin/integrity', () => {
|
||||
const { body: listBody } = await request(app)
|
||||
.post('/admin/integrity/report')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({ type: 'orphan_file' });
|
||||
.send({ type: 'untracked_file' });
|
||||
|
||||
const report = (listBody as IntegrityReportResponseDto).items.find(
|
||||
(item) => item.path === `/data/upload/${admin.userId}/orphan1.png`,
|
||||
(item) => item.path === `/data/upload/${admin.userId}/untracked1.png`,
|
||||
)!;
|
||||
|
||||
const { status, headers, body } = await request(app)
|
||||
@@ -660,7 +660,7 @@ describe('/admin/integrity', () => {
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(headers['content-type']).toContain('application/octet-stream');
|
||||
expect(body.toString()).toBe('orphan-content');
|
||||
expect(body.toString()).toBe('untracked-content');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,9 +195,9 @@
|
||||
"maintenance_integrity_missing_file": "Missing Files",
|
||||
"maintenance_integrity_missing_file_job": "Check for missing files",
|
||||
"maintenance_integrity_missing_file_refresh_job": "Refresh missing file reports",
|
||||
"maintenance_integrity_orphan_file": "Orphan Files",
|
||||
"maintenance_integrity_orphan_file_job": "Check for orphaned files",
|
||||
"maintenance_integrity_orphan_file_refresh_job": "Refresh orphan file reports",
|
||||
"maintenance_integrity_untracked_file": "Untracked Files",
|
||||
"maintenance_integrity_untracked_file_job": "Check for untracked files",
|
||||
"maintenance_integrity_untracked_file_refresh_job": "Refresh untracked file reports",
|
||||
"maintenance_integrity_report": "Integrity Report",
|
||||
"maintenance_settings": "Maintenance",
|
||||
"maintenance_settings_description": "Put Immich into maintenance mode.",
|
||||
|
||||
@@ -180,7 +180,7 @@ class MaintenanceAdminApi {
|
||||
|
||||
/// Download flagged file
|
||||
///
|
||||
/// Download the orphan/broken file if one exists
|
||||
/// Download the untracked/broken file if one exists
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
@@ -215,7 +215,7 @@ class MaintenanceAdminApi {
|
||||
|
||||
/// Download flagged file
|
||||
///
|
||||
/// Download the orphan/broken file if one exists
|
||||
/// Download the untracked/broken file if one exists
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
|
||||
@@ -24,7 +24,7 @@ class IntegrityGetReportDto {
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
DateTime? cursor;
|
||||
String? cursor;
|
||||
|
||||
/// Minimum value: 1
|
||||
///
|
||||
@@ -56,7 +56,7 @@ class IntegrityGetReportDto {
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.cursor != null) {
|
||||
json[r'cursor'] = this.cursor!.toUtc().toIso8601String();
|
||||
json[r'cursor'] = this.cursor;
|
||||
} else {
|
||||
// json[r'cursor'] = null;
|
||||
}
|
||||
@@ -78,7 +78,7 @@ class IntegrityGetReportDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return IntegrityGetReportDto(
|
||||
cursor: mapDateTime(json, r'cursor', r''),
|
||||
cursor: mapValueOfType<String>(json, r'cursor'),
|
||||
limit: num.parse('${json[r'limit']}'),
|
||||
type: IntegrityReportType.fromJson(json[r'type'])!,
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ class IntegrityReportResponseDto {
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
DateTime? nextCursor;
|
||||
String? nextCursor;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is IntegrityReportResponseDto &&
|
||||
@@ -45,7 +45,7 @@ class IntegrityReportResponseDto {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'items'] = this.items;
|
||||
if (this.nextCursor != null) {
|
||||
json[r'nextCursor'] = this.nextCursor!.toUtc().toIso8601String();
|
||||
json[r'nextCursor'] = this.nextCursor;
|
||||
} else {
|
||||
// json[r'nextCursor'] = null;
|
||||
}
|
||||
@@ -62,7 +62,7 @@ class IntegrityReportResponseDto {
|
||||
|
||||
return IntegrityReportResponseDto(
|
||||
items: IntegrityReportDto.listFromJson(json[r'items']),
|
||||
nextCursor: mapDateTime(json, r'nextCursor', r''),
|
||||
nextCursor: mapValueOfType<String>(json, r'nextCursor'),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -15,36 +15,36 @@ class IntegrityReportSummaryResponseDto {
|
||||
IntegrityReportSummaryResponseDto({
|
||||
required this.checksumMismatch,
|
||||
required this.missingFile,
|
||||
required this.orphanFile,
|
||||
required this.untrackedFile,
|
||||
});
|
||||
|
||||
int checksumMismatch;
|
||||
|
||||
int missingFile;
|
||||
|
||||
int orphanFile;
|
||||
int untrackedFile;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is IntegrityReportSummaryResponseDto &&
|
||||
other.checksumMismatch == checksumMismatch &&
|
||||
other.missingFile == missingFile &&
|
||||
other.orphanFile == orphanFile;
|
||||
other.untrackedFile == untrackedFile;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(checksumMismatch.hashCode) +
|
||||
(missingFile.hashCode) +
|
||||
(orphanFile.hashCode);
|
||||
(untrackedFile.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'IntegrityReportSummaryResponseDto[checksumMismatch=$checksumMismatch, missingFile=$missingFile, orphanFile=$orphanFile]';
|
||||
String toString() => 'IntegrityReportSummaryResponseDto[checksumMismatch=$checksumMismatch, missingFile=$missingFile, untrackedFile=$untrackedFile]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'checksum_mismatch'] = this.checksumMismatch;
|
||||
json[r'missing_file'] = this.missingFile;
|
||||
json[r'orphan_file'] = this.orphanFile;
|
||||
json[r'untracked_file'] = this.untrackedFile;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class IntegrityReportSummaryResponseDto {
|
||||
return IntegrityReportSummaryResponseDto(
|
||||
checksumMismatch: mapValueOfType<int>(json, r'checksum_mismatch')!,
|
||||
missingFile: mapValueOfType<int>(json, r'missing_file')!,
|
||||
orphanFile: mapValueOfType<int>(json, r'orphan_file')!,
|
||||
untrackedFile: mapValueOfType<int>(json, r'untracked_file')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -109,7 +109,7 @@ class IntegrityReportSummaryResponseDto {
|
||||
static const requiredKeys = <String>{
|
||||
'checksum_mismatch',
|
||||
'missing_file',
|
||||
'orphan_file',
|
||||
'untracked_file',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ class IntegrityReportType {
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const orphanFile = IntegrityReportType._(r'orphan_file');
|
||||
static const untrackedFile = IntegrityReportType._(r'untracked_file');
|
||||
static const missingFile = IntegrityReportType._(r'missing_file');
|
||||
static const checksumMismatch = IntegrityReportType._(r'checksum_mismatch');
|
||||
|
||||
/// List of all possible values in this [enum][IntegrityReportType].
|
||||
static const values = <IntegrityReportType>[
|
||||
orphanFile,
|
||||
untrackedFile,
|
||||
missingFile,
|
||||
checksumMismatch,
|
||||
];
|
||||
@@ -70,7 +70,7 @@ class IntegrityReportTypeTypeTransformer {
|
||||
IntegrityReportType? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'orphan_file': return IntegrityReportType.orphanFile;
|
||||
case r'untracked_file': return IntegrityReportType.untrackedFile;
|
||||
case r'missing_file': return IntegrityReportType.missingFile;
|
||||
case r'checksum_mismatch': return IntegrityReportType.checksumMismatch;
|
||||
default:
|
||||
|
||||
18
mobile/openapi/lib/model/job_name.dart
generated
18
mobile/openapi/lib/model/job_name.dart
generated
@@ -78,9 +78,9 @@ class JobName {
|
||||
static const ocrQueueAll = JobName._(r'OcrQueueAll');
|
||||
static const ocr = JobName._(r'Ocr');
|
||||
static const workflowRun = JobName._(r'WorkflowRun');
|
||||
static const integrityOrphanedFilesQueueAll = JobName._(r'IntegrityOrphanedFilesQueueAll');
|
||||
static const integrityOrphanedFiles = JobName._(r'IntegrityOrphanedFiles');
|
||||
static const integrityOrphanedRefresh = JobName._(r'IntegrityOrphanedRefresh');
|
||||
static const integrityUntrackedFilesQueueAll = JobName._(r'IntegrityUntrackedFilesQueueAll');
|
||||
static const integrityUntrackedFiles = JobName._(r'IntegrityUntrackedFiles');
|
||||
static const integrityUntrackedRefresh = JobName._(r'IntegrityUntrackedRefresh');
|
||||
static const integrityMissingFilesQueueAll = JobName._(r'IntegrityMissingFilesQueueAll');
|
||||
static const integrityMissingFiles = JobName._(r'IntegrityMissingFiles');
|
||||
static const integrityMissingFilesRefresh = JobName._(r'IntegrityMissingFilesRefresh');
|
||||
@@ -146,9 +146,9 @@ class JobName {
|
||||
ocrQueueAll,
|
||||
ocr,
|
||||
workflowRun,
|
||||
integrityOrphanedFilesQueueAll,
|
||||
integrityOrphanedFiles,
|
||||
integrityOrphanedRefresh,
|
||||
integrityUntrackedFilesQueueAll,
|
||||
integrityUntrackedFiles,
|
||||
integrityUntrackedRefresh,
|
||||
integrityMissingFilesQueueAll,
|
||||
integrityMissingFiles,
|
||||
integrityMissingFilesRefresh,
|
||||
@@ -249,9 +249,9 @@ class JobNameTypeTransformer {
|
||||
case r'OcrQueueAll': return JobName.ocrQueueAll;
|
||||
case r'Ocr': return JobName.ocr;
|
||||
case r'WorkflowRun': return JobName.workflowRun;
|
||||
case r'IntegrityOrphanedFilesQueueAll': return JobName.integrityOrphanedFilesQueueAll;
|
||||
case r'IntegrityOrphanedFiles': return JobName.integrityOrphanedFiles;
|
||||
case r'IntegrityOrphanedRefresh': return JobName.integrityOrphanedRefresh;
|
||||
case r'IntegrityUntrackedFilesQueueAll': return JobName.integrityUntrackedFilesQueueAll;
|
||||
case r'IntegrityUntrackedFiles': return JobName.integrityUntrackedFiles;
|
||||
case r'IntegrityUntrackedRefresh': return JobName.integrityUntrackedRefresh;
|
||||
case r'IntegrityMissingFilesQueueAll': return JobName.integrityMissingFilesQueueAll;
|
||||
case r'IntegrityMissingFiles': return JobName.integrityMissingFiles;
|
||||
case r'IntegrityMissingFilesRefresh': return JobName.integrityMissingFilesRefresh;
|
||||
|
||||
18
mobile/openapi/lib/model/manual_job_name.dart
generated
18
mobile/openapi/lib/model/manual_job_name.dart
generated
@@ -30,13 +30,13 @@ class ManualJobName {
|
||||
static const memoryCreate = ManualJobName._(r'memory-create');
|
||||
static const backupDatabase = ManualJobName._(r'backup-database');
|
||||
static const integrityMissingFiles = ManualJobName._(r'integrity-missing-files');
|
||||
static const integrityOrphanFiles = ManualJobName._(r'integrity-orphan-files');
|
||||
static const integrityUntrackedFiles = ManualJobName._(r'integrity-untracked-files');
|
||||
static const integrityChecksumMismatch = ManualJobName._(r'integrity-checksum-mismatch');
|
||||
static const integrityMissingFilesRefresh = ManualJobName._(r'integrity-missing-files-refresh');
|
||||
static const integrityOrphanFilesRefresh = ManualJobName._(r'integrity-orphan-files-refresh');
|
||||
static const integrityUntrackedFilesRefresh = ManualJobName._(r'integrity-untracked-files-refresh');
|
||||
static const integrityChecksumMismatchRefresh = ManualJobName._(r'integrity-checksum-mismatch-refresh');
|
||||
static const integrityMissingFilesDeleteAll = ManualJobName._(r'integrity-missing-files-delete-all');
|
||||
static const integrityOrphanFilesDeleteAll = ManualJobName._(r'integrity-orphan-files-delete-all');
|
||||
static const integrityUntrackedFilesDeleteAll = ManualJobName._(r'integrity-untracked-files-delete-all');
|
||||
static const integrityChecksumMismatchDeleteAll = ManualJobName._(r'integrity-checksum-mismatch-delete-all');
|
||||
|
||||
/// List of all possible values in this [enum][ManualJobName].
|
||||
@@ -48,13 +48,13 @@ class ManualJobName {
|
||||
memoryCreate,
|
||||
backupDatabase,
|
||||
integrityMissingFiles,
|
||||
integrityOrphanFiles,
|
||||
integrityUntrackedFiles,
|
||||
integrityChecksumMismatch,
|
||||
integrityMissingFilesRefresh,
|
||||
integrityOrphanFilesRefresh,
|
||||
integrityUntrackedFilesRefresh,
|
||||
integrityChecksumMismatchRefresh,
|
||||
integrityMissingFilesDeleteAll,
|
||||
integrityOrphanFilesDeleteAll,
|
||||
integrityUntrackedFilesDeleteAll,
|
||||
integrityChecksumMismatchDeleteAll,
|
||||
];
|
||||
|
||||
@@ -101,13 +101,13 @@ class ManualJobNameTypeTransformer {
|
||||
case r'memory-create': return ManualJobName.memoryCreate;
|
||||
case r'backup-database': return ManualJobName.backupDatabase;
|
||||
case r'integrity-missing-files': return ManualJobName.integrityMissingFiles;
|
||||
case r'integrity-orphan-files': return ManualJobName.integrityOrphanFiles;
|
||||
case r'integrity-untracked-files': return ManualJobName.integrityUntrackedFiles;
|
||||
case r'integrity-checksum-mismatch': return ManualJobName.integrityChecksumMismatch;
|
||||
case r'integrity-missing-files-refresh': return ManualJobName.integrityMissingFilesRefresh;
|
||||
case r'integrity-orphan-files-refresh': return ManualJobName.integrityOrphanFilesRefresh;
|
||||
case r'integrity-untracked-files-refresh': return ManualJobName.integrityUntrackedFilesRefresh;
|
||||
case r'integrity-checksum-mismatch-refresh': return ManualJobName.integrityChecksumMismatchRefresh;
|
||||
case r'integrity-missing-files-delete-all': return ManualJobName.integrityMissingFilesDeleteAll;
|
||||
case r'integrity-orphan-files-delete-all': return ManualJobName.integrityOrphanFilesDeleteAll;
|
||||
case r'integrity-untracked-files-delete-all': return ManualJobName.integrityUntrackedFilesDeleteAll;
|
||||
case r'integrity-checksum-mismatch-delete-all': return ManualJobName.integrityChecksumMismatchDeleteAll;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
|
||||
@@ -15,36 +15,36 @@ class SystemConfigIntegrityChecks {
|
||||
SystemConfigIntegrityChecks({
|
||||
required this.checksumFiles,
|
||||
required this.missingFiles,
|
||||
required this.orphanedFiles,
|
||||
required this.untrackedFiles,
|
||||
});
|
||||
|
||||
SystemConfigIntegrityChecksumJob checksumFiles;
|
||||
|
||||
SystemConfigIntegrityJob missingFiles;
|
||||
|
||||
SystemConfigIntegrityJob orphanedFiles;
|
||||
SystemConfigIntegrityJob untrackedFiles;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SystemConfigIntegrityChecks &&
|
||||
other.checksumFiles == checksumFiles &&
|
||||
other.missingFiles == missingFiles &&
|
||||
other.orphanedFiles == orphanedFiles;
|
||||
other.untrackedFiles == untrackedFiles;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(checksumFiles.hashCode) +
|
||||
(missingFiles.hashCode) +
|
||||
(orphanedFiles.hashCode);
|
||||
(untrackedFiles.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SystemConfigIntegrityChecks[checksumFiles=$checksumFiles, missingFiles=$missingFiles, orphanedFiles=$orphanedFiles]';
|
||||
String toString() => 'SystemConfigIntegrityChecks[checksumFiles=$checksumFiles, missingFiles=$missingFiles, untrackedFiles=$untrackedFiles]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'checksumFiles'] = this.checksumFiles;
|
||||
json[r'missingFiles'] = this.missingFiles;
|
||||
json[r'orphanedFiles'] = this.orphanedFiles;
|
||||
json[r'untrackedFiles'] = this.untrackedFiles;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class SystemConfigIntegrityChecks {
|
||||
return SystemConfigIntegrityChecks(
|
||||
checksumFiles: SystemConfigIntegrityChecksumJob.fromJson(json[r'checksumFiles'])!,
|
||||
missingFiles: SystemConfigIntegrityJob.fromJson(json[r'missingFiles'])!,
|
||||
orphanedFiles: SystemConfigIntegrityJob.fromJson(json[r'orphanedFiles'])!,
|
||||
untrackedFiles: SystemConfigIntegrityJob.fromJson(json[r'untrackedFiles'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -109,7 +109,7 @@ class SystemConfigIntegrityChecks {
|
||||
static const requiredKeys = <String>{
|
||||
'checksumFiles',
|
||||
'missingFiles',
|
||||
'orphanedFiles',
|
||||
'untrackedFiles',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -431,7 +431,7 @@
|
||||
},
|
||||
"/admin/integrity/report/{id}/file": {
|
||||
"get": {
|
||||
"description": "Download the orphan/broken file if one exists",
|
||||
"description": "Download the untracked/broken file if one exists",
|
||||
"operationId": "getIntegrityReportFile",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -16997,20 +16997,20 @@
|
||||
"missing_file": {
|
||||
"type": "integer"
|
||||
},
|
||||
"orphan_file": {
|
||||
"untracked_file": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"checksum_mismatch",
|
||||
"missing_file",
|
||||
"orphan_file"
|
||||
"untracked_file"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"IntegrityReportType": {
|
||||
"enum": [
|
||||
"orphan_file",
|
||||
"untracked_file",
|
||||
"missing_file",
|
||||
"checksum_mismatch"
|
||||
],
|
||||
@@ -17088,9 +17088,9 @@
|
||||
"OcrQueueAll",
|
||||
"Ocr",
|
||||
"WorkflowRun",
|
||||
"IntegrityOrphanedFilesQueueAll",
|
||||
"IntegrityOrphanedFiles",
|
||||
"IntegrityOrphanedRefresh",
|
||||
"IntegrityUntrackedFilesQueueAll",
|
||||
"IntegrityUntrackedFiles",
|
||||
"IntegrityUntrackedRefresh",
|
||||
"IntegrityMissingFilesQueueAll",
|
||||
"IntegrityMissingFiles",
|
||||
"IntegrityMissingFilesRefresh",
|
||||
@@ -17368,13 +17368,13 @@
|
||||
"memory-create",
|
||||
"backup-database",
|
||||
"integrity-missing-files",
|
||||
"integrity-orphan-files",
|
||||
"integrity-untracked-files",
|
||||
"integrity-checksum-mismatch",
|
||||
"integrity-missing-files-refresh",
|
||||
"integrity-orphan-files-refresh",
|
||||
"integrity-untracked-files-refresh",
|
||||
"integrity-checksum-mismatch-refresh",
|
||||
"integrity-missing-files-delete-all",
|
||||
"integrity-orphan-files-delete-all",
|
||||
"integrity-untracked-files-delete-all",
|
||||
"integrity-checksum-mismatch-delete-all"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -22022,14 +22022,14 @@
|
||||
"missingFiles": {
|
||||
"$ref": "#/components/schemas/SystemConfigIntegrityJob"
|
||||
},
|
||||
"orphanedFiles": {
|
||||
"untrackedFiles": {
|
||||
"$ref": "#/components/schemas/SystemConfigIntegrityJob"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"checksumFiles",
|
||||
"missingFiles",
|
||||
"orphanedFiles"
|
||||
"untrackedFiles"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as Oazapfts from "@oazapfts/runtime";
|
||||
import * as QS from "@oazapfts/runtime/query";
|
||||
export const defaults: Oazapfts.Defaults<Oazapfts.CustomHeaders> = {
|
||||
headers: {},
|
||||
baseUrl: "/api",
|
||||
baseUrl: "/api"
|
||||
};
|
||||
const oazapfts = Oazapfts.runtime(defaults);
|
||||
export const servers = {
|
||||
@@ -57,7 +57,7 @@ export type IntegrityReportResponseDto = {
|
||||
export type IntegrityReportSummaryResponseDto = {
|
||||
checksum_mismatch: number;
|
||||
missing_file: number;
|
||||
orphan_file: number;
|
||||
untracked_file: number;
|
||||
};
|
||||
export type SetMaintenanceModeDto = {
|
||||
action: MaintenanceAction;
|
||||
@@ -1491,7 +1491,7 @@ export type SystemConfigIntegrityJob = {
|
||||
export type SystemConfigIntegrityChecks = {
|
||||
checksumFiles: SystemConfigIntegrityChecksumJob;
|
||||
missingFiles: SystemConfigIntegrityJob;
|
||||
orphanedFiles: SystemConfigIntegrityJob;
|
||||
untrackedFiles: SystemConfigIntegrityJob;
|
||||
};
|
||||
export type JobSettingsDto = {
|
||||
concurrency: number;
|
||||
@@ -5257,7 +5257,7 @@ export enum UserAvatarColor {
|
||||
Amber = "amber"
|
||||
}
|
||||
export enum IntegrityReportType {
|
||||
OrphanFile = "orphan_file",
|
||||
UntrackedFile = "untracked_file",
|
||||
MissingFile = "missing_file",
|
||||
ChecksumMismatch = "checksum_mismatch"
|
||||
}
|
||||
@@ -5503,13 +5503,13 @@ export enum ManualJobName {
|
||||
MemoryCreate = "memory-create",
|
||||
BackupDatabase = "backup-database",
|
||||
IntegrityMissingFiles = "integrity-missing-files",
|
||||
IntegrityOrphanFiles = "integrity-orphan-files",
|
||||
IntegrityUntrackedFiles = "integrity-untracked-files",
|
||||
IntegrityChecksumMismatch = "integrity-checksum-mismatch",
|
||||
IntegrityMissingFilesRefresh = "integrity-missing-files-refresh",
|
||||
IntegrityOrphanFilesRefresh = "integrity-orphan-files-refresh",
|
||||
IntegrityUntrackedFilesRefresh = "integrity-untracked-files-refresh",
|
||||
IntegrityChecksumMismatchRefresh = "integrity-checksum-mismatch-refresh",
|
||||
IntegrityMissingFilesDeleteAll = "integrity-missing-files-delete-all",
|
||||
IntegrityOrphanFilesDeleteAll = "integrity-orphan-files-delete-all",
|
||||
IntegrityUntrackedFilesDeleteAll = "integrity-untracked-files-delete-all",
|
||||
IntegrityChecksumMismatchDeleteAll = "integrity-checksum-mismatch-delete-all"
|
||||
}
|
||||
export enum QueueName {
|
||||
@@ -5624,9 +5624,9 @@ export enum JobName {
|
||||
OcrQueueAll = "OcrQueueAll",
|
||||
Ocr = "Ocr",
|
||||
WorkflowRun = "WorkflowRun",
|
||||
IntegrityOrphanedFilesQueueAll = "IntegrityOrphanedFilesQueueAll",
|
||||
IntegrityOrphanedFiles = "IntegrityOrphanedFiles",
|
||||
IntegrityOrphanedRefresh = "IntegrityOrphanedRefresh",
|
||||
IntegrityUntrackedFilesQueueAll = "IntegrityUntrackedFilesQueueAll",
|
||||
IntegrityUntrackedFiles = "IntegrityUntrackedFiles",
|
||||
IntegrityUntrackedRefresh = "IntegrityUntrackedRefresh",
|
||||
IntegrityMissingFilesQueueAll = "IntegrityMissingFilesQueueAll",
|
||||
IntegrityMissingFiles = "IntegrityMissingFiles",
|
||||
IntegrityMissingFilesRefresh = "IntegrityMissingFilesRefresh",
|
||||
|
||||
@@ -51,7 +51,7 @@ export interface SystemConfig {
|
||||
enabled: boolean;
|
||||
cronExpression: string;
|
||||
};
|
||||
orphanedFiles: {
|
||||
untrackedFiles: {
|
||||
enabled: boolean;
|
||||
cronExpression: string;
|
||||
};
|
||||
@@ -243,7 +243,7 @@ export const defaults = Object.freeze<SystemConfig>({
|
||||
enabled: true,
|
||||
cronExpression: CronExpression.EVERY_DAY_AT_3AM,
|
||||
},
|
||||
orphanedFiles: {
|
||||
untrackedFiles: {
|
||||
enabled: true,
|
||||
cronExpression: CronExpression.EVERY_DAY_AT_3AM,
|
||||
},
|
||||
|
||||
@@ -76,7 +76,7 @@ export class IntegrityController {
|
||||
@Get('report/:id/file')
|
||||
@Endpoint({
|
||||
summary: 'Download flagged file',
|
||||
description: 'Download the orphan/broken file if one exists',
|
||||
description: 'Download the untracked/broken file if one exists',
|
||||
history: new HistoryBuilder().added('v9.9.9').alpha('v9.9.9'),
|
||||
})
|
||||
@FileResponse()
|
||||
|
||||
@@ -10,7 +10,7 @@ export class IntegrityReportSummaryResponseDto {
|
||||
@ApiProperty({ type: 'integer' })
|
||||
[IntegrityReportType.MissingFile]!: number;
|
||||
@ApiProperty({ type: 'integer' })
|
||||
[IntegrityReportType.OrphanFile]!: number;
|
||||
[IntegrityReportType.UntrackedFile]!: number;
|
||||
}
|
||||
|
||||
export class IntegrityGetReportDto {
|
||||
|
||||
@@ -174,7 +174,7 @@ class SystemConfigIntegrityChecks {
|
||||
@Type(() => SystemConfigIntegrityJob)
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
orphanedFiles!: SystemConfigIntegrityJob;
|
||||
untrackedFiles!: SystemConfigIntegrityJob;
|
||||
|
||||
@Type(() => SystemConfigIntegrityChecksumJob)
|
||||
@ValidateNested()
|
||||
|
||||
@@ -347,7 +347,7 @@ export enum SourceType {
|
||||
}
|
||||
|
||||
export enum IntegrityReportType {
|
||||
OrphanFile = 'orphan_file',
|
||||
UntrackedFile = 'untracked_file',
|
||||
MissingFile = 'missing_file',
|
||||
ChecksumFail = 'checksum_mismatch',
|
||||
}
|
||||
@@ -360,13 +360,13 @@ export enum ManualJobName {
|
||||
MemoryCreate = 'memory-create',
|
||||
BackupDatabase = 'backup-database',
|
||||
IntegrityMissingFiles = `integrity-missing-files`,
|
||||
IntegrityOrphanFiles = `integrity-orphan-files`,
|
||||
IntegrityUntrackedFiles = `integrity-untracked-files`,
|
||||
IntegrityChecksumFiles = `integrity-checksum-mismatch`,
|
||||
IntegrityMissingFilesRefresh = `integrity-missing-files-refresh`,
|
||||
IntegrityOrphanFilesRefresh = `integrity-orphan-files-refresh`,
|
||||
IntegrityUntrackedFilesRefresh = `integrity-untracked-files-refresh`,
|
||||
IntegrityChecksumFilesRefresh = `integrity-checksum-mismatch-refresh`,
|
||||
IntegrityMissingFilesDeleteAll = `integrity-missing-files-delete-all`,
|
||||
IntegrityOrphanFilesDeleteAll = `integrity-orphan-files-delete-all`,
|
||||
IntegrityUntrackedFilesDeleteAll = `integrity-untracked-files-delete-all`,
|
||||
IntegrityChecksumFilesDeleteAll = `integrity-checksum-mismatch-delete-all`,
|
||||
}
|
||||
|
||||
@@ -662,9 +662,9 @@ export enum JobName {
|
||||
WorkflowRun = 'WorkflowRun',
|
||||
|
||||
// Integrity
|
||||
IntegrityOrphanedFilesQueueAll = 'IntegrityOrphanedFilesQueueAll',
|
||||
IntegrityOrphanedFiles = 'IntegrityOrphanedFiles',
|
||||
IntegrityOrphanedFilesRefresh = 'IntegrityOrphanedRefresh',
|
||||
IntegrityUntrackedFilesQueueAll = 'IntegrityUntrackedFilesQueueAll',
|
||||
IntegrityUntrackedFiles = 'IntegrityUntrackedFiles',
|
||||
IntegrityUntrackedFilesRefresh = 'IntegrityUntrackedRefresh',
|
||||
IntegrityMissingFilesQueueAll = 'IntegrityMissingFilesQueueAll',
|
||||
IntegrityMissingFiles = 'IntegrityMissingFiles',
|
||||
IntegrityMissingFilesRefresh = 'IntegrityMissingFilesRefresh',
|
||||
|
||||
@@ -21,7 +21,7 @@ select
|
||||
count(*) filter (
|
||||
where
|
||||
"type" = $3
|
||||
) as "orphan_file"
|
||||
) as "untracked_file"
|
||||
from
|
||||
"integrity_report"
|
||||
|
||||
|
||||
@@ -57,8 +57,8 @@ export class IntegrityRepository {
|
||||
.select((eb) =>
|
||||
eb.fn
|
||||
.countAll<number>()
|
||||
.filterWhere('type', '=', IntegrityReportType.OrphanFile)
|
||||
.as(IntegrityReportType.OrphanFile),
|
||||
.filterWhere('type', '=', IntegrityReportType.UntrackedFile)
|
||||
.as(IntegrityReportType.UntrackedFile),
|
||||
)
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export class IntegrityRepository {
|
||||
)
|
||||
.leftJoin('integrity_report', (join) =>
|
||||
join
|
||||
.on('integrity_report.type', '=', IntegrityReportType.OrphanFile)
|
||||
.on('integrity_report.type', '=', IntegrityReportType.UntrackedFile)
|
||||
.on((eb) =>
|
||||
eb.or([
|
||||
eb('integrity_report.assetId', '=', eb.ref('allPaths.assetId')),
|
||||
|
||||
@@ -145,7 +145,7 @@ describe(IntegrityService.name, () => {
|
||||
expect(mocks.asset.deleteFiles).toHaveBeenCalledWith([{ id: 'fileAssetId' }]);
|
||||
});
|
||||
|
||||
it('deletes orphaned file', async () => {
|
||||
it('deletes untracked file', async () => {
|
||||
mocks.integrityReport.getById.mockResolvedValue({
|
||||
id: 'id',
|
||||
createdAt: new Date(0),
|
||||
@@ -169,7 +169,7 @@ describe(IntegrityService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleOrphanedFilesQueueAll', () => {
|
||||
describe('handleUntrackedFilesQueueAll', () => {
|
||||
beforeEach(() => {
|
||||
mocks.integrityReport.streamIntegrityReportsWithAssetChecksum.mockReturnValue((function* () {})() as never);
|
||||
});
|
||||
@@ -189,11 +189,11 @@ describe(IntegrityService.name, () => {
|
||||
})() as never,
|
||||
);
|
||||
|
||||
await sut.handleOrphanedFilesQueueAll({ refreshOnly: false });
|
||||
await sut.handleUntrackedFilesQueueAll({ refreshOnly: false });
|
||||
|
||||
expect(mocks.job.queue).toBeCalledTimes(4);
|
||||
expect(mocks.job.queue).toBeCalledWith({
|
||||
name: JobName.IntegrityOrphanedFiles,
|
||||
name: JobName.IntegrityUntrackedFiles,
|
||||
data: {
|
||||
type: 'asset',
|
||||
paths: expect.arrayContaining(['/path/to/file']),
|
||||
@@ -201,7 +201,7 @@ describe(IntegrityService.name, () => {
|
||||
});
|
||||
|
||||
expect(mocks.job.queue).toBeCalledWith({
|
||||
name: JobName.IntegrityOrphanedFiles,
|
||||
name: JobName.IntegrityUntrackedFiles,
|
||||
data: {
|
||||
type: 'asset_file',
|
||||
paths: expect.arrayContaining(['/path/to/file3']),
|
||||
@@ -216,11 +216,11 @@ describe(IntegrityService.name, () => {
|
||||
})() as never,
|
||||
);
|
||||
|
||||
await sut.handleOrphanedFilesQueueAll({ refreshOnly: false });
|
||||
await sut.handleUntrackedFilesQueueAll({ refreshOnly: false });
|
||||
|
||||
expect(mocks.job.queue).toBeCalledTimes(1);
|
||||
expect(mocks.job.queue).toBeCalledWith({
|
||||
name: JobName.IntegrityOrphanedFilesRefresh,
|
||||
name: JobName.IntegrityUntrackedFilesRefresh,
|
||||
data: {
|
||||
items: expect.arrayContaining(['mockReport']),
|
||||
},
|
||||
@@ -228,33 +228,33 @@ describe(IntegrityService.name, () => {
|
||||
});
|
||||
|
||||
it('should succeed', async () => {
|
||||
await expect(sut.handleOrphanedFilesQueueAll({ refreshOnly: false })).resolves.toBe(JobStatus.Success);
|
||||
await expect(sut.handleUntrackedFilesQueueAll({ refreshOnly: false })).resolves.toBe(JobStatus.Success);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleOrphanedFiles', () => {
|
||||
it('should detect orphaned asset files', async () => {
|
||||
describe('handleUntrackedFiles', () => {
|
||||
it('should detect untracked asset files', async () => {
|
||||
mocks.integrityReport.getAssetPathsByPaths.mockResolvedValue([
|
||||
{ originalPath: '/path/to/file1', encodedVideoPath: null },
|
||||
]);
|
||||
|
||||
await sut.handleOrphanedFiles({
|
||||
await sut.handleUntrackedFiles({
|
||||
type: 'asset',
|
||||
paths: ['/path/to/file1', '/path/to/orphan'],
|
||||
paths: ['/path/to/file1', '/path/to/untracked'],
|
||||
});
|
||||
|
||||
expect(mocks.integrityReport.getAssetPathsByPaths).toHaveBeenCalledWith(['/path/to/file1', '/path/to/orphan']);
|
||||
expect(mocks.integrityReport.getAssetPathsByPaths).toHaveBeenCalledWith(['/path/to/file1', '/path/to/untracked']);
|
||||
expect(mocks.integrityReport.create).toHaveBeenCalledWith([
|
||||
{ type: IntegrityReportType.OrphanFile, path: '/path/to/orphan' },
|
||||
{ type: IntegrityReportType.UntrackedFile, path: '/path/to/untracked' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not create reports when no orphans found for assets', async () => {
|
||||
it('should not create reports when no untracked files found for assets', async () => {
|
||||
mocks.integrityReport.getAssetPathsByPaths.mockResolvedValue([
|
||||
{ originalPath: '/path/to/file1', encodedVideoPath: '/path/to/encoded' },
|
||||
]);
|
||||
|
||||
await sut.handleOrphanedFiles({
|
||||
await sut.handleUntrackedFiles({
|
||||
type: 'asset',
|
||||
paths: ['/path/to/file1', '/path/to/encoded'],
|
||||
});
|
||||
@@ -262,28 +262,28 @@ describe(IntegrityService.name, () => {
|
||||
expect(mocks.integrityReport.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should detect orphaned asset_file files', async () => {
|
||||
it('should detect untracked asset_file files', async () => {
|
||||
mocks.integrityReport.getAssetFilePathsByPaths.mockResolvedValue([{ path: '/path/to/thumb1' }]);
|
||||
|
||||
await sut.handleOrphanedFiles({
|
||||
await sut.handleUntrackedFiles({
|
||||
type: 'asset_file',
|
||||
paths: ['/path/to/thumb1', '/path/to/orphan_thumb'],
|
||||
paths: ['/path/to/thumb1', '/path/to/untracked_thumb'],
|
||||
});
|
||||
|
||||
expect(mocks.integrityReport.create).toHaveBeenCalledWith([
|
||||
{ type: IntegrityReportType.OrphanFile, path: '/path/to/orphan_thumb' },
|
||||
{ type: IntegrityReportType.UntrackedFile, path: '/path/to/untracked_thumb' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleOrphanedRefresh', () => {
|
||||
describe('handleUntrackedRefresh', () => {
|
||||
it('should delete reports for files that no longer exist', async () => {
|
||||
mocks.storage.stat
|
||||
.mockRejectedValueOnce(new Error('ENOENT'))
|
||||
.mockResolvedValueOnce({} as never)
|
||||
.mockRejectedValueOnce(new Error('ENOENT'));
|
||||
|
||||
await sut.handleOrphanedRefresh({
|
||||
await sut.handleUntrackedRefresh({
|
||||
items: [
|
||||
{ reportId: 'report1', path: '/path/to/missing1' },
|
||||
{ reportId: 'report2', path: '/path/to/existing' },
|
||||
@@ -297,7 +297,7 @@ describe(IntegrityService.name, () => {
|
||||
it('should not delete reports for files that still exist', async () => {
|
||||
mocks.storage.stat.mockResolvedValue({} as never);
|
||||
|
||||
await sut.handleOrphanedRefresh({
|
||||
await sut.handleUntrackedRefresh({
|
||||
items: [{ reportId: 'report1', path: '/path/to/existing' }],
|
||||
});
|
||||
|
||||
@@ -305,7 +305,7 @@ describe(IntegrityService.name, () => {
|
||||
});
|
||||
|
||||
it('should succeed', async () => {
|
||||
await expect(sut.handleOrphanedRefresh({ items: [] })).resolves.toBe(JobStatus.Success);
|
||||
await expect(sut.handleUntrackedRefresh({ items: [] })).resolves.toBe(JobStatus.Success);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -604,24 +604,24 @@ describe(IntegrityService.name, () => {
|
||||
expect(mocks.job.queue).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should queue delete jobs for orphan file reports', async () => {
|
||||
it('should queue delete jobs for untracked file reports', async () => {
|
||||
mocks.integrityReport.streamIntegrityReportsByProperty.mockReturnValue(
|
||||
(function* () {
|
||||
yield { id: 'report1', path: '/path/to/orphan' };
|
||||
yield { id: 'report1', path: '/path/to/untracked' };
|
||||
})() as never,
|
||||
);
|
||||
|
||||
await sut.handleDeleteAllIntegrityReports({ type: IntegrityReportType.OrphanFile });
|
||||
await sut.handleDeleteAllIntegrityReports({ type: IntegrityReportType.UntrackedFile });
|
||||
|
||||
expect(mocks.integrityReport.streamIntegrityReportsByProperty).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
IntegrityReportType.OrphanFile,
|
||||
IntegrityReportType.UntrackedFile,
|
||||
);
|
||||
|
||||
expect(mocks.job.queue).toHaveBeenCalledWith({
|
||||
name: JobName.IntegrityDeleteReports,
|
||||
data: {
|
||||
reports: [{ id: 'report1', path: '/path/to/orphan' }],
|
||||
reports: [{ id: 'report1', path: '/path/to/untracked' }],
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -648,7 +648,7 @@ describe(IntegrityService.name, () => {
|
||||
{ id: 'report1', assetId: 'asset1', fileAssetId: null, path: '/path/to/file1' },
|
||||
{ id: 'report2', assetId: 'asset2', fileAssetId: null, path: '/path/to/file2' },
|
||||
{ id: 'report3', assetId: null, fileAssetId: 'fileAsset1', path: '/path/to/file3' },
|
||||
{ id: 'report4', assetId: null, fileAssetId: null, path: '/path/to/orphan' },
|
||||
{ id: 'report4', assetId: null, fileAssetId: null, path: '/path/to/untracked' },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -666,7 +666,7 @@ describe(IntegrityService.name, () => {
|
||||
|
||||
expect(mocks.asset.deleteFiles).toHaveBeenCalledWith([{ id: 'fileAsset1' }]);
|
||||
|
||||
expect(mocks.storage.unlink).toHaveBeenCalledWith('/path/to/orphan');
|
||||
expect(mocks.storage.unlink).toHaveBeenCalledWith('/path/to/untracked');
|
||||
expect(mocks.integrityReport.deleteByIds).toHaveBeenCalledWith(['report4']);
|
||||
});
|
||||
|
||||
|
||||
@@ -31,15 +31,15 @@ import {
|
||||
IIntegrityDeleteReportTypeJob,
|
||||
IIntegrityJob,
|
||||
IIntegrityMissingFilesJob,
|
||||
IIntegrityOrphanedFilesJob,
|
||||
IIntegrityPathWithChecksumJob,
|
||||
IIntegrityPathWithReportJob,
|
||||
IIntegrityUntrackedFilesJob,
|
||||
} from 'src/types';
|
||||
import { ImmichFileResponse } from 'src/utils/file';
|
||||
import { handlePromiseError } from 'src/utils/misc';
|
||||
|
||||
/**
|
||||
* Orphan Files:
|
||||
* Untracked Files:
|
||||
* Files are detected in /data/encoded-video, /data/library, /data/upload
|
||||
* Checked against the asset table
|
||||
* Files are detected in /data/thumbs
|
||||
@@ -69,20 +69,20 @@ export class IntegrityService extends BaseService {
|
||||
@OnEvent({ name: 'ConfigInit', workers: [ImmichWorker.Microservices] })
|
||||
async onConfigInit({
|
||||
newConfig: {
|
||||
integrityChecks: { orphanedFiles, missingFiles, checksumFiles },
|
||||
integrityChecks: { untrackedFiles, missingFiles, checksumFiles },
|
||||
},
|
||||
}: ArgOf<'ConfigInit'>) {
|
||||
this.integrityLock = await this.databaseRepository.tryLock(DatabaseLock.IntegrityCheck);
|
||||
if (this.integrityLock) {
|
||||
this.cronRepository.create({
|
||||
name: 'integrityOrphanedFiles',
|
||||
expression: orphanedFiles.cronExpression,
|
||||
name: 'integrityUntrackedFiles',
|
||||
expression: untrackedFiles.cronExpression,
|
||||
onTick: () =>
|
||||
handlePromiseError(
|
||||
this.jobRepository.queue({ name: JobName.IntegrityOrphanedFilesQueueAll, data: {} }),
|
||||
this.jobRepository.queue({ name: JobName.IntegrityUntrackedFilesQueueAll, data: {} }),
|
||||
this.logger,
|
||||
),
|
||||
start: orphanedFiles.enabled,
|
||||
start: untrackedFiles.enabled,
|
||||
});
|
||||
|
||||
this.cronRepository.create({
|
||||
@@ -109,7 +109,7 @@ export class IntegrityService extends BaseService {
|
||||
@OnEvent({ name: 'ConfigUpdate', server: true })
|
||||
onConfigUpdate({
|
||||
newConfig: {
|
||||
integrityChecks: { orphanedFiles, missingFiles, checksumFiles },
|
||||
integrityChecks: { untrackedFiles, missingFiles, checksumFiles },
|
||||
},
|
||||
}: ArgOf<'ConfigUpdate'>) {
|
||||
if (!this.integrityLock) {
|
||||
@@ -117,9 +117,9 @@ export class IntegrityService extends BaseService {
|
||||
}
|
||||
|
||||
this.cronRepository.update({
|
||||
name: 'integrityOrphanedFiles',
|
||||
expression: orphanedFiles.cronExpression,
|
||||
start: orphanedFiles.enabled,
|
||||
name: 'integrityUntrackedFiles',
|
||||
expression: untrackedFiles.cronExpression,
|
||||
start: untrackedFiles.enabled,
|
||||
});
|
||||
|
||||
this.cronRepository.update({
|
||||
@@ -194,16 +194,16 @@ export class IntegrityService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
@OnJob({ name: JobName.IntegrityOrphanedFilesQueueAll, queue: QueueName.IntegrityCheck })
|
||||
async handleOrphanedFilesQueueAll({ refreshOnly }: IIntegrityJob = {}): Promise<JobStatus> {
|
||||
this.logger.log(`Checking for out of date orphaned file reports...`);
|
||||
@OnJob({ name: JobName.IntegrityUntrackedFilesQueueAll, queue: QueueName.IntegrityCheck })
|
||||
async handleUntrackedFilesQueueAll({ refreshOnly }: IIntegrityJob = {}): Promise<JobStatus> {
|
||||
this.logger.log(`Checking for out of date untracked file reports...`);
|
||||
|
||||
const reports = this.integrityRepository.streamIntegrityReportsWithAssetChecksum(IntegrityReportType.OrphanFile);
|
||||
const reports = this.integrityRepository.streamIntegrityReportsWithAssetChecksum(IntegrityReportType.UntrackedFile);
|
||||
|
||||
let total = 0;
|
||||
for await (const batchReports of chunk(reports, JOBS_LIBRARY_PAGINATION_SIZE)) {
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.IntegrityOrphanedFilesRefresh,
|
||||
name: JobName.IntegrityUntrackedFilesRefresh,
|
||||
data: {
|
||||
items: batchReports,
|
||||
},
|
||||
@@ -218,7 +218,7 @@ export class IntegrityService extends BaseService {
|
||||
return JobStatus.Success;
|
||||
}
|
||||
|
||||
this.logger.log(`Scanning for orphaned files...`);
|
||||
this.logger.log(`Scanning for untracked files...`);
|
||||
|
||||
const assetPaths = this.storageRepository.walk({
|
||||
pathsToCrawl: [StorageFolder.EncodedVideo, StorageFolder.Library, StorageFolder.Upload].map((folder) =>
|
||||
@@ -247,7 +247,7 @@ export class IntegrityService extends BaseService {
|
||||
total = 0;
|
||||
for await (const [batchType, batchPaths] of paths()) {
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.IntegrityOrphanedFiles,
|
||||
name: JobName.IntegrityUntrackedFiles,
|
||||
data: {
|
||||
type: batchType,
|
||||
paths: batchPaths,
|
||||
@@ -257,48 +257,48 @@ export class IntegrityService extends BaseService {
|
||||
const count = batchPaths.length;
|
||||
total += count;
|
||||
|
||||
this.logger.log(`Queued orphan check of ${count} file(s) (${total} so far)`);
|
||||
this.logger.log(`Queued untracked check of ${count} file(s) (${total} so far)`);
|
||||
}
|
||||
|
||||
return JobStatus.Success;
|
||||
}
|
||||
|
||||
@OnJob({ name: JobName.IntegrityOrphanedFiles, queue: QueueName.IntegrityCheck })
|
||||
async handleOrphanedFiles({ type, paths }: IIntegrityOrphanedFilesJob): Promise<JobStatus> {
|
||||
this.logger.log(`Processing batch of ${paths.length} files to check if they are orphaned.`);
|
||||
@OnJob({ name: JobName.IntegrityUntrackedFiles, queue: QueueName.IntegrityCheck })
|
||||
async handleUntrackedFiles({ type, paths }: IIntegrityUntrackedFilesJob): Promise<JobStatus> {
|
||||
this.logger.log(`Processing batch of ${paths.length} files to check if they are untracked.`);
|
||||
|
||||
const orphanedFiles = new Set<string>(paths);
|
||||
const untrackedFiles = new Set<string>(paths);
|
||||
if (type === 'asset') {
|
||||
const assets = await this.integrityRepository.getAssetPathsByPaths(paths);
|
||||
for (const { originalPath, encodedVideoPath } of assets) {
|
||||
orphanedFiles.delete(originalPath);
|
||||
untrackedFiles.delete(originalPath);
|
||||
|
||||
if (encodedVideoPath) {
|
||||
orphanedFiles.delete(encodedVideoPath);
|
||||
untrackedFiles.delete(encodedVideoPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const assets = await this.integrityRepository.getAssetFilePathsByPaths(paths);
|
||||
for (const { path } of assets) {
|
||||
orphanedFiles.delete(path);
|
||||
untrackedFiles.delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (orphanedFiles.size > 0) {
|
||||
if (untrackedFiles.size > 0) {
|
||||
await this.integrityRepository.create(
|
||||
[...orphanedFiles].map((path) => ({
|
||||
type: IntegrityReportType.OrphanFile,
|
||||
[...untrackedFiles].map((path) => ({
|
||||
type: IntegrityReportType.UntrackedFile,
|
||||
path,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(`Processed ${paths.length} and found ${orphanedFiles.size} orphaned file(s).`);
|
||||
this.logger.log(`Processed ${paths.length} and found ${untrackedFiles.size} untracked file(s).`);
|
||||
return JobStatus.Success;
|
||||
}
|
||||
|
||||
@OnJob({ name: JobName.IntegrityOrphanedFilesRefresh, queue: QueueName.IntegrityCheck })
|
||||
async handleOrphanedRefresh({ items }: IIntegrityPathWithReportJob): Promise<JobStatus> {
|
||||
@OnJob({ name: JobName.IntegrityUntrackedFilesRefresh, queue: QueueName.IntegrityCheck })
|
||||
async handleUntrackedRefresh({ items }: IIntegrityPathWithReportJob): Promise<JobStatus> {
|
||||
this.logger.log(`Processing batch of ${items.length} reports to check if they are out of date.`);
|
||||
|
||||
const results = await Promise.all(
|
||||
@@ -619,7 +619,7 @@ export class IntegrityService extends BaseService {
|
||||
properties = ['assetId', 'fileAssetId'] as const;
|
||||
break;
|
||||
}
|
||||
case IntegrityReportType.OrphanFile: {
|
||||
case IntegrityReportType.UntrackedFile: {
|
||||
properties = [void 0] as const;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ const asJobItem = (dto: JobCreateDto): JobItem => {
|
||||
return { name: JobName.IntegrityMissingFilesQueueAll };
|
||||
}
|
||||
|
||||
case ManualJobName.IntegrityOrphanFiles: {
|
||||
return { name: JobName.IntegrityOrphanedFilesQueueAll };
|
||||
case ManualJobName.IntegrityUntrackedFiles: {
|
||||
return { name: JobName.IntegrityUntrackedFilesQueueAll };
|
||||
}
|
||||
|
||||
case ManualJobName.IntegrityChecksumFiles: {
|
||||
@@ -50,8 +50,8 @@ const asJobItem = (dto: JobCreateDto): JobItem => {
|
||||
return { name: JobName.IntegrityMissingFilesQueueAll, data: { refreshOnly: true } };
|
||||
}
|
||||
|
||||
case ManualJobName.IntegrityOrphanFilesRefresh: {
|
||||
return { name: JobName.IntegrityOrphanedFilesQueueAll, data: { refreshOnly: true } };
|
||||
case ManualJobName.IntegrityUntrackedFilesRefresh: {
|
||||
return { name: JobName.IntegrityUntrackedFilesQueueAll, data: { refreshOnly: true } };
|
||||
}
|
||||
|
||||
case ManualJobName.IntegrityChecksumFilesRefresh: {
|
||||
@@ -62,8 +62,8 @@ const asJobItem = (dto: JobCreateDto): JobItem => {
|
||||
return { name: JobName.IntegrityDeleteReportType, data: { type: IntegrityReportType.MissingFile } };
|
||||
}
|
||||
|
||||
case ManualJobName.IntegrityOrphanFilesDeleteAll: {
|
||||
return { name: JobName.IntegrityDeleteReportType, data: { type: IntegrityReportType.OrphanFile } };
|
||||
case ManualJobName.IntegrityUntrackedFilesDeleteAll: {
|
||||
return { name: JobName.IntegrityDeleteReportType, data: { type: IntegrityReportType.UntrackedFile } };
|
||||
}
|
||||
|
||||
case ManualJobName.IntegrityChecksumFilesDeleteAll: {
|
||||
|
||||
@@ -74,7 +74,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
|
||||
tonemap: ToneMapping.Hable,
|
||||
},
|
||||
integrityChecks: {
|
||||
orphanedFiles: {
|
||||
untrackedFiles: {
|
||||
enabled: true,
|
||||
cronExpression: '0 03 * * *',
|
||||
},
|
||||
|
||||
@@ -294,7 +294,7 @@ export interface IIntegrityDeleteReportsJob {
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface IIntegrityOrphanedFilesJob {
|
||||
export interface IIntegrityUntrackedFilesJob {
|
||||
type: 'asset' | 'asset_file';
|
||||
paths: string[];
|
||||
}
|
||||
@@ -428,9 +428,9 @@ export type JobItem =
|
||||
| { name: JobName.WorkflowRun; data: IWorkflowJob }
|
||||
|
||||
// Integrity
|
||||
| { name: JobName.IntegrityOrphanedFilesQueueAll; data?: IIntegrityJob }
|
||||
| { name: JobName.IntegrityOrphanedFiles; data: IIntegrityOrphanedFilesJob }
|
||||
| { name: JobName.IntegrityOrphanedFilesRefresh; data: IIntegrityPathWithReportJob }
|
||||
| { name: JobName.IntegrityUntrackedFilesQueueAll; data?: IIntegrityJob }
|
||||
| { name: JobName.IntegrityUntrackedFiles; data: IIntegrityUntrackedFilesJob }
|
||||
| { name: JobName.IntegrityUntrackedFilesRefresh; data: IIntegrityPathWithReportJob }
|
||||
| { name: JobName.IntegrityMissingFilesQueueAll; data?: IIntegrityJob }
|
||||
| { name: JobName.IntegrityMissingFiles; data: IIntegrityPathWithReportJob }
|
||||
| { name: JobName.IntegrityMissingFilesRefresh; data: IIntegrityPathWithReportJob }
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
value: ManualJobName.IntegrityMissingFiles,
|
||||
},
|
||||
{
|
||||
title: $t('admin.maintenance_integrity_orphan_file_job'),
|
||||
value: ManualJobName.IntegrityOrphanFiles,
|
||||
title: $t('admin.maintenance_integrity_untracked_file_job'),
|
||||
value: ManualJobName.IntegrityUntrackedFiles,
|
||||
},
|
||||
{
|
||||
title: $t('admin.maintenance_integrity_checksum_mismatch_job'),
|
||||
@@ -33,8 +33,8 @@
|
||||
value: ManualJobName.IntegrityMissingFilesRefresh,
|
||||
},
|
||||
{
|
||||
title: $t('admin.maintenance_integrity_orphan_file_refresh_job'),
|
||||
value: ManualJobName.IntegrityOrphanFilesRefresh,
|
||||
title: $t('admin.maintenance_integrity_untracked_file_refresh_job'),
|
||||
value: ManualJobName.IntegrityUntrackedFilesRefresh,
|
||||
},
|
||||
{
|
||||
title: $t('admin.maintenance_integrity_checksum_mismatch_refresh_job'),
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
let integrityReport: IntegrityReportSummaryResponseDto = $state(data.integrityReport);
|
||||
|
||||
const TYPES: IntegrityReportType[] = [
|
||||
IntegrityReportType.OrphanFile,
|
||||
IntegrityReportType.UntrackedFile,
|
||||
IntegrityReportType.MissingFile,
|
||||
IntegrityReportType.ChecksumMismatch,
|
||||
];
|
||||
@@ -53,8 +53,8 @@
|
||||
async function runJob(reportType: IntegrityReportType, refreshOnly?: boolean) {
|
||||
let name: ManualJobName;
|
||||
switch (reportType) {
|
||||
case IntegrityReportType.OrphanFile: {
|
||||
name = refreshOnly ? ManualJobName.IntegrityOrphanFilesRefresh : ManualJobName.IntegrityOrphanFiles;
|
||||
case IntegrityReportType.UntrackedFile: {
|
||||
name = refreshOnly ? ManualJobName.IntegrityUntrackedFilesRefresh : ManualJobName.IntegrityUntrackedFiles;
|
||||
break;
|
||||
}
|
||||
case IntegrityReportType.MissingFile: {
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
if (confirm) {
|
||||
let name: ManualJobName;
|
||||
switch (data.type) {
|
||||
case IntegrityReportType.OrphanFile: {
|
||||
name = ManualJobName.IntegrityOrphanFilesDeleteAll;
|
||||
case IntegrityReportType.UntrackedFile: {
|
||||
name = ManualJobName.IntegrityUntrackedFilesDeleteAll;
|
||||
break;
|
||||
}
|
||||
case IntegrityReportType.MissingFile: {
|
||||
@@ -108,7 +108,7 @@
|
||||
const handleOpen = async (event: Event, props: Partial<ContextMenuBaseProps>, reportId: string) => {
|
||||
const items: MenuItems = [];
|
||||
|
||||
if (data.type === IntegrityReportType.OrphanFile || data.type === IntegrityReportType.ChecksumMismatch) {
|
||||
if (data.type === IntegrityReportType.UntrackedFile || data.type === IntegrityReportType.ChecksumMismatch) {
|
||||
items.push({
|
||||
title: $t('download'),
|
||||
icon: mdiDownload,
|
||||
|
||||
Reference in New Issue
Block a user