MOBILE-2261 site: Implement sites factory and events provider

main
Dani Palou 2017-11-13 13:35:44 +01:00
parent 4089a85f94
commit c8935be6fe
11 changed files with 1888 additions and 253 deletions

View File

@ -26,6 +26,8 @@ import { CoreMimetypeUtilsProvider } from '../providers/utils/mimetype';
import { CoreInitDelegate } from '../providers/init';
import { CoreFileProvider } from '../providers/file';
import { CoreWSProvider } from '../providers/ws';
import { CoreEventsProvider } from '../providers/events';
import { CoreSitesFactoryProvider } from '../providers/sites-factory';
// For translate loader. AoT requires an exported function for factories.
export function createTranslateLoader(http: HttpClient) {
@ -72,7 +74,9 @@ export function createTranslateLoader(http: HttpClient) {
CoreMimetypeUtilsProvider,
CoreInitDelegate,
CoreFileProvider,
CoreWSProvider
CoreWSProvider,
CoreEventsProvider,
CoreSitesFactoryProvider,
]
})
export class AppModule {

1387
src/classes/site.ts 100644

File diff suppressed because it is too large Load Diff

View File

@ -303,12 +303,14 @@ export class SQLiteDB {
/**
* Execute a SQL query.
* IMPORTANT: Use this function only if you cannot use any of the other functions in this API. Please take into account that
* these query will be run in SQLite (Mobile) and Web SQL (desktop), so your query should work in both environments.
*
* @param {string} sql SQL query to execute.
* @param {any[]} params Query parameters.
* @return {Promise<any>} Promise resolved with the result.
*/
protected execute(sql: string, params?: any[]) : Promise<any> {
execute(sql: string, params?: any[]) : Promise<any> {
return this.ready().then(() => {
return this.db.executeSql(sql, params);
});
@ -316,16 +318,28 @@ export class SQLiteDB {
/**
* Execute a set of SQL queries. This operation is atomic.
* IMPORTANT: Use this function only if you cannot use any of the other functions in this API. Please take into account that
* these query will be run in SQLite (Mobile) and Web SQL (desktop), so your query should work in both environments.
*
* @param {any[]} sqlStatements SQL statements to execute.
* @return {Promise<any>} Promise resolved with the result.
*/
protected executeBatch(sqlStatements: any[]) : Promise<any> {
executeBatch(sqlStatements: any[]) : Promise<any> {
return this.ready().then(() => {
return this.db.sqlBatch(sqlStatements);
});
}
/**
* Get all the records from a table.
*
* @param {string} table The table to query.
* @return {Promise<any>} Promise resolved with the records.
*/
getAllRecords(table: string) : Promise<any> {
return this.getRecords(table);
}
/**
* Get a single field value from a table record where all the given conditions met.
*

View File

@ -25,4 +25,5 @@ export class CoreConstants {
public static dontShowError = 'CoreDontShowError';
public static settingsRichTextEditor = 'CoreSettingsRichTextEditor';
public static wsTimeout = 30000;
public static wsPrefix = 'local_mobile_';
}

View File

@ -78,12 +78,14 @@ export class SQLiteDBMock extends SQLiteDB {
/**
* Execute a SQL query.
* IMPORTANT: Use this function only if you cannot use any of the other functions in this API. Please take into account that
* these query will be run in SQLite (Mobile) and Web SQL (desktop), so your query should work in both environments.
*
* @param {string} sql SQL query to execute.
* @param {any[]} params Query parameters.
* @return {Promise<any>} Promise resolved with the result.
*/
protected execute(sql: string, params?: any[]) : Promise<any> {
execute(sql: string, params?: any[]) : Promise<any> {
return new Promise((resolve, reject) => {
// With WebSQL, all queries must be run in a transaction.
this.db.transaction((tx) => {
@ -96,11 +98,13 @@ export class SQLiteDBMock extends SQLiteDB {
/**
* Execute a set of SQL queries. This operation is atomic.
* IMPORTANT: Use this function only if you cannot use any of the other functions in this API. Please take into account that
* these query will be run in SQLite (Mobile) and Web SQL (desktop), so your query should work in both environments.
*
* @param {any[]} sqlStatements SQL statements to execute.
* @return {Promise<any>} Promise resolved with the result.
*/
protected executeBatch(sqlStatements: any[]) : Promise<any> {
executeBatch(sqlStatements: any[]) : Promise<any> {
return new Promise((resolve, reject) => {
// Create a transaction to execute the queries.
this.db.transaction((tx) => {

View File

@ -0,0 +1,127 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Subject } from 'rxjs';
import { CoreLoggerProvider } from '../providers/logger';
export interface CoreEventObserver {
off: () => void; // Unsubscribe.
};
/*
* Service to send and listen to events.
*/
@Injectable()
export class CoreEventsProvider {
public static SESSION_EXPIRED = 'session_expired';
public static PASSWORD_CHANGE_FORCED = 'password_change_forced';
public static USER_NOT_FULLY_SETUP = 'user_not_fully_setup';
public static SITE_POLICY_NOT_AGREED = 'site_policy_not_agreed';
public static LOGIN = 'login';
public static LOGOUT = 'logout';
public static LANGUAGE_CHANGED = 'language_changed';
public static NOTIFICATION_SOUND_CHANGED = 'notification_sound_changed';
public static SITE_ADDED = 'site_added';
public static SITE_UPDATED = 'site_updated';
public static SITE_DELETED = 'site_deleted';
public static COMPLETION_MODULE_VIEWED = 'completion_module_viewed';
public static USER_DELETED = 'user_deleted';
public static PACKAGE_STATUS_CHANGED = 'package_status_changed';
public static SECTION_STATUS_CHANGED = 'section_status_changed';
public static REMOTE_ADDONS_LOADED = 'remote_addons_loaded';
logger;
observables = {};
uniqueEvents = {};
constructor(logger: CoreLoggerProvider) {
this.logger = logger.getInstance('CoreEventsProvider');
}
/**
* Listen for a certain event. To stop listening to the event:
* let observer = eventsProvider.on('something', myCallBack);
* ...
* observer.off();
*
* @param {string} eventName Name of the event to listen to.
* @param {Function} callBack Function to call when the event is triggered.
* @return {CoreEventObserver} Observer to stop listening.
*/
on(eventName: string, callBack: Function) : CoreEventObserver {
// If it's a unique event and has been triggered already, call the callBack.
// We don't need to create an observer because the event won't be triggered again.
if (this.uniqueEvents[eventName]) {
callBack(this.uniqueEvents[eventName].data);
// Return a fake observer to prevent errors.
return {
off: () => {}
};
}
this.logger.debug(`New observer listening to event '${eventName}'`);
if (typeof this.observables[eventName] == 'undefined') {
// No observable for this event, create a new one.
this.observables[eventName] = new Subject<any>();
}
this.observables[eventName].subscribe(callBack);
// Create and return a CoreEventObserver.
return {
off: () => {
this.logger.debug(`Stop listening to event '${eventName}'`);
this.observables[eventName].unsubscribe(callBack);
}
};
}
/**
* Triggers an event, notifying all the observers.
*
* @param {string} event Name of the event to trigger.
* @param {any} data Data to pass to the observers.
*/
trigger(eventName: string, data: any) : void {
this.logger.debug(`Event '${eventName}' triggered.`);
if (this.observables[eventName]) {
this.observables[eventName].next(data);
}
}
/**
* Triggers a unique event, notifying all the observers. If the event has already been triggered, don't do anything.
*
* @param {string} event Name of the event to trigger.
* @param {any} data Data to pass to the observers.
*/
triggerUnique(eventName: string, data: any) : void {
if (this.uniqueEvents[eventName]) {
this.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`);
} else {
this.logger.debug(`Unique event '${eventName}' triggered.`);
// Store the data so it can be passed to observers that register from now on.
this.uniqueEvents[eventName] = {
data: data
};
// Now pass the data to observers.
if (this.observables[eventName]) {
this.observables[eventName].next(data);
}
}
}
}

View File

@ -0,0 +1,57 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, Injector } from '@angular/core';
import { CoreSite } from '../classes/site';
/*
* Provider to create sites instances.
*/
@Injectable()
export class CoreSitesFactoryProvider {
constructor(private injector: Injector) {}
/**
* Make a site object.
*
* @param {string} id Site ID.
* @param {string} siteUrl Site URL.
* @param {string} token Site's WS token.
* @param {any} info Site info.
* @param {string} [privateToken] Private token.
* @param {any} [config] Site public config.
* @param {boolean} [loggedOut] Whether user is logged out.
* @return {CoreSite} Site instance.
* @description
* This returns a site object.
*/
makeSite = function(id: string, siteUrl: string, token: string, info: any, privateToken?: string,
config?: any, loggedOut?: boolean) : CoreSite {
return new CoreSite(this.injector, id, siteUrl, token, info, privateToken, config, loggedOut);
}
/**
* Gets the list of Site methods.
*
* @return {string[]} List of methods.
*/
getSiteMethods(): string[] {
let methods = [];
for (let name in CoreSite.prototype) {
methods.push(name);
}
return methods;
}
}

View File

@ -25,25 +25,6 @@ export class CoreTextUtilsProvider {
constructor(private translate: TranslateService, private langProvider: CoreLangProvider) {}
/**
* Add or remove 'www' from a URL. The url needs to have http or https protocol.
*
* @param {string} url URL to modify.
* @return {string} Modified URL.
*/
addOrRemoveWWW(url: string) : string {
if (url) {
if (url.match(/http(s)?:\/\/www\./)) {
// Already has www. Remove it.
url = url.replace('www.', '');
} else {
url = url.replace('https://', 'https://www.');
url = url.replace('http://', 'http://www.');
}
}
return url;
}
/**
* Given a list of sentences, build a message with all of them wrapped in <p>.
*
@ -326,47 +307,6 @@ export class CoreTextUtilsProvider {
});
}
/**
* Formats a URL, trim, lowercase, etc...
*
* @param {string} url The url to be formatted.
* @return {string} Fromatted url.
*/
formatURL(url: string) : string {
url = url.trim();
// Check if the URL starts by http or https.
if (! /^http(s)?\:\/\/.*/i.test(url)) {
// Test first allways https.
url = 'https://' + url;
}
// http allways in lowercase.
url = url.replace(/^http/i, 'http');
url = url.replace(/^https/i, 'https');
// Replace last slash.
url = url.replace(/\/$/, "");
return url;
}
/**
* Given a URL, returns what's after the last '/' without params.
* Example:
* http://mysite.com/a/course.html?id=1 -> course.html
*
* @param {string} url URL to treat.
* @return {string} Last file without params.
*/
getLastFileWithoutParams(url: string) : string {
let filename = url.substr(url.lastIndexOf('/') + 1);
if (filename.indexOf('?') != -1) {
filename = filename.substr(0, filename.indexOf('?'));
}
return filename;
}
/**
* Get the pluginfile URL to replace @@PLUGINFILE@@ wildcards.
*
@ -383,61 +323,6 @@ export class CoreTextUtilsProvider {
return undefined;
}
/**
* Get the protocol from a URL.
* E.g. http://www.google.com returns 'http'.
*
* @param {string} url URL to treat.
* @return {string} Protocol, undefined if no protocol found.
*/
getUrlProtocol(url: string) : string {
if (!url) {
return;
}
let matches = url.match(/^([^\/:\.\?]*):\/\//);
if (matches && matches[1]) {
return matches[1];
}
}
/**
* Get the scheme from a URL. Please notice that, if a URL has protocol, it will return the protocol.
* E.g. javascript:doSomething() returns 'javascript'.
*
* @param {string} url URL to treat.
* @return {string} Scheme, undefined if no scheme found.
*/
getUrlScheme(url: string) : string {
if (!url) {
return;
}
let matches = url.match(/^([a-z][a-z0-9+\-.]*):/);
if (matches && matches[1]) {
return matches[1];
}
}
/*
* Gets a username from a URL like: user@mysite.com.
*
* @param {string} url URL to treat.
* @return {string} Username. Undefined if no username found.
*/
getUsernameFromUrl(url: string) : string {
if (url.indexOf('@') > -1) {
// Get URL without protocol.
let withoutProtocol = url.replace(/.*?:\/\//, ''),
matches = withoutProtocol.match(/[^@]*/);
// Make sure that @ is at the start of the URL, not in a param at the end.
if (matches && matches.length && !matches[0].match(/[\/|?]/)) {
return matches[0];
}
}
}
/**
* Check if a text contains HTML tags.
*
@ -498,20 +383,6 @@ export class CoreTextUtilsProvider {
return json;
}
/**
* Remove protocol and www from a URL.
*
* @param {string} url URL to treat.
* @return {string} Treated URL.
*/
removeProtocolAndWWW(url: string) : string {
// Remove protocol.
url = url.replace(/.*?:\/\//g, '');
// Remove www.
url = url.replace(/^www./, '');
return url;
}
/**
* Replace all characters that cause problems with files in Android and iOS.
*

View File

@ -23,6 +23,25 @@ export class CoreUrlUtilsProvider {
constructor(private langProvider: CoreLangProvider) {}
/**
* Add or remove 'www' from a URL. The url needs to have http or https protocol.
*
* @param {string} url URL to modify.
* @return {string} Modified URL.
*/
addOrRemoveWWW(url: string) : string {
if (url) {
if (url.match(/http(s)?:\/\/www\./)) {
// Already has www. Remove it.
url = url.replace('www.', '');
} else {
url = url.replace('https://', 'https://www.');
url = url.replace('http://', 'http://www.');
}
}
return url;
}
/**
* Extracts the parameters from a URL and stores them in an object.
*
@ -41,6 +60,71 @@ export class CoreUrlUtilsProvider {
return params;
}
/**
* Generic function for adding the wstoken to Moodle urls and for pointing to the correct script.
* For download remote files from Moodle we need to use the special /webservice/pluginfile passing
* the ws token as a get parameter.
*
* @param {string} url The url to be fixed.
* @param {string} token Token to use.
* @return {string} Fixed URL.
*/
fixPluginfileURL(url: string, token: string) : string {
if (!url || !token) {
return '';
}
// First check if we need to fix this url or is already fixed.
if (url.indexOf('token=') != -1) {
return url;
}
// Check if is a valid URL (contains the pluginfile endpoint).
if (!this.isPluginFileUrl(url)) {
return url;
}
// In which way the server is serving the files? Are we using slash parameters?
if (url.indexOf('?file=') != -1 || url.indexOf('?forcedownload=') != -1 || url.indexOf('?rev=') != -1) {
url += '&';
} else {
url += '?';
}
// Always send offline=1 (for external repositories). It shouldn't cause problems for local files or old Moodles.
url += 'token=' + token + '&offline=1';
// Some webservices returns directly the correct download url, others not.
if (url.indexOf('/webservice/pluginfile') == -1) {
url = url.replace('/pluginfile', '/webservice/pluginfile');
}
return url;
}
/**
* Formats a URL, trim, lowercase, etc...
*
* @param {string} url The url to be formatted.
* @return {string} Fromatted url.
*/
formatURL(url: string) : string {
url = url.trim();
// Check if the URL starts by http or https.
if (! /^http(s)?\:\/\/.*/i.test(url)) {
// Test first allways https.
url = 'https://' + url;
}
// http allways in lowercase.
url = url.replace(/^http/i, 'http');
url = url.replace(/^https/i, 'https');
// Replace last slash.
url = url.replace(/\/$/, "");
return url;
}
/**
* Returns the URL to the documentation of the app, based on Moodle version and current language.
*
@ -67,6 +151,77 @@ export class CoreUrlUtilsProvider {
});
}
/**
* Given a URL, returns what's after the last '/' without params.
* Example:
* http://mysite.com/a/course.html?id=1 -> course.html
*
* @param {string} url URL to treat.
* @return {string} Last file without params.
*/
getLastFileWithoutParams(url: string) : string {
let filename = url.substr(url.lastIndexOf('/') + 1);
if (filename.indexOf('?') != -1) {
filename = filename.substr(0, filename.indexOf('?'));
}
return filename;
}
/**
* Get the protocol from a URL.
* E.g. http://www.google.com returns 'http'.
*
* @param {string} url URL to treat.
* @return {string} Protocol, undefined if no protocol found.
*/
getUrlProtocol(url: string) : string {
if (!url) {
return;
}
let matches = url.match(/^([^\/:\.\?]*):\/\//);
if (matches && matches[1]) {
return matches[1];
}
}
/**
* Get the scheme from a URL. Please notice that, if a URL has protocol, it will return the protocol.
* E.g. javascript:doSomething() returns 'javascript'.
*
* @param {string} url URL to treat.
* @return {string} Scheme, undefined if no scheme found.
*/
getUrlScheme(url: string) : string {
if (!url) {
return;
}
let matches = url.match(/^([a-z][a-z0-9+\-.]*):/);
if (matches && matches[1]) {
return matches[1];
}
}
/*
* Gets a username from a URL like: user@mysite.com.
*
* @param {string} url URL to treat.
* @return {string} Username. Undefined if no username found.
*/
getUsernameFromUrl(url: string) : string {
if (url.indexOf('@') > -1) {
// Get URL without protocol.
let withoutProtocol = url.replace(/.*?:\/\//, ''),
matches = withoutProtocol.match(/[^@]*/);
// Make sure that @ is at the start of the URL, not in a param at the end.
if (matches && matches.length && !matches[0].match(/[\/|?]/)) {
return matches[0];
}
}
}
/**
* Returns if a URL has any protocol (not a relative URL).
*
@ -128,42 +283,16 @@ export class CoreUrlUtilsProvider {
}
/**
* Generic function for adding the wstoken to Moodle urls and for pointing to the correct script.
* For download remote files from Moodle we need to use the special /webservice/pluginfile passing
* the ws token as a get parameter.
* Remove protocol and www from a URL.
*
* @param {string} url The url to be fixed.
* @param {string} token Token to use.
* @return {string} Fixed URL.
* @param {string} url URL to treat.
* @return {string} Treated URL.
*/
fixPluginfileURL(url: string, token: string) : string {
if (!url || !token) {
return '';
}
// First check if we need to fix this url or is already fixed.
if (url.indexOf('token=') != -1) {
return url;
}
// Check if is a valid URL (contains the pluginfile endpoint).
if (!this.isPluginFileUrl(url)) {
return url;
}
// In which way the server is serving the files? Are we using slash parameters?
if (url.indexOf('?file=') != -1 || url.indexOf('?forcedownload=') != -1 || url.indexOf('?rev=') != -1) {
url += '&';
} else {
url += '?';
}
// Always send offline=1 (for external repositories). It shouldn't cause problems for local files or old Moodles.
url += 'token=' + token + '&offline=1';
// Some webservices returns directly the correct download url, others not.
if (url.indexOf('/webservice/pluginfile') == -1) {
url = url.replace('/pluginfile', '/webservice/pluginfile');
}
removeProtocolAndWWW(url: string) : string {
// Remove protocol.
url = url.replace(/.*?:\/\//g, '');
// Remove www.
url = url.replace(/^www./, '');
return url;
}

View File

@ -14,6 +14,7 @@
import { Injectable } from '@angular/core';
import { Platform } from 'ionic-angular';
import { Observable } from 'rxjs';
import { InAppBrowser, InAppBrowserObject } from '@ionic-native/in-app-browser';
import { Clipboard } from '@ionic-native/clipboard';
import { CoreAppProvider } from '../app';
@ -349,6 +350,34 @@ export class CoreUtilsProvider {
return this.allPromises(promises);
}
/**
* Flatten an object, moving subobjects' properties to the first level using dot notation. E.g.:
* {a: {b: 1, c: 2}, d: 3} -> {'a.b': 1, 'a.c': 2, d: 3}
*
* @param {object} obj Object to flatten.
* @return {object} Flatten object.
*/
flattenObject(obj: object) : object {
let toReturn = {};
for (let name in obj) {
if (!obj.hasOwnProperty(name)) continue;
if (typeof obj[name] == 'object') {
let flatObject = this.flattenObject(obj[name]);
for (let subName in flatObject) {
if (!flatObject.hasOwnProperty(subName)) continue;
toReturn[name + '.' + subName] = flatObject[subName];
}
} else {
toReturn[name] = obj[name];
}
}
return toReturn;
}
/**
* Given an array of strings, return only the ones that match a regular expression.
*
@ -916,6 +945,18 @@ export class CoreUtilsProvider {
return mapped;
}
/**
* Given an observable, convert it to a Promise that will resolve with the first received value.
*
* @param {Observable<any>} obs The observable to convert.
* @return {Promise<any>} Promise.
*/
observableToPromise(obs: Observable<any>) : Promise<any> {
return new Promise((resolve, reject) => {
obs.subscribe(resolve, reject);
});
}
/**
* Similar to AngularJS $q.defer(). It will return an object containing the promise, and the resolve and reject functions.
*
@ -1021,6 +1062,17 @@ export class CoreUtilsProvider {
return query.length ? query.substr(0, query.length - 1) : query;
}
/**
* Stringify an object, sorting the properties. It doesn't sort arrays, only object properties. E.g.:
* {b: 2, a: 1} -> '{"a":1,"b":2}'
*
* @param {object} obj Object to stringify.
* @return {string} Stringified object.
*/
sortAndStringify(obj: object) : string {
return JSON.stringify(obj, Object.keys(this.flattenObject(obj)).sort());
}
/**
* Sum the filesizes from a list of files checking if the size will be partial or totally calculated.
*

View File

@ -104,7 +104,7 @@ export class CoreWSProvider {
* A wrapper function for a moodle WebService call.
*
* @param {string} method The WebService method to be called.
* @param {any} data Arguments to pass to the method.
* @param {any} data Arguments to pass to the method. It's recommended to call convertValuesToString before passing the data.
* @param {CoreWSPreSets} preSets Extra settings and information.
* @return {Promise} Promise resolved with the response data in success and rejected with the error message if it fails.
*/
@ -123,13 +123,6 @@ export class CoreWSProvider {
preSets.responseExpected = true;
}
try {
data = this.convertValuesToString(data, preSets.cleanUnicode);
} catch (e) {
// Empty cleaned text found.
return Promise.reject(this.createFakeWSError('mm.core.unicodenotsupportedcleanerror', true));
}
data.wsfunction = method;
data.wstoken = preSets.wsToken;
siteUrl = preSets.siteUrl + '/webservice/rest/server.php?moodlewsrestformat=json';
@ -182,33 +175,32 @@ export class CoreWSProvider {
siteUrl = preSets.siteUrl + '/lib/ajax/service.php';
return new Promise((resolve, reject) => {
this.http.post(siteUrl, JSON.stringify(ajaxData)).timeout(CoreConstants.wsTimeout).subscribe((data: any) => {
// Some moodle web services return null. If the responseExpected value is set then so long as no data
// is returned, we create a blank object.
if (!data && !preSets.responseExpected) {
data = [{}];
}
let observable = this.http.post(siteUrl, JSON.stringify(ajaxData)).timeout(CoreConstants.wsTimeout);
return this.utils.observableToPromise(observable).then((data: any) => {
// Some moodle web services return null. If the responseExpected value is set then so long as no data
// is returned, we create a blank object.
if (!data && !preSets.responseExpected) {
data = [{}];
}
// Check if error. Ajax layer should always return an object (if error) or an array (if success).
if (!data || typeof data != 'object') {
return rejectWithError(this.translate.instant('mm.core.serverconnection'));
} else if (data.error) {
return rejectWithError(data.error, data.errorcode);
}
// Check if error. Ajax layer should always return an object (if error) or an array (if success).
if (!data || typeof data != 'object') {
return rejectWithError(this.translate.instant('mm.core.serverconnection'));
} else if (data.error) {
return rejectWithError(data.error, data.errorcode);
}
// Get the first response since only one request was done.
data = data[0];
// Get the first response since only one request was done.
data = data[0];
if (data.error) {
return rejectWithError(data.exception.message, data.exception.errorcode);
}
if (data.error) {
return rejectWithError(data.exception.message, data.exception.errorcode);
}
return data.data;
}, (data) => {
let available = data.status == 404 ? -1 : 0;
return rejectWithError(this.translate.instant('mm.core.serverconnection'), '', available);
});
return data.data;
}, (data) => {
let available = data.status == 404 ? -1 : 0;
return rejectWithError(this.translate.instant('mm.core.serverconnection'), '', available);
});
// Convenience function to return an error.
@ -237,7 +229,7 @@ export class CoreWSProvider {
* @param {boolean} [stripUnicode] If Unicode long chars need to be stripped.
* @return {object} The cleaned object, with multilevel array and objects preserved.
*/
protected convertValuesToString(data: object, stripUnicode?: boolean) : object {
convertValuesToString(data: object, stripUnicode?: boolean) : object {
let result;
if (!Array.isArray(data) && typeof data == 'object') {
result = {};
@ -435,10 +427,7 @@ export class CoreWSProvider {
let promise = this.getPromiseHttp('head', url);
if (!promise) {
promise = new Promise((resolve, reject) => {
this.http.head(url).timeout(CoreConstants.wsTimeout).subscribe(resolve, reject);
});
promise = this.utils.observableToPromise(this.http.head(url).timeout(CoreConstants.wsTimeout));
this.setPromiseHttp(promise, 'head', url);
}
@ -455,58 +444,58 @@ export class CoreWSProvider {
* @return {Promise<any>} Promise resolved with the response data in success and rejected with CoreWSError if it fails.
*/
performPost(method: string, siteUrl: string, ajaxData: any, preSets: CoreWSPreSets) : Promise<any> {
// Create the promise for the request.
let promise = new Promise((resolve, reject) => {
// Perform the post request.
let observable = this.http.post(siteUrl, ajaxData).timeout(CoreConstants.wsTimeout),
promise;
this.http.post(siteUrl, ajaxData).timeout(CoreConstants.wsTimeout).subscribe((data: any) => {
// Some moodle web services return null.
// If the responseExpected value is set to false, we create a blank object if the response is null.
if (!data && !preSets.responseExpected) {
data = {};
}
if (!data) {
return Promise.reject(this.createFakeWSError('mm.core.serverconnection', true));
} else if (typeof data != preSets.typeExpected) {
this.logger.warn('Response of type "' + typeof data + `" received, expecting "${preSets.typeExpected}"`);
return Promise.reject(this.createFakeWSError('mm.core.errorinvalidresponse', true));
}
if (typeof data.exception !== 'undefined') {
return Promise.reject(data);
}
if (typeof data.debuginfo != 'undefined') {
return Promise.reject(this.createFakeWSError('Error. ' + data.message));
}
return data;
}, (error) => {
// If server has heavy load, retry after some seconds.
if (error.status == 429) {
let retryPromise = this.addToRetryQueue(method, siteUrl, ajaxData, preSets);
// Only process the queue one time.
if (this.retryTimeout == 0) {
this.retryTimeout = parseInt(error.headers('Retry-After'), 10) || 5;
this.logger.warn(`${error.statusText}. Retrying in ${this.retryTimeout} seconds. ` +
`${this.retryCalls.length} calls left.`);
setTimeout(() => {
this.logger.warn(`Retrying now with ${this.retryCalls.length} calls to process.`);
// Finish timeout.
this.retryTimeout = 0;
this.processRetryQueue();
}, this.retryTimeout * 1000);
} else {
this.logger.warn('Calls locked, trying later...');
}
return retryPromise;
}
promise = this.utils.observableToPromise(observable).then((data: any) => {
// Some moodle web services return null.
// If the responseExpected value is set to false, we create a blank object if the response is null.
if (!data && !preSets.responseExpected) {
data = {};
}
if (!data) {
return Promise.reject(this.createFakeWSError('mm.core.serverconnection', true));
});
} else if (typeof data != preSets.typeExpected) {
this.logger.warn('Response of type "' + typeof data + `" received, expecting "${preSets.typeExpected}"`);
return Promise.reject(this.createFakeWSError('mm.core.errorinvalidresponse', true));
}
if (typeof data.exception !== 'undefined') {
return Promise.reject(data);
}
if (typeof data.debuginfo != 'undefined') {
return Promise.reject(this.createFakeWSError('Error. ' + data.message));
}
return data;
}, (error) => {
// If server has heavy load, retry after some seconds.
if (error.status == 429) {
let retryPromise = this.addToRetryQueue(method, siteUrl, ajaxData, preSets);
// Only process the queue one time.
if (this.retryTimeout == 0) {
this.retryTimeout = parseInt(error.headers('Retry-After'), 10) || 5;
this.logger.warn(`${error.statusText}. Retrying in ${this.retryTimeout} seconds. ` +
`${this.retryCalls.length} calls left.`);
setTimeout(() => {
this.logger.warn(`Retrying now with ${this.retryCalls.length} calls to process.`);
// Finish timeout.
this.retryTimeout = 0;
this.processRetryQueue();
}, this.retryTimeout * 1000);
} else {
this.logger.warn('Calls locked, trying later...');
}
return retryPromise;
}
return Promise.reject(this.createFakeWSError('mm.core.serverconnection', true));
});
this.setPromiseHttp(promise, 'post', preSets.siteUrl, ajaxData);