1
0
mirror of https://github.com/immich-app/immich.git synced 2025-06-19 04:07:43 +02:00

refactor(server): jobs (#2023)

* refactor: job to domain

* chore: regenerate open api

* chore: tests

* fix: missing breaks

* fix: get asset with missing exif data

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Jason Rasmussen
2023-03-20 11:55:28 -04:00
committed by GitHub
parent db6b14361d
commit 386eef046d
68 changed files with 1355 additions and 907 deletions

View File

@ -291,34 +291,52 @@ export interface AlbumResponseDto {
export interface AllJobStatusResponseDto {
/**
*
* @type {JobCounts}
* @type {JobCountsDto}
* @memberof AllJobStatusResponseDto
*/
'thumbnail-generation': JobCounts;
'thumbnail-generation-queue': JobCountsDto;
/**
*
* @type {JobCounts}
* @type {JobCountsDto}
* @memberof AllJobStatusResponseDto
*/
'metadata-extraction': JobCounts;
'metadata-extraction-queue': JobCountsDto;
/**
*
* @type {JobCounts}
* @type {JobCountsDto}
* @memberof AllJobStatusResponseDto
*/
'video-conversion': JobCounts;
'video-conversion-queue': JobCountsDto;
/**
*
* @type {JobCounts}
* @type {JobCountsDto}
* @memberof AllJobStatusResponseDto
*/
'machine-learning': JobCounts;
'object-tagging-queue': JobCountsDto;
/**
*
* @type {JobCounts}
* @type {JobCountsDto}
* @memberof AllJobStatusResponseDto
*/
'storage-template-migration': JobCounts;
'clip-encoding-queue': JobCountsDto;
/**
*
* @type {JobCountsDto}
* @memberof AllJobStatusResponseDto
*/
'storage-template-migration-queue': JobCountsDto;
/**
*
* @type {JobCountsDto}
* @memberof AllJobStatusResponseDto
*/
'background-task-queue': JobCountsDto;
/**
*
* @type {JobCountsDto}
* @memberof AllJobStatusResponseDto
*/
'search-queue': JobCountsDto;
}
/**
*
@ -1203,7 +1221,8 @@ export interface GetAssetCountByTimeBucketDto {
export const JobCommand = {
Start: 'start',
Stop: 'stop'
Pause: 'pause',
Empty: 'empty'
} as const;
export type JobCommand = typeof JobCommand[keyof typeof JobCommand];
@ -1226,42 +1245,42 @@ export interface JobCommandDto {
* @type {boolean}
* @memberof JobCommandDto
*/
'includeAllAssets': boolean;
'force': boolean;
}
/**
*
* @export
* @interface JobCounts
* @interface JobCountsDto
*/
export interface JobCounts {
export interface JobCountsDto {
/**
*
* @type {number}
* @memberof JobCounts
* @memberof JobCountsDto
*/
'active': number;
/**
*
* @type {number}
* @memberof JobCounts
* @memberof JobCountsDto
*/
'completed': number;
/**
*
* @type {number}
* @memberof JobCounts
* @memberof JobCountsDto
*/
'failed': number;
/**
*
* @type {number}
* @memberof JobCounts
* @memberof JobCountsDto
*/
'delayed': number;
/**
*
* @type {number}
* @memberof JobCounts
* @memberof JobCountsDto
*/
'waiting': number;
}
@ -1271,15 +1290,18 @@ export interface JobCounts {
* @enum {string}
*/
export const JobId = {
ThumbnailGeneration: 'thumbnail-generation',
MetadataExtraction: 'metadata-extraction',
VideoConversion: 'video-conversion',
MachineLearning: 'machine-learning',
StorageTemplateMigration: 'storage-template-migration'
export const JobName = {
ThumbnailGenerationQueue: 'thumbnail-generation-queue',
MetadataExtractionQueue: 'metadata-extraction-queue',
VideoConversionQueue: 'video-conversion-queue',
ObjectTaggingQueue: 'object-tagging-queue',
ClipEncodingQueue: 'clip-encoding-queue',
BackgroundTaskQueue: 'background-task-queue',
StorageTemplateMigrationQueue: 'storage-template-migration-queue',
SearchQueue: 'search-queue'
} as const;
export type JobId = typeof JobId[keyof typeof JobId];
export type JobName = typeof JobName[keyof typeof JobName];
/**
@ -6169,12 +6191,12 @@ export const JobApiAxiosParamCreator = function (configuration?: Configuration)
},
/**
*
* @param {JobId} jobId
* @param {JobName} jobId
* @param {JobCommandDto} jobCommandDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
sendJobCommand: async (jobId: JobId, jobCommandDto: JobCommandDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
sendJobCommand: async (jobId: JobName, jobCommandDto: JobCommandDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'jobId' is not null or undefined
assertParamExists('sendJobCommand', 'jobId', jobId)
// verify required parameter 'jobCommandDto' is not null or undefined
@ -6233,12 +6255,12 @@ export const JobApiFp = function(configuration?: Configuration) {
},
/**
*
* @param {JobId} jobId
* @param {JobName} jobId
* @param {JobCommandDto} jobCommandDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async sendJobCommand(jobId: JobId, jobCommandDto: JobCommandDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<number>> {
async sendJobCommand(jobId: JobName, jobCommandDto: JobCommandDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.sendJobCommand(jobId, jobCommandDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
@ -6262,12 +6284,12 @@ export const JobApiFactory = function (configuration?: Configuration, basePath?:
},
/**
*
* @param {JobId} jobId
* @param {JobName} jobId
* @param {JobCommandDto} jobCommandDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
sendJobCommand(jobId: JobId, jobCommandDto: JobCommandDto, options?: any): AxiosPromise<number> {
sendJobCommand(jobId: JobName, jobCommandDto: JobCommandDto, options?: any): AxiosPromise<void> {
return localVarFp.sendJobCommand(jobId, jobCommandDto, options).then((request) => request(axios, basePath));
},
};
@ -6292,13 +6314,13 @@ export class JobApi extends BaseAPI {
/**
*
* @param {JobId} jobId
* @param {JobName} jobId
* @param {JobCommandDto} jobCommandDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof JobApi
*/
public sendJobCommand(jobId: JobId, jobCommandDto: JobCommandDto, options?: AxiosRequestConfig) {
public sendJobCommand(jobId: JobName, jobCommandDto: JobCommandDto, options?: AxiosRequestConfig) {
return JobApiFp(this.configuration).sendJobCommand(jobId, jobCommandDto, options).then((request) => request(this.axios, this.basePath));
}
}

View File

@ -5,11 +5,11 @@
import AllInclusive from 'svelte-material-icons/AllInclusive.svelte';
import { locale } from '$lib/stores/preferences.store';
import { createEventDispatcher } from 'svelte';
import { JobCounts } from '@api';
import { JobCountsDto } from '@api';
export let title: string;
export let subtitle: string;
export let jobCounts: JobCounts;
export let jobCounts: JobCountsDto;
/**
* Show options to run job on all assets of just missing ones
*/
@ -19,8 +19,8 @@
const dispatch = createEventDispatcher();
const run = (includeAllAssets: boolean) => {
dispatch('click', { includeAllAssets });
const run = (force: boolean) => {
dispatch('click', { force });
};
</script>

View File

@ -4,7 +4,7 @@
NotificationType
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import { AllJobStatusResponseDto, api, JobCommand, JobId } from '@api';
import { AllJobStatusResponseDto, api, JobCommand, JobName } from '@api';
import { onDestroy, onMount } from 'svelte';
import JobTile from './job-tile.svelte';
@ -18,35 +18,42 @@
onMount(async () => {
await load();
timer = setInterval(async () => await load(), 1_000);
timer = setInterval(async () => await load(), 5_000);
});
onDestroy(() => {
clearInterval(timer);
});
const run = async (
jobId: JobId,
jobName: string,
emptyMessage: string,
includeAllAssets: boolean
) => {
try {
const { data } = await api.jobApi.sendJobCommand(jobId, {
command: JobCommand.Start,
includeAllAssets
});
function getJobLabel(jobName: JobName) {
const names: Record<JobName, string> = {
[JobName.ThumbnailGenerationQueue]: 'Generate Thumbnails',
[JobName.MetadataExtractionQueue]: 'Extract Metadata',
[JobName.VideoConversionQueue]: 'Transcode Videos',
[JobName.ObjectTaggingQueue]: 'Tag Objects',
[JobName.ClipEncodingQueue]: 'Clip Encoding',
[JobName.BackgroundTaskQueue]: 'Background Task',
[JobName.StorageTemplateMigrationQueue]: 'Storage Template Migration',
[JobName.SearchQueue]: 'Search'
};
if (data) {
notificationController.show({
message: includeAllAssets ? `Started ${jobName} for all assets` : `Started ${jobName}`,
type: NotificationType.Info
});
} else {
notificationController.show({ message: emptyMessage, type: NotificationType.Info });
}
return names[jobName];
}
const start = async (jobId: JobName, force: boolean) => {
const label = getJobLabel(jobId);
try {
await api.jobApi.sendJobCommand(jobId, { command: JobCommand.Start, force });
jobs[jobId].active += 1;
notificationController.show({
message: `Started job: ${label}`,
type: NotificationType.Info
});
} catch (error) {
handleError(error, `Unable to start ${jobName}`);
handleError(error, `Unable to start job: ${label}`);
}
};
</script>
@ -54,76 +61,48 @@
<div class="flex flex-col gap-7">
{#if jobs}
<JobTile
title={'Generate thumbnails'}
subtitle={'Regenerate JPEG and WebP thumbnails'}
on:click={(e) => {
const { includeAllAssets } = e.detail;
run(
JobId.ThumbnailGeneration,
'thumbnail generation',
'No missing thumbnails found',
includeAllAssets
);
}}
jobCounts={jobs[JobId.ThumbnailGeneration]}
title="Generate thumbnails"
subtitle="Regenerate JPEG and WebP thumbnails"
on:click={(e) => start(JobName.ThumbnailGenerationQueue, e.detail.force)}
jobCounts={jobs[JobName.ThumbnailGenerationQueue]}
/>
<JobTile
title={'EXTRACT METADATA'}
subtitle={'Extract metadata information i.e. GPS, resolution...etc'}
on:click={(e) => {
const { includeAllAssets } = e.detail;
run(JobId.MetadataExtraction, 'extract EXIF', 'No missing EXIF found', includeAllAssets);
}}
jobCounts={jobs[JobId.MetadataExtraction]}
title="Extract Metadata"
subtitle="Extract metadata information i.e. GPS, resolution...etc"
on:click={(e) => start(JobName.MetadataExtractionQueue, e.detail.force)}
jobCounts={jobs[JobName.MetadataExtractionQueue]}
/>
<JobTile
title={'Detect objects'}
subtitle={'Run machine learning process to detect and classify objects'}
on:click={(e) => {
const { includeAllAssets } = e.detail;
run(
JobId.MachineLearning,
'object detection',
'No missing object detection found',
includeAllAssets
);
}}
jobCounts={jobs[JobId.MachineLearning]}
title="Tag Objects"
subtitle="Run machine learning to tag objects"
on:click={(e) => start(JobName.ObjectTaggingQueue, e.detail.force)}
jobCounts={jobs[JobName.ObjectTaggingQueue]}
>
Note that some assets may not have any objects detected
</JobTile>
<JobTile
title={'Video transcoding'}
subtitle={'Transcode videos not in the desired format'}
on:click={(e) => {
const { includeAllAssets } = e.detail;
run(
JobId.VideoConversion,
'video conversion',
'No videos without an encoded version found',
includeAllAssets
);
}}
jobCounts={jobs[JobId.VideoConversion]}
title="Encode Clip"
subtitle="Run machine learning to generate clip embeddings"
on:click={(e) => start(JobName.ClipEncodingQueue, e.detail.force)}
jobCounts={jobs[JobName.ClipEncodingQueue]}
/>
<JobTile
title={'Storage migration'}
title="Transcode Videos"
subtitle="Transcode videos not in the desired format"
on:click={(e) => start(JobName.VideoConversionQueue, e.detail.force)}
jobCounts={jobs[JobName.VideoConversionQueue]}
/>
<JobTile
title="Storage migration"
showOptions={false}
subtitle={''}
on:click={() =>
run(
JobId.StorageTemplateMigration,
'storage template migration',
'All files have been migrated to the new storage template',
false
)}
jobCounts={jobs[JobId.StorageTemplateMigration]}
on:click={(e) => start(JobName.StorageTemplateMigrationQueue, e.detail.force)}
jobCounts={jobs[JobName.StorageTemplateMigrationQueue]}
>
Apply the current
<a