2020-10-07 08:53:19 +00:00
|
|
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
import { Injectable, NgZone } from '@angular/core';
|
|
|
|
|
2020-12-01 17:35:07 +00:00
|
|
|
import { CoreApp } from '@services/app';
|
2020-10-07 08:53:19 +00:00
|
|
|
import { CoreConfig } from '@services/config';
|
|
|
|
import { CoreUtils } from '@services/utils/utils';
|
2020-11-19 11:40:18 +00:00
|
|
|
import { CoreConstants } from '@/core/constants';
|
2020-10-07 08:53:19 +00:00
|
|
|
import { SQLiteDB } from '@classes/sqlitedb';
|
2020-10-14 06:29:45 +00:00
|
|
|
import { CoreError } from '@classes/errors/error';
|
2020-10-07 08:53:19 +00:00
|
|
|
|
2020-11-24 08:31:11 +00:00
|
|
|
import { makeSingleton, Network } from '@singletons';
|
2020-10-07 08:53:19 +00:00
|
|
|
import { CoreLogger } from '@singletons/logger';
|
2020-11-24 08:31:11 +00:00
|
|
|
import { APP_SCHEMA, CRON_TABLE_NAME, CronDBEntry } from '@services/db/cron';
|
2020-10-07 08:53:19 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Service to handle cron processes. The registered processes will be executed every certain time.
|
|
|
|
*/
|
2020-11-19 15:35:17 +00:00
|
|
|
@Injectable({ providedIn: 'root' })
|
2020-10-07 08:53:19 +00:00
|
|
|
export class CoreCronDelegate {
|
2020-10-14 06:29:45 +00:00
|
|
|
|
2020-10-07 08:53:19 +00:00
|
|
|
// Constants.
|
2020-10-14 06:29:45 +00:00
|
|
|
static readonly DEFAULT_INTERVAL = 3600000; // Default interval is 1 hour.
|
|
|
|
static readonly MIN_INTERVAL = 300000; // Minimum interval is 5 minutes.
|
|
|
|
static readonly MAX_TIME_PROCESS = 120000; // Max time a process can block the queue. Defaults to 2 minutes.
|
2020-10-07 08:53:19 +00:00
|
|
|
|
2020-10-14 06:29:45 +00:00
|
|
|
protected logger: CoreLogger;
|
2020-10-07 08:53:19 +00:00
|
|
|
protected appDB: SQLiteDB;
|
2020-10-14 06:29:45 +00:00
|
|
|
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
|
2020-10-07 08:53:19 +00:00
|
|
|
protected handlers: { [s: string]: CoreCronHandler } = {};
|
2020-10-14 14:38:24 +00:00
|
|
|
protected queuePromise: Promise<void> = Promise.resolve();
|
2020-10-07 08:53:19 +00:00
|
|
|
|
|
|
|
constructor(zone: NgZone) {
|
|
|
|
this.logger = CoreLogger.getInstance('CoreCronDelegate');
|
|
|
|
|
|
|
|
this.appDB = CoreApp.instance.getDB();
|
2020-10-28 13:25:18 +00:00
|
|
|
this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => {
|
2020-10-07 08:53:19 +00:00
|
|
|
// Ignore errors.
|
|
|
|
});
|
|
|
|
|
|
|
|
// When the app is re-connected, start network handlers that were stopped.
|
|
|
|
Network.instance.onConnect().subscribe(() => {
|
|
|
|
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
|
|
|
zone.run(() => {
|
|
|
|
this.startNetworkHandlers();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Try to execute a handler. It will schedule the next execution once done.
|
|
|
|
* If the handler cannot be executed or it fails, it will be re-executed after mmCoreCronMinInterval.
|
|
|
|
*
|
|
|
|
* @param name Name of the handler.
|
|
|
|
* @param force Wether the execution is forced (manual sync).
|
|
|
|
* @param siteId Site ID. If not defined, all sites.
|
|
|
|
* @return Promise resolved if handler is executed successfully, rejected otherwise.
|
|
|
|
*/
|
2020-10-21 14:32:27 +00:00
|
|
|
protected async checkAndExecuteHandler(name: string, force?: boolean, siteId?: string): Promise<void> {
|
2020-10-07 08:53:19 +00:00
|
|
|
if (!this.handlers[name] || !this.handlers[name].execute) {
|
|
|
|
// Invalid handler.
|
2020-10-14 06:29:45 +00:00
|
|
|
const message = `Cannot execute handler because is invalid: ${name}`;
|
|
|
|
this.logger.debug(message);
|
2020-10-07 08:53:19 +00:00
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
throw new CoreError(message);
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const usesNetwork = this.handlerUsesNetwork(name);
|
|
|
|
const isSync = !force && this.isHandlerSync(name);
|
|
|
|
|
|
|
|
if (usesNetwork && !CoreApp.instance.isOnline()) {
|
|
|
|
// Offline, stop executing.
|
2020-10-14 06:29:45 +00:00
|
|
|
const message = `Cannot execute handler because device is offline: ${name}`;
|
|
|
|
this.logger.debug(message);
|
2020-10-07 08:53:19 +00:00
|
|
|
this.stopHandler(name);
|
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
throw new CoreError(message);
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isSync) {
|
|
|
|
// Check network connection.
|
2020-10-21 14:32:27 +00:00
|
|
|
const syncOnlyOnWifi = await CoreConfig.instance.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, false);
|
2020-10-07 08:53:19 +00:00
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
if (syncOnlyOnWifi && !CoreApp.instance.isWifi()) {
|
2020-10-07 08:53:19 +00:00
|
|
|
// Cannot execute in this network connection, retry soon.
|
2020-10-14 06:29:45 +00:00
|
|
|
const message = `Cannot execute handler because device is using limited connection: ${name}`;
|
|
|
|
this.logger.debug(message);
|
2020-10-07 08:53:19 +00:00
|
|
|
this.scheduleNextExecution(name, CoreCronDelegate.MIN_INTERVAL);
|
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
throw new CoreError(message);
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
2020-10-21 14:32:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add the execution to the queue.
|
|
|
|
this.queuePromise = CoreUtils.instance.ignoreErrors(this.queuePromise).then(async () => {
|
|
|
|
try {
|
|
|
|
await this.executeHandler(name, force, siteId);
|
2020-10-07 08:53:19 +00:00
|
|
|
|
2020-10-14 06:29:45 +00:00
|
|
|
this.logger.debug(`Execution of handler '${name}' was a success.`);
|
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
await CoreUtils.instance.ignoreErrors(this.setHandlerLastExecutionTime(name, Date.now()));
|
|
|
|
|
|
|
|
this.scheduleNextExecution(name);
|
|
|
|
|
|
|
|
return;
|
|
|
|
} catch (error) {
|
2020-10-14 06:29:45 +00:00
|
|
|
// Handler call failed. Retry soon.
|
|
|
|
const message = `Execution of handler '${name}' failed.`;
|
|
|
|
this.logger.error(message, error);
|
|
|
|
this.scheduleNextExecution(name, CoreCronDelegate.MIN_INTERVAL);
|
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
throw new CoreError(message);
|
|
|
|
}
|
2020-10-07 08:53:19 +00:00
|
|
|
});
|
2020-10-21 14:32:27 +00:00
|
|
|
|
|
|
|
return this.queuePromise;
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Run a handler, cancelling the execution if it takes more than MAX_TIME_PROCESS.
|
|
|
|
*
|
|
|
|
* @param name Name of the handler.
|
|
|
|
* @param force Wether the execution is forced (manual sync).
|
|
|
|
* @param siteId Site ID. If not defined, all sites.
|
|
|
|
* @return Promise resolved when the handler finishes or reaches max time, rejected if it fails.
|
|
|
|
*/
|
2020-10-14 06:29:45 +00:00
|
|
|
protected executeHandler(name: string, force?: boolean, siteId?: string): Promise<void> {
|
2020-10-07 08:53:19 +00:00
|
|
|
return new Promise((resolve, reject): void => {
|
|
|
|
this.logger.debug('Executing handler: ' + name);
|
|
|
|
|
|
|
|
// Wrap the call in Promise.resolve to make sure it's a promise.
|
2020-10-21 14:32:27 +00:00
|
|
|
Promise.resolve(this.handlers[name].execute!(siteId, force)).then(resolve).catch(reject).finally(() => {
|
2020-10-07 08:53:19 +00:00
|
|
|
clearTimeout(cancelTimeout);
|
|
|
|
});
|
|
|
|
|
2020-10-14 06:29:45 +00:00
|
|
|
const cancelTimeout = setTimeout(() => {
|
2020-10-07 08:53:19 +00:00
|
|
|
// The handler took too long. Resolve because we don't want to retry soon.
|
|
|
|
this.logger.debug(`Resolving execution of handler '${name}' because it took too long.`);
|
|
|
|
resolve();
|
|
|
|
}, CoreCronDelegate.MAX_TIME_PROCESS);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Force execution of synchronization cron tasks without waiting for the scheduled time.
|
|
|
|
* Please notice that some tasks may not be executed depending on the network connection and sync settings.
|
|
|
|
*
|
|
|
|
* @param siteId Site ID. If not defined, all sites.
|
|
|
|
* @return Promise resolved if all handlers are executed successfully, rejected otherwise.
|
|
|
|
*/
|
2020-10-14 06:29:45 +00:00
|
|
|
async forceSyncExecution(siteId?: string): Promise<void> {
|
2020-10-21 14:32:27 +00:00
|
|
|
const promises: Promise<void>[] = [];
|
2020-10-07 08:53:19 +00:00
|
|
|
|
|
|
|
for (const name in this.handlers) {
|
|
|
|
if (this.isHandlerManualSync(name)) {
|
|
|
|
// Now force the execution of the handler.
|
|
|
|
promises.push(this.forceCronHandlerExecution(name, siteId));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-14 06:29:45 +00:00
|
|
|
await CoreUtils.instance.allPromises(promises);
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Force execution of a cron tasks without waiting for the scheduled time.
|
|
|
|
* Please notice that some tasks may not be executed depending on the network connection and sync settings.
|
|
|
|
*
|
2020-10-21 14:32:27 +00:00
|
|
|
* @param name Name of the handler.
|
2020-10-07 08:53:19 +00:00
|
|
|
* @param siteId Site ID. If not defined, all sites.
|
|
|
|
* @return Promise resolved if handler has been executed successfully, rejected otherwise.
|
|
|
|
*/
|
2020-10-21 14:32:27 +00:00
|
|
|
forceCronHandlerExecution(name: string, siteId?: string): Promise<void> {
|
2020-10-07 08:53:19 +00:00
|
|
|
const handler = this.handlers[name];
|
|
|
|
|
|
|
|
// Mark the handler as running (it might be running already).
|
|
|
|
handler.running = true;
|
|
|
|
|
|
|
|
// Cancel pending timeout.
|
|
|
|
clearTimeout(handler.timeout);
|
|
|
|
delete handler.timeout;
|
|
|
|
|
|
|
|
// Now force the execution of the handler.
|
|
|
|
return this.checkAndExecuteHandler(name, true, siteId);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a handler's interval.
|
|
|
|
*
|
|
|
|
* @param name Handler's name.
|
|
|
|
* @return Handler's interval.
|
|
|
|
*/
|
|
|
|
protected getHandlerInterval(name: string): number {
|
|
|
|
if (!this.handlers[name] || !this.handlers[name].getInterval) {
|
|
|
|
// Invalid, return default.
|
|
|
|
return CoreCronDelegate.DEFAULT_INTERVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't allow intervals lower than the minimum.
|
2020-10-29 11:39:15 +00:00
|
|
|
const minInterval = CoreCronDelegate.MIN_INTERVAL;
|
2020-10-21 14:32:27 +00:00
|
|
|
const handlerInterval = this.handlers[name].getInterval!();
|
2020-10-07 08:53:19 +00:00
|
|
|
|
|
|
|
if (!handlerInterval) {
|
|
|
|
return CoreCronDelegate.DEFAULT_INTERVAL;
|
|
|
|
} else {
|
|
|
|
return Math.max(minInterval, handlerInterval);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a handler's last execution ID.
|
|
|
|
*
|
|
|
|
* @param name Handler's name.
|
|
|
|
* @return Handler's last execution ID.
|
|
|
|
*/
|
|
|
|
protected getHandlerLastExecutionId(name: string): string {
|
|
|
|
return 'last_execution_' + name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a handler's last execution time. If not defined, return 0.
|
|
|
|
*
|
|
|
|
* @param name Handler's name.
|
|
|
|
* @return Promise resolved with the handler's last execution time.
|
|
|
|
*/
|
|
|
|
protected async getHandlerLastExecutionTime(name: string): Promise<number> {
|
|
|
|
await this.dbReady;
|
|
|
|
|
|
|
|
const id = this.getHandlerLastExecutionId(name);
|
|
|
|
|
|
|
|
try {
|
2020-10-28 13:25:18 +00:00
|
|
|
const entry = await this.appDB.getRecord<CronDBEntry>(CRON_TABLE_NAME, { id });
|
2020-10-14 14:38:24 +00:00
|
|
|
|
|
|
|
const time = Number(entry.value);
|
2020-10-07 08:53:19 +00:00
|
|
|
|
|
|
|
return isNaN(time) ? 0 : time;
|
|
|
|
} catch (err) {
|
|
|
|
return 0; // Not set, return 0.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a handler uses network. Defaults to true.
|
|
|
|
*
|
|
|
|
* @param name Handler's name.
|
|
|
|
* @return True if handler uses network or not defined, false otherwise.
|
|
|
|
*/
|
|
|
|
protected handlerUsesNetwork(name: string): boolean {
|
2020-10-21 14:32:27 +00:00
|
|
|
if (!this.handlers[name] || this.handlers[name].usesNetwork) {
|
2020-10-07 08:53:19 +00:00
|
|
|
// Invalid, return default.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
return this.handlers[name].usesNetwork!();
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if there is any manual sync handler registered.
|
|
|
|
*
|
|
|
|
* @return Whether it has at least 1 manual sync handler.
|
|
|
|
*/
|
|
|
|
hasManualSyncHandlers(): boolean {
|
|
|
|
for (const name in this.handlers) {
|
|
|
|
if (this.isHandlerManualSync(name)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if there is any sync handler registered.
|
|
|
|
*
|
|
|
|
* @return Whether it has at least 1 sync handler.
|
|
|
|
*/
|
|
|
|
hasSyncHandlers(): boolean {
|
|
|
|
for (const name in this.handlers) {
|
|
|
|
if (this.isHandlerSync(name)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a handler can be manually synced. Defaults will use isSync instead.
|
|
|
|
*
|
|
|
|
* @param name Handler's name.
|
|
|
|
* @return True if handler is a sync process and can be manually executed or not defined, false otherwise.
|
|
|
|
*/
|
|
|
|
protected isHandlerManualSync(name: string): boolean {
|
|
|
|
if (!this.handlers[name] || !this.handlers[name].canManualSync) {
|
|
|
|
// Invalid, return default.
|
|
|
|
return this.isHandlerSync(name);
|
|
|
|
}
|
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
return this.handlers[name].canManualSync!();
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a handler is a sync process. Defaults to true.
|
|
|
|
*
|
|
|
|
* @param name Handler's name.
|
|
|
|
* @return True if handler is a sync process or not defined, false otherwise.
|
|
|
|
*/
|
|
|
|
protected isHandlerSync(name: string): boolean {
|
|
|
|
if (!this.handlers[name] || !this.handlers[name].isSync) {
|
|
|
|
// Invalid, return default.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
return this.handlers[name].isSync!();
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a handler to be executed every certain time.
|
|
|
|
*
|
|
|
|
* @param handler The handler to register.
|
|
|
|
*/
|
|
|
|
register(handler: CoreCronHandler): void {
|
|
|
|
if (!handler || !handler.name) {
|
|
|
|
// Invalid handler.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (typeof this.handlers[handler.name] != 'undefined') {
|
|
|
|
this.logger.debug(`The cron handler '${handler.name}' is already registered.`);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.logger.debug(`Register handler '${handler.name}' in cron.`);
|
|
|
|
|
|
|
|
handler.running = false;
|
|
|
|
this.handlers[handler.name] = handler;
|
|
|
|
|
|
|
|
// Start the handler.
|
|
|
|
this.startHandler(handler.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Schedule a next execution for a handler.
|
|
|
|
*
|
|
|
|
* @param name Name of the handler.
|
2020-10-21 14:32:27 +00:00
|
|
|
* @param timeToNextExecution Time (in milliseconds). If not supplied it will be calculated.
|
|
|
|
* @return Promise resolved when done.
|
2020-10-07 08:53:19 +00:00
|
|
|
*/
|
2020-10-21 14:32:27 +00:00
|
|
|
protected async scheduleNextExecution(name: string, timeToNextExecution?: number): Promise<void> {
|
2020-10-07 08:53:19 +00:00
|
|
|
if (!this.handlers[name]) {
|
|
|
|
// Invalid handler.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (this.handlers[name].timeout) {
|
|
|
|
// There's already a pending timeout.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
if (!timeToNextExecution) {
|
2020-10-07 08:53:19 +00:00
|
|
|
// Get last execution time to check when do we need to execute it.
|
2020-10-21 14:32:27 +00:00
|
|
|
const lastExecution = await this.getHandlerLastExecutionTime(name);
|
2020-10-07 08:53:19 +00:00
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
const interval = this.getHandlerInterval(name);
|
|
|
|
|
|
|
|
timeToNextExecution = lastExecution + interval - Date.now();
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
this.logger.debug(`Scheduling next execution of handler '${name}' in '${timeToNextExecution}' ms`);
|
|
|
|
if (timeToNextExecution < 0) {
|
|
|
|
timeToNextExecution = 0; // Big negative numbers aren't executed immediately.
|
|
|
|
}
|
2020-10-07 08:53:19 +00:00
|
|
|
|
2020-10-21 14:32:27 +00:00
|
|
|
this.handlers[name].timeout = window.setTimeout(() => {
|
|
|
|
delete this.handlers[name].timeout;
|
|
|
|
CoreUtils.instance.ignoreErrors(this.checkAndExecuteHandler(name));
|
|
|
|
}, timeToNextExecution);
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set a handler's last execution time.
|
|
|
|
*
|
|
|
|
* @param name Handler's name.
|
|
|
|
* @param time Time to set.
|
|
|
|
* @return Promise resolved when the execution time is saved.
|
|
|
|
*/
|
2020-10-14 06:29:45 +00:00
|
|
|
protected async setHandlerLastExecutionTime(name: string, time: number): Promise<void> {
|
2020-10-07 08:53:19 +00:00
|
|
|
await this.dbReady;
|
|
|
|
|
|
|
|
const id = this.getHandlerLastExecutionId(name);
|
|
|
|
const entry = {
|
|
|
|
id,
|
2020-10-14 06:29:45 +00:00
|
|
|
value: time,
|
2020-10-07 08:53:19 +00:00
|
|
|
};
|
|
|
|
|
2020-10-28 13:25:18 +00:00
|
|
|
await this.appDB.insertRecord(CRON_TABLE_NAME, entry);
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start running a handler periodically.
|
|
|
|
*
|
|
|
|
* @param name Name of the handler.
|
|
|
|
*/
|
|
|
|
protected startHandler(name: string): void {
|
|
|
|
if (!this.handlers[name]) {
|
|
|
|
// Invalid handler.
|
|
|
|
this.logger.debug(`Cannot start handler '${name}', is invalid.`);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.handlers[name].running) {
|
|
|
|
this.logger.debug(`Handler '${name}', is already running.`);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.handlers[name].running = true;
|
|
|
|
|
|
|
|
this.scheduleNextExecution(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Start running periodically the handlers that use network.
|
|
|
|
*/
|
|
|
|
startNetworkHandlers(): void {
|
|
|
|
for (const name in this.handlers) {
|
|
|
|
if (this.handlerUsesNetwork(name)) {
|
|
|
|
this.startHandler(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop running a handler periodically.
|
|
|
|
*
|
|
|
|
* @param name Name of the handler.
|
|
|
|
*/
|
|
|
|
protected stopHandler(name: string): void {
|
|
|
|
if (!this.handlers[name]) {
|
|
|
|
// Invalid handler.
|
|
|
|
this.logger.debug(`Cannot stop handler '${name}', is invalid.`);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.handlers[name].running) {
|
|
|
|
this.logger.debug(`Cannot stop handler '${name}', it's not running.`);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.handlers[name].running = false;
|
|
|
|
clearTimeout(this.handlers[name].timeout);
|
|
|
|
delete this.handlers[name].timeout;
|
|
|
|
}
|
2020-10-14 06:29:45 +00:00
|
|
|
|
2020-10-07 08:53:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export class CoreCron extends makeSingleton(CoreCronDelegate) {}
|
2020-10-14 06:29:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Interface that all cron handlers must implement.
|
|
|
|
*/
|
|
|
|
export interface CoreCronHandler {
|
|
|
|
/**
|
|
|
|
* A name to identify the handler.
|
|
|
|
*/
|
|
|
|
name: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the handler is running. Used internally by the provider, there's no need to set it.
|
|
|
|
*/
|
|
|
|
running?: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Timeout ID for the handler scheduling. Used internally by the provider, there's no need to set it.
|
|
|
|
*/
|
|
|
|
timeout?: number;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns handler's interval in milliseconds. Defaults to CoreCronDelegate.DEFAULT_INTERVAL.
|
|
|
|
*
|
|
|
|
* @return Interval time (in milliseconds).
|
|
|
|
*/
|
|
|
|
getInterval?(): number;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether the process uses network or not. True if not defined.
|
|
|
|
*
|
|
|
|
* @return Whether the process uses network or not
|
|
|
|
*/
|
|
|
|
usesNetwork?(): boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether it's a synchronization process or not. True if not defined.
|
|
|
|
*
|
|
|
|
* @return Whether it's a synchronization process or not.
|
|
|
|
*/
|
|
|
|
isSync?(): boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether the sync can be executed manually. Call isSync if not defined.
|
|
|
|
*
|
|
|
|
* @return Whether the sync can be executed manually.
|
|
|
|
*/
|
|
|
|
canManualSync?(): boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute the process.
|
|
|
|
*
|
|
|
|
* @param siteId ID of the site affected. If not defined, all sites.
|
|
|
|
* @param force Determines if it's a forced execution.
|
|
|
|
* @return Promise resolved when done. If the promise is rejected, this function will be called again often,
|
|
|
|
* it shouldn't be abused.
|
|
|
|
*/
|
|
|
|
execute?(siteId?: string, force?: boolean): Promise<void>;
|
|
|
|
}
|