1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-01-08 04:03:48 +02:00

Fix broken job scheduling. Also add UI indicator for active timer #725

This commit is contained in:
Patrik J. Braun 2023-09-20 18:16:41 +02:00
parent eabb39d7b0
commit 55822c5cd8
6 changed files with 77 additions and 64 deletions

View File

@ -5,6 +5,7 @@ import {Config} from '../../../common/config/private/Config';
import {ConfigDiagnostics} from '../../model/diagnostics/ConfigDiagnostics';
import {ConfigClassBuilder} from '../../../../node_modules/typeconfig/node';
import {TAGS} from '../../../common/config/public/ClientConfig';
import {ObjectManagers} from '../../model/ObjectManagers';
const LOG_TAG = '[SettingsMWs]';
@ -47,6 +48,8 @@ export class SettingsMWs {
Config[settingsPath] = settings;
await original.save();
await ConfigDiagnostics.runDiagnostics();
// restart all schedule timers. In case they have changed
ObjectManagers.getInstance().JobManager.runSchedules();
Logger.info(LOG_TAG, 'new config:');
Logger.info(LOG_TAG, JSON.stringify(Config.toJSON({attachDescription: false}), null, '\t'));
return next();

View File

@ -1,4 +1,4 @@
import {JobProgressDTO, JobProgressStates,} from '../../../common/entities/job/JobProgressDTO';
import {JobProgressStates, OnTimerJobProgressDTO,} from '../../../common/entities/job/JobProgressDTO';
import {IJob} from './jobs/IJob';
import {JobRepository} from './JobRepository';
import {Config} from '../../../common/config/private/Config';
@ -8,6 +8,8 @@ import {NotificationManager} from '../NotifocationManager';
import {IJobListener} from './jobs/IJobListener';
import {JobProgress} from './jobs/JobProgress';
import {JobProgressManager} from './JobProgressManager';
import {JobDTOUtils} from '../../../common/entities/job/JobDTO';
import {Utils} from '../../../common/Utils';
const LOG_TAG = '[JobManager]';
@ -22,33 +24,35 @@ export class JobManager implements IJobListener {
protected get JobRunning(): boolean {
return (
JobRepository.Instance.getAvailableJobs().findIndex(
(j): boolean => j.InProgress === true
) !== -1
JobRepository.Instance.getAvailableJobs().findIndex(
(j): boolean => j.InProgress === true
) !== -1
);
}
protected get JobNoParallelRunning(): boolean {
return (
JobRepository.Instance.getAvailableJobs().findIndex(
(j): boolean => j.InProgress === true && j.allowParallelRun
) !== -1
JobRepository.Instance.getAvailableJobs().findIndex(
(j): boolean => j.InProgress === true && j.allowParallelRun
) !== -1
);
}
getProgresses(): { [id: string]: JobProgressDTO } {
return this.progressManager.Progresses;
public getProgresses(): { [id: string]: OnTimerJobProgressDTO } {
const prg = Utils.clone(this.progressManager.Progresses);
this.timers.forEach(t => (prg[JobDTOUtils.getHashName(t.schedule.jobName, t.schedule.config)] as OnTimerJobProgressDTO).onTimer = true);
return prg;
}
async run<T>(
jobName: string,
config: T,
soloRun: boolean,
allowParallelRun: boolean
public async run<T>(
jobName: string,
config: T,
soloRun: boolean,
allowParallelRun: boolean
): Promise<void> {
if (
(allowParallelRun === false && this.JobRunning === true) ||
this.JobNoParallelRunning === true
(allowParallelRun === false && this.JobRunning === true) ||
this.JobNoParallelRunning === true
) {
throw new Error('Can\'t start this job while another is running');
}
@ -62,7 +66,7 @@ export class JobManager implements IJobListener {
}
}
stop(jobName: string): void {
public stop(jobName: string): void {
const t = this.findJob(jobName);
if (t) {
t.cancel();
@ -71,40 +75,40 @@ export class JobManager implements IJobListener {
}
}
onProgressUpdate = (progress: JobProgress): void => {
public onProgressUpdate = (progress: JobProgress): void => {
this.progressManager.onJobProgressUpdate(progress.toDTO());
};
onJobFinished = async (
job: IJob<unknown>,
state: JobProgressStates,
soloRun: boolean
job: IJob<unknown>,
state: JobProgressStates,
soloRun: boolean
): Promise<void> => {
// if it was not finished peacefully or was a soloRun, do not start the next one
if (state !== JobProgressStates.finished || soloRun === true) {
return;
}
const sch = Config.Jobs.scheduled.find(
(s): boolean => s.jobName === job.Name
(s): boolean => s.jobName === job.Name
);
if (sch) {
const children = Config.Jobs.scheduled.filter(
(s): boolean =>
s.trigger.type === JobTriggerType.after &&
(s.trigger as AfterJobTrigger).afterScheduleName === sch.name
(s): boolean =>
s.trigger.type === JobTriggerType.after &&
(s.trigger as AfterJobTrigger).afterScheduleName === sch.name
);
for (const item of children) {
try {
await this.run(
item.jobName,
item.config,
false,
item.allowParallelRun
item.jobName,
item.config,
false,
item.allowParallelRun
);
} catch (e) {
NotificationManager.warning(
'Job running error:' + item.name,
e.toString()
'Job running error:' + item.name,
e.toString()
);
}
}
@ -138,25 +142,25 @@ export class JobManager implements IJobListener {
*/
private runSchedule(schedule: JobScheduleDTO): void {
const nextDate = JobScheduleDTOUtils.getNextRunningDate(
new Date(),
schedule
new Date(),
schedule
);
if (nextDate && nextDate.getTime() > Date.now()) {
Logger.debug(
LOG_TAG,
'running schedule: ' +
schedule.jobName +
' at ' +
nextDate.toLocaleString(undefined, {hour12: false})
LOG_TAG,
'running schedule: ' +
schedule.jobName +
' at ' +
nextDate.toLocaleString(undefined, {hour12: false})
);
const timer: NodeJS.Timeout = setTimeout(async (): Promise<void> => {
this.timers = this.timers.filter((t): boolean => t.timer !== timer);
await this.run(
schedule.jobName,
schedule.config,
false,
schedule.allowParallelRun
schedule.jobName,
schedule.config,
false,
schedule.allowParallelRun
);
this.runSchedule(schedule);
}, nextDate.getTime() - Date.now());

View File

@ -28,3 +28,8 @@ export interface JobProgressDTO {
end: number;
};
}
export interface OnTimerJobProgressDTO extends JobProgressDTO {
onTimer?: boolean; // indicates if there is an active timer set for the job
}

View File

@ -1,6 +1,6 @@
import {EventEmitter, Injectable} from '@angular/core';
import {BehaviorSubject} from 'rxjs';
import {JobProgressDTO, JobProgressStates,} from '../../../../common/entities/job/JobProgressDTO';
import {JobProgressDTO, JobProgressStates, OnTimerJobProgressDTO,} from '../../../../common/entities/job/JobProgressDTO';
import {NetworkService} from '../../model/network/network.service';
import {JobScheduleDTO} from '../../../../common/entities/job/JobScheduleDTO';
import {ConfigTemplateEntry, JobDTO, JobDTOUtils} from '../../../../common/entities/job/JobDTO';
@ -9,7 +9,7 @@ import {NotificationService} from '../../model/notification.service';
@Injectable()
export class ScheduledJobsService {
public progress: BehaviorSubject<Record<string, JobProgressDTO>>;
public progress: BehaviorSubject<Record<string, OnTimerJobProgressDTO>>;
public onJobFinish: EventEmitter<string> = new EventEmitter<string>();
timer: number = null;
public availableJobs: BehaviorSubject<JobDTO[]>;

View File

@ -1,4 +1,5 @@
<button class="btn {{danger ? 'btn-danger': 'btn-success'}}"
[class.progress-bar-striped]="Progress?.onTimer"
title="Trigger job run manually"
i18n-title
*ngIf="!Running"

View File

@ -1,5 +1,5 @@
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {JobProgressDTO, JobProgressStates,} from '../../../../../../common/entities/job/JobProgressDTO';
import {JobProgressStates, OnTimerJobProgressDTO,} from '../../../../../../common/entities/job/JobProgressDTO';
import {ErrorDTO} from '../../../../../../common/entities/Error';
import {ScheduledJobsService} from '../../scheduled-jobs.service';
import {NotificationService} from '../../../../model/notification.service';
@ -23,9 +23,9 @@ export class JobButtonComponent {
@Output() jobError = new EventEmitter<string>();
constructor(
private notification: NotificationService,
public jobsService: ScheduledJobsService,
public backendTextService: BackendtextService
private notification: NotificationService,
public jobsService: ScheduledJobsService,
public backendTextService: BackendtextService
) {
}
@ -41,17 +41,17 @@ export class JobButtonComponent {
public get Running(): boolean {
return (
this.Progress &&
(this.Progress.state === JobProgressStates.running ||
this.Progress.state === JobProgressStates.cancelling)
this.Progress &&
(this.Progress.state === JobProgressStates.running ||
this.Progress.state === JobProgressStates.cancelling)
);
}
get Progress(): JobProgressDTO {
get Progress(): OnTimerJobProgressDTO {
this.populateConfig();
return this.jobsService.progress.value[
JobDTOUtils.getHashName(this.jobName, this.config)
];
JobDTOUtils.getHashName(this.jobName, this.config)
];
}
public async start(): Promise<boolean> {
@ -59,15 +59,15 @@ export class JobButtonComponent {
this.populateConfig();
try {
await this.jobsService.start(
this.jobName,
this.config,
this.soloRun,
this.allowParallelRun
this.jobName,
this.config,
this.soloRun,
this.allowParallelRun
);
this.notification.success(
$localize`Job started` +
': ' +
this.backendTextService.getJobName(this.jobName)
$localize`Job started` +
': ' +
this.backendTextService.getJobName(this.jobName)
);
return true;
} catch (err) {
@ -85,9 +85,9 @@ export class JobButtonComponent {
try {
await this.jobsService.stop(this.jobName);
this.notification.info(
$localize`Stopping job` +
': ' +
this.backendTextService.getJobName(this.jobName)
$localize`Stopping job` +
': ' +
this.backendTextService.getJobName(this.jobName)
);
return true;
} catch (err) {