MOBILE-3565 core: Migrate some core classes
parent
1e979b57bb
commit
811bb39781
|
@ -0,0 +1,352 @@
|
|||
// (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 { CoreSites } from '@services/sites';
|
||||
import { CoreEvents, CoreEventsProvider } from '@services/events';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
|
||||
/**
|
||||
* Superclass to help creating delegates
|
||||
*/
|
||||
export class CoreDelegate {
|
||||
|
||||
/**
|
||||
* Logger instance.
|
||||
*/
|
||||
protected logger: CoreLogger;
|
||||
|
||||
/**
|
||||
* List of registered handlers.
|
||||
*/
|
||||
protected handlers: { [s: string]: CoreDelegateHandler } = {};
|
||||
|
||||
/**
|
||||
* List of registered handlers enabled for the current site.
|
||||
*/
|
||||
protected enabledHandlers: { [s: string]: CoreDelegateHandler } = {};
|
||||
|
||||
/**
|
||||
* Default handler
|
||||
*/
|
||||
protected defaultHandler: CoreDelegateHandler;
|
||||
|
||||
/**
|
||||
* Time when last updateHandler functions started.
|
||||
*/
|
||||
protected lastUpdateHandlersStart: number;
|
||||
|
||||
/**
|
||||
* Feature prefix to check is feature is enabled or disabled in site.
|
||||
* This check is only made if not false. Override on the subclass or override isFeatureDisabled function.
|
||||
*/
|
||||
protected featurePrefix: string;
|
||||
|
||||
/**
|
||||
* Name of the property to be used to index the handlers. By default, the handler's name will be used.
|
||||
* If your delegate uses a Moodle component name to identify the handlers, please override this property.
|
||||
* E.g. CoreCourseModuleDelegate uses 'modName' to index the handlers.
|
||||
*/
|
||||
protected handlerNameProperty = 'name';
|
||||
|
||||
/**
|
||||
* Set of promises to update a handler, to prevent doing the same operation twice.
|
||||
*/
|
||||
protected updatePromises: {[siteId: string]: {[name: string]: Promise<any>}} = {};
|
||||
|
||||
/**
|
||||
* Whether handlers have been initialized.
|
||||
*/
|
||||
protected handlersInitialized = false;
|
||||
|
||||
/**
|
||||
* Promise to wait for handlers to be initialized.
|
||||
*/
|
||||
protected handlersInitPromise: Promise<any>;
|
||||
|
||||
/**
|
||||
* Function to resolve the handlers init promise.
|
||||
*/
|
||||
protected handlersInitResolve: (value?: any) => void;
|
||||
|
||||
/**
|
||||
* Constructor of the Delegate.
|
||||
*
|
||||
* @param delegateName Delegate name used for logging purposes.
|
||||
* @param listenSiteEvents Whether to update the handler when a site event occurs (login, site updated, ...).
|
||||
*/
|
||||
constructor(delegateName: string, listenSiteEvents?: boolean) {
|
||||
this.logger = CoreLogger.getInstance(delegateName);
|
||||
|
||||
this.handlersInitPromise = new Promise((resolve): void => {
|
||||
this.handlersInitResolve = resolve;
|
||||
});
|
||||
|
||||
if (listenSiteEvents) {
|
||||
// Update handlers on this cases.
|
||||
CoreEvents.instance.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
|
||||
CoreEvents.instance.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
|
||||
CoreEvents.instance.on(CoreEventsProvider.SITE_PLUGINS_LOADED, this.updateHandlers.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a certain function in a enabled handler.
|
||||
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
||||
*
|
||||
* @param handlerName The handler name.
|
||||
* @param fnName Name of the function to execute.
|
||||
* @param params Parameters to pass to the function.
|
||||
* @return Function returned value or default value.
|
||||
*/
|
||||
protected executeFunctionOnEnabled(handlerName: string, fnName: string, params?: any[]): any {
|
||||
return this.execute(this.enabledHandlers[handlerName], fnName, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a certain function in a handler.
|
||||
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
||||
*
|
||||
* @param handlerName The handler name.
|
||||
* @param fnName Name of the function to execute.
|
||||
* @param params Parameters to pass to the function.
|
||||
* @return Function returned value or default value.
|
||||
*/
|
||||
protected executeFunction(handlerName: string, fnName: string, params?: any[]): any {
|
||||
return this.execute(this.handlers[handlerName], fnName, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a certain function in a handler.
|
||||
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
||||
*
|
||||
* @param handler The handler.
|
||||
* @param fnName Name of the function to execute.
|
||||
* @param params Parameters to pass to the function.
|
||||
* @return Function returned value or default value.
|
||||
*/
|
||||
private execute(handler: any, fnName: string, params?: any[]): any {
|
||||
if (handler && handler[fnName]) {
|
||||
return handler[fnName].apply(handler, params);
|
||||
} else if (this.defaultHandler && this.defaultHandler[fnName]) {
|
||||
return this.defaultHandler[fnName].apply(this.defaultHandler, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a handler.
|
||||
*
|
||||
* @param handlerName The handler name.
|
||||
* @param enabled Only enabled, or any.
|
||||
* @return Handler.
|
||||
*/
|
||||
protected getHandler(handlerName: string, enabled: boolean = false): CoreDelegateHandler {
|
||||
return enabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the handler full name for a given name. This is useful when the handlerNameProperty is different than "name".
|
||||
* E.g. blocks are indexed by blockName. If you call this function passing the blockName it will return the name.
|
||||
*
|
||||
* @param name Name used to indentify the handler.
|
||||
* @return Full name of corresponding handler.
|
||||
*/
|
||||
getHandlerName(name: string): string {
|
||||
const handler = this.getHandler(name, true);
|
||||
|
||||
if (!handler) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return handler.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if function exists on a handler.
|
||||
*
|
||||
* @param handlerName The handler name.
|
||||
* @param fnName Name of the function to execute.
|
||||
* @param onlyEnabled If check only enabled handlers or all.
|
||||
* @return Function returned value or default value.
|
||||
*/
|
||||
protected hasFunction(handlerName: string, fnName: string, onlyEnabled: boolean = true): any {
|
||||
const handler = onlyEnabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName];
|
||||
|
||||
return handler && handler[fnName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a handler name has a registered handler (not necessarily enabled).
|
||||
*
|
||||
* @param name The handler name.
|
||||
* @param enabled Only enabled, or any.
|
||||
* @return If the handler is registered or not.
|
||||
*/
|
||||
hasHandler(name: string, enabled: boolean = false): boolean {
|
||||
return enabled ? typeof this.enabledHandlers[name] !== 'undefined' : typeof this.handlers[name] !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a time belongs to the last update handlers call.
|
||||
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
|
||||
*
|
||||
* @param time Time to check.
|
||||
* @return Whether it's the last call.
|
||||
*/
|
||||
isLastUpdateCall(time: number): boolean {
|
||||
if (!this.lastUpdateHandlersStart) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return time == this.lastUpdateHandlersStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a handler.
|
||||
*
|
||||
* @param handler The handler delegate object to register.
|
||||
* @return True when registered, false if already registered.
|
||||
*/
|
||||
registerHandler(handler: CoreDelegateHandler): boolean {
|
||||
const key = handler[this.handlerNameProperty] || handler.name;
|
||||
|
||||
if (typeof this.handlers[key] !== 'undefined') {
|
||||
this.logger.log(`Handler '${handler[this.handlerNameProperty]}' already registered`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.log(`Registered handler '${handler[this.handlerNameProperty]}'`);
|
||||
this.handlers[key] = handler;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handler for the current site.
|
||||
*
|
||||
* @param handler The handler to check.
|
||||
* @param time Time this update process started.
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected updateHandler(handler: CoreDelegateHandler, time: number): Promise<void> {
|
||||
const siteId = CoreSites.instance.getCurrentSiteId();
|
||||
const currentSite = CoreSites.instance.getCurrentSite();
|
||||
let promise;
|
||||
|
||||
if (this.updatePromises[siteId] && this.updatePromises[siteId][handler.name]) {
|
||||
// There's already an update ongoing for this handler, return the promise.
|
||||
return this.updatePromises[siteId][handler.name];
|
||||
} else if (!this.updatePromises[siteId]) {
|
||||
this.updatePromises[siteId] = {};
|
||||
}
|
||||
|
||||
if (!CoreSites.instance.isLoggedIn()) {
|
||||
promise = Promise.reject(null);
|
||||
} else if (this.isFeatureDisabled(handler, currentSite)) {
|
||||
promise = Promise.resolve(false);
|
||||
} else {
|
||||
promise = Promise.resolve(handler.isEnabled());
|
||||
}
|
||||
|
||||
// Checks if the handler is enabled.
|
||||
this.updatePromises[siteId][handler.name] = promise.catch(() => {
|
||||
return false;
|
||||
}).then((enabled: boolean) => {
|
||||
// Check that site hasn't changed since the check started.
|
||||
if (CoreSites.instance.getCurrentSiteId() === siteId) {
|
||||
const key = handler[this.handlerNameProperty] || handler.name;
|
||||
|
||||
if (enabled) {
|
||||
this.enabledHandlers[key] = handler;
|
||||
} else {
|
||||
delete this.enabledHandlers[key];
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
// Update finished, delete the promise.
|
||||
delete this.updatePromises[siteId][handler.name];
|
||||
});
|
||||
|
||||
return this.updatePromises[siteId][handler.name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name.
|
||||
*
|
||||
* @param handler Handler to check.
|
||||
* @param site Site to check.
|
||||
* @return Whether is enabled or disabled in site.
|
||||
*/
|
||||
protected isFeatureDisabled(handler: CoreDelegateHandler, site: CoreSite): boolean {
|
||||
return typeof this.featurePrefix != 'undefined' && site.isFeatureDisabled(this.featurePrefix + handler.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the handlers for the current site.
|
||||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected updateHandlers(): Promise<void> {
|
||||
const promises = [],
|
||||
now = Date.now();
|
||||
|
||||
this.logger.debug('Updating handlers for current site.');
|
||||
|
||||
this.lastUpdateHandlersStart = now;
|
||||
|
||||
// Loop over all the handlers.
|
||||
for (const name in this.handlers) {
|
||||
promises.push(this.updateHandler(this.handlers[name], now));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return true;
|
||||
}, () => {
|
||||
// Never reject.
|
||||
return true;
|
||||
}).then(() => {
|
||||
|
||||
// Verify that this call is the last one that was started.
|
||||
if (this.isLastUpdateCall(now)) {
|
||||
this.handlersInitialized = true;
|
||||
this.handlersInitResolve();
|
||||
|
||||
this.updateData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update handlers Data.
|
||||
* Override this function to update handlers data.
|
||||
*/
|
||||
updateData(): any {
|
||||
// To be overridden.
|
||||
}
|
||||
}
|
||||
|
||||
export interface CoreDelegateHandler {
|
||||
/**
|
||||
* Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...).
|
||||
* This name will be used to check if the feature is disabled.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean>;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// (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.
|
||||
|
||||
/**
|
||||
* Base Error class.
|
||||
*
|
||||
* The native Error class cannot be extended in Typescript without restoring the prototype chain, extend this
|
||||
* class instead.
|
||||
*
|
||||
* @see https://stackoverflow.com/questions/41102060/typescript-extending-error-class
|
||||
*/
|
||||
export class CoreError extends Error {
|
||||
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
|
||||
// Fix prototype chain: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
|
||||
this.name = new.target.name;
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// (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 } from '@angular/core';
|
||||
import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Interceptor for Http calls. Adds the header 'Content-Type'='application/x-www-form-urlencoded'
|
||||
* and serializes the parameters if needed.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreInterceptor implements HttpInterceptor {
|
||||
|
||||
/**
|
||||
* Serialize an object to be used in a request.
|
||||
*
|
||||
* @param obj Object to serialize.
|
||||
* @param addNull Add null values to the serialized as empty parameters.
|
||||
* @return Serialization of the object.
|
||||
*/
|
||||
static serialize(obj: any, addNull?: boolean): string {
|
||||
let query = '';
|
||||
let fullSubName;
|
||||
let subValue;
|
||||
let innerObj;
|
||||
|
||||
for (const name in obj) {
|
||||
const value = obj[name];
|
||||
|
||||
if (value instanceof Array) {
|
||||
for (let i = 0; i < value.length; ++i) {
|
||||
subValue = value[i];
|
||||
fullSubName = name + '[' + i + ']';
|
||||
innerObj = {};
|
||||
innerObj[fullSubName] = subValue;
|
||||
query += this.serialize(innerObj) + '&';
|
||||
}
|
||||
} else if (value instanceof Object) {
|
||||
for (const subName in value) {
|
||||
subValue = value[subName];
|
||||
fullSubName = name + '[' + subName + ']';
|
||||
innerObj = {};
|
||||
innerObj[fullSubName] = subValue;
|
||||
query += this.serialize(innerObj) + '&';
|
||||
}
|
||||
} else if (addNull || (typeof value != 'undefined' && value !== null)) {
|
||||
query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
|
||||
}
|
||||
}
|
||||
|
||||
return query.length ? query.substr(0, query.length - 1) : query;
|
||||
}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
|
||||
// Add the header and serialize the body if needed.
|
||||
const newReq = req.clone({
|
||||
headers: req.headers.set('Content-Type', 'application/x-www-form-urlencoded'),
|
||||
body: typeof req.body == 'object' && String(req.body) != '[object File]' ?
|
||||
CoreInterceptor.serialize(req.body) : req.body
|
||||
});
|
||||
|
||||
// Pass on the cloned request instead of the original request.
|
||||
return next.handle(newReq);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
// (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 { HttpResponse as AngularHttpResponse, HttpHeaders } from '@angular/common/http';
|
||||
import { HTTPResponse as NativeHttpResponse } from '@ionic-native/http';
|
||||
|
||||
const HTTP_STATUS_MESSAGES = {
|
||||
100: 'Continue',
|
||||
101: 'Switching Protocol',
|
||||
102: 'Processing',
|
||||
103: 'Early Hints',
|
||||
200: 'OK',
|
||||
201: 'Created',
|
||||
202: 'Accepted',
|
||||
203: 'Non-Authoritative Information',
|
||||
204: 'No Content',
|
||||
205: 'Reset Content',
|
||||
206: 'Partial Content',
|
||||
207: 'Multi-Status',
|
||||
208: 'Already Reported',
|
||||
226: 'IM Used',
|
||||
300: 'Multiple Choice',
|
||||
301: 'Moved Permanently',
|
||||
302: 'Found',
|
||||
303: 'See Other',
|
||||
304: 'Not Modified',
|
||||
305: 'Use Proxy',
|
||||
306: 'unused',
|
||||
307: 'Temporary Redirect',
|
||||
308: 'Permanent Redirect',
|
||||
400: 'Bad Request',
|
||||
401: 'Unauthorized',
|
||||
402: 'Payment Required',
|
||||
403: 'Forbidden',
|
||||
404: 'Not Found',
|
||||
405: 'Method Not Allowed',
|
||||
406: 'Not Acceptable',
|
||||
407: 'Proxy Authentication Required',
|
||||
408: 'Request Timeout',
|
||||
409: 'Conflict',
|
||||
410: 'Gone',
|
||||
411: 'Length Required',
|
||||
412: 'Precondition Failed',
|
||||
413: 'Payload Too Large',
|
||||
414: 'URI Too Long',
|
||||
415: 'Unsupported Media Type',
|
||||
416: 'Range Not Satisfiable',
|
||||
417: 'Expectation Failed',
|
||||
418: 'I\'m a teapot',
|
||||
421: 'Misdirected Request',
|
||||
422: 'Unprocessable Entity',
|
||||
423: 'Locked',
|
||||
424: 'Failed Dependency',
|
||||
425: 'Too Early',
|
||||
426: 'Upgrade Required',
|
||||
428: 'Precondition Required',
|
||||
429: 'Too Many Requests',
|
||||
431: 'Request Header Fields Too Large',
|
||||
451: 'Unavailable For Legal Reasons',
|
||||
500: 'Internal Server Error',
|
||||
501: 'Not Implemented',
|
||||
502: 'Bad Gateway',
|
||||
503: 'Service Unavailable',
|
||||
504: 'Gateway Timeout',
|
||||
505: 'HTTP Version Not Supported',
|
||||
506: 'Variant Also Negotiates',
|
||||
507: 'Insufficient Storage',
|
||||
508: 'Loop Detected',
|
||||
510: 'Not Extended',
|
||||
511: 'Network Authentication Required',
|
||||
};
|
||||
|
||||
/**
|
||||
* Class that adapts a Cordova plugin http response to an Angular http response.
|
||||
*/
|
||||
export class CoreNativeToAngularHttpResponse<T> extends AngularHttpResponse<T> {
|
||||
|
||||
constructor(protected nativeResponse: NativeHttpResponse) {
|
||||
super({
|
||||
body: nativeResponse.data,
|
||||
headers: new HttpHeaders(nativeResponse.headers),
|
||||
status: nativeResponse.status,
|
||||
statusText: HTTP_STATUS_MESSAGES[nativeResponse.status] || '',
|
||||
url: nativeResponse.url || ''
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
// (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 { CoreUtils, PromiseDefer } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Function to add to the queue.
|
||||
*/
|
||||
export type CoreQueueRunnerFunction<T> = (...args: any[]) => T | Promise<T>;
|
||||
|
||||
/**
|
||||
* Queue item.
|
||||
*/
|
||||
export type CoreQueueRunnerItem<T = any> = {
|
||||
/**
|
||||
* Item ID.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Function to execute.
|
||||
*/
|
||||
fn: CoreQueueRunnerFunction<T>;
|
||||
|
||||
/**
|
||||
* Deferred with a promise resolved/rejected with the result of the function.
|
||||
*/
|
||||
deferred: PromiseDefer<T>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Options to pass to add item.
|
||||
*/
|
||||
export type CoreQueueRunnerAddOptions = {
|
||||
/**
|
||||
* Whether to allow having multiple entries with same ID in the queue.
|
||||
*/
|
||||
allowRepeated?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A queue to prevent having too many concurrent executions.
|
||||
*/
|
||||
export class CoreQueueRunner {
|
||||
protected queue: {[id: string]: CoreQueueRunnerItem} = {};
|
||||
protected orderedQueue: CoreQueueRunnerItem[] = [];
|
||||
protected numberRunning = 0;
|
||||
|
||||
constructor(protected maxParallel: number = 1) { }
|
||||
|
||||
/**
|
||||
* Get unique ID.
|
||||
*
|
||||
* @param id ID.
|
||||
* @return Unique ID.
|
||||
*/
|
||||
protected getUniqueId(id: string): string {
|
||||
let newId = id;
|
||||
let num = 1;
|
||||
|
||||
do {
|
||||
newId = id + '-' + num;
|
||||
num++;
|
||||
} while (newId in this.queue);
|
||||
|
||||
return newId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process next item in the queue.
|
||||
*
|
||||
* @return Promise resolved when next item has been treated.
|
||||
*/
|
||||
protected async processNextItem(): Promise<void> {
|
||||
if (!this.orderedQueue.length || this.numberRunning >= this.maxParallel) {
|
||||
// Queue is empty or max number of parallel runs reached, stop.
|
||||
return;
|
||||
}
|
||||
|
||||
const item = this.orderedQueue.shift();
|
||||
this.numberRunning++;
|
||||
|
||||
try {
|
||||
const result = await item.fn();
|
||||
|
||||
item.deferred.resolve(result);
|
||||
} catch (error) {
|
||||
item.deferred.reject(error);
|
||||
} finally {
|
||||
delete this.queue[item.id];
|
||||
this.numberRunning--;
|
||||
|
||||
this.processNextItem();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the queue.
|
||||
*
|
||||
* @param id ID.
|
||||
* @param fn Function to call.
|
||||
* @param options Options.
|
||||
* @return Promise resolved when the function has been executed.
|
||||
*/
|
||||
run<T>(id: string, fn: CoreQueueRunnerFunction<T>, options?: CoreQueueRunnerAddOptions): Promise<T> {
|
||||
options = options || {};
|
||||
|
||||
if (id in this.queue) {
|
||||
if (!options.allowRepeated) {
|
||||
// Item already in queue, return its promise.
|
||||
return this.queue[id].deferred.promise;
|
||||
}
|
||||
|
||||
id = this.getUniqueId(id);
|
||||
}
|
||||
|
||||
// Add the item in the queue.
|
||||
const item = {
|
||||
id,
|
||||
fn,
|
||||
deferred: CoreUtils.instance.promiseDefer<T>(),
|
||||
};
|
||||
|
||||
this.queue[id] = item;
|
||||
this.orderedQueue.push(item);
|
||||
|
||||
// Process next item if we haven't reached the max yet.
|
||||
this.processNextItem();
|
||||
|
||||
return item.deferred.promise;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -12,9 +12,32 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injector } from '@angular/core';
|
||||
import { Injector, NgZone as NgZoneService } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { SplashScreen as SplashScreenPlugin } from '@ionic-native/splash-screen/ngx';
|
||||
import { Platform as PlatformService } from '@ionic/angular';
|
||||
|
||||
import { Clipboard as ClipboardService } from '@ionic-native/clipboard/ngx';
|
||||
import { Diagnostic as DiagnosticService } from '@ionic-native/diagnostic/ngx';
|
||||
import { Device as DeviceService } from '@ionic-native/device/ngx';
|
||||
import { File as FileService } from '@ionic-native/file/ngx';
|
||||
import { FileOpener as FileOpenerService } from '@ionic-native/file-opener/ngx';
|
||||
import { FileTransfer as FileTransferService } from '@ionic-native/file-transfer/ngx';
|
||||
import { Geolocation as GeolocationService } from '@ionic-native/geolocation/ngx';
|
||||
import { Globalization as GlobalizationService } from '@ionic-native/globalization/ngx';
|
||||
import { InAppBrowser as InAppBrowserService } from '@ionic-native/in-app-browser/ngx';
|
||||
import { Keyboard as KeyboardService } from '@ionic-native/keyboard/ngx';
|
||||
import { LocalNotifications as LocalNotificationsService } from '@ionic-native/local-notifications/ngx';
|
||||
import { Network as NetworkService } from '@ionic-native/network/ngx';
|
||||
import { Push as PushService } from '@ionic-native/push/ngx';
|
||||
import { QRScanner as QRScannerService } from '@ionic-native/qr-scanner/ngx';
|
||||
import { StatusBar as StatusBarService } from '@ionic-native/status-bar/ngx';
|
||||
import { SplashScreen as SplashScreenService } from '@ionic-native/splash-screen/ngx';
|
||||
import { SQLite as SQLiteService } from '@ionic-native/sqlite/ngx';
|
||||
import { WebIntent as WebIntentService } from '@ionic-native/web-intent/ngx';
|
||||
import { Zip as ZipService } from '@ionic-native/zip/ngx';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { CoreSingletonsFactory, CoreInjectionToken, CoreSingletonClass } from '@classes/singletons-factory';
|
||||
|
||||
|
@ -39,4 +62,31 @@ export function makeSingleton<Service>(injectionToken: CoreInjectionToken<Servic
|
|||
return factory.makeSingleton(injectionToken);
|
||||
}
|
||||
|
||||
export class SplashScreen extends makeSingleton(SplashScreenPlugin) {}
|
||||
// Convert ionic-native services to singleton.
|
||||
export class Clipboard extends makeSingleton(ClipboardService) {}
|
||||
export class Device extends makeSingleton(DeviceService) {}
|
||||
export class Diagnostic extends makeSingleton(DiagnosticService) {}
|
||||
export class File extends makeSingleton(FileService) {}
|
||||
export class FileOpener extends makeSingleton(FileOpenerService) {}
|
||||
export class FileTransfer extends makeSingleton(FileTransferService) {}
|
||||
export class Geolocation extends makeSingleton(GeolocationService) {}
|
||||
export class Globalization extends makeSingleton(GlobalizationService) {}
|
||||
export class InAppBrowser extends makeSingleton(InAppBrowserService) {}
|
||||
export class Keyboard extends makeSingleton(KeyboardService) {}
|
||||
export class LocalNotifications extends makeSingleton(LocalNotificationsService) {}
|
||||
export class Network extends makeSingleton(NetworkService) {}
|
||||
export class Push extends makeSingleton(PushService) {}
|
||||
export class QRScanner extends makeSingleton(QRScannerService) {}
|
||||
export class StatusBar extends makeSingleton(StatusBarService) {}
|
||||
export class SplashScreen extends makeSingleton(SplashScreenService) {}
|
||||
export class SQLite extends makeSingleton(SQLiteService) {}
|
||||
export class WebIntent extends makeSingleton(WebIntentService) {}
|
||||
export class Zip extends makeSingleton(ZipService) {}
|
||||
|
||||
// Convert some Angular and Ionic injectables to singletons.
|
||||
export class NgZone extends makeSingleton(NgZoneService) {}
|
||||
export class Http extends makeSingleton(HttpClient) {}
|
||||
export class Platform extends makeSingleton(PlatformService) {}
|
||||
|
||||
// Convert external libraries injectables.
|
||||
export class Translate extends makeSingleton(TranslateService) {}
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"no-angle-bracket-type-assertion": false,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
|
@ -58,12 +59,15 @@
|
|||
],
|
||||
"no-non-null-assertion": true,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-shadowed-variable": false,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unused-expression": false,
|
||||
"no-var-requires": false,
|
||||
"object-literal-key-quotes": [
|
||||
true,
|
||||
"as-needed"
|
||||
],
|
||||
"prefer-for-of": false,
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
|
@ -104,7 +108,8 @@
|
|||
"options": [
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-pascal-case"
|
||||
"allow-pascal-case",
|
||||
"allow-leading-underscore"
|
||||
]
|
||||
},
|
||||
"whitespace": {
|
||||
|
|
Loading…
Reference in New Issue