diff --git a/src/classes/site.ts b/src/classes/site.ts
index dcf06ef29..54b2dd294 100644
--- a/src/classes/site.ts
+++ b/src/classes/site.ts
@@ -170,7 +170,7 @@ interface RequestQueueItem {
/**
* Class that represents a site (combination of site + user).
* It will have all the site data and provide utility functions regarding a site.
- * To add tables to the site's database, please use CoreSitesProvider.createTablesFromSchema. This will make sure that
+ * To add tables to the site's database, please use CoreSitesProvider.registerSiteSchema. This will make sure that
* the tables are created in all the sites, not just the current one.
*/
export class CoreSite {
@@ -233,6 +233,7 @@ export class CoreSite {
protected requestQueueTimeout = null;
protected tokenPluginFileWorks: boolean;
protected tokenPluginFileWorksPromise: Promise;
+ protected oauthId: number;
/**
* Create a site.
@@ -404,6 +405,15 @@ export class CoreSite {
return !!this.loggedOut;
}
+ /**
+ * Get OAuth ID.
+ *
+ * @return OAuth ID.
+ */
+ getOAuthId(): number {
+ return this.oauthId;
+ }
+
/**
* Set site info.
*
@@ -444,6 +454,24 @@ export class CoreSite {
this.loggedOut = !!loggedOut;
}
+ /**
+ * Set OAuth ID.
+ *
+ * @param oauth OAuth ID.
+ */
+ setOAuthId(oauthId: number): void {
+ this.oauthId = oauthId;
+ }
+
+ /**
+ * Check if the user authenticated in the site using an OAuth method.
+ *
+ * @return {boolean} Whether the user authenticated in the site using an OAuth method.
+ */
+ isOAuth(): boolean {
+ return this.oauthId != null && typeof this.oauthId != 'undefined';
+ }
+
/**
* Can the user access their private files?
*
diff --git a/src/core/emulator/providers/local-notifications.ts b/src/core/emulator/providers/local-notifications.ts
index d3234bcd8..affff2e19 100644
--- a/src/core/emulator/providers/local-notifications.ts
+++ b/src/core/emulator/providers/local-notifications.ts
@@ -14,10 +14,10 @@
import { Injectable } from '@angular/core';
import { LocalNotifications, ILocalNotification, ILocalNotificationAction } from '@ionic-native/local-notifications';
-import { CoreAppProvider } from '@providers/app';
+import { CoreAppProvider, CoreAppSchema } from '@providers/app';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreUtilsProvider } from '@providers/utils/utils';
-import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
+import { SQLiteDB } from '@classes/sqlitedb';
import { CoreConstants } from '@core/constants';
import { CoreConfigConstants } from '../../../configconstants';
import * as moment from 'moment';
@@ -43,41 +43,48 @@ export class LocalNotificationsMock extends LocalNotifications {
// Variables for database.
protected DESKTOP_NOTIFS_TABLE = 'desktop_local_notifications';
- protected tableSchema: SQLiteDBTableSchema = {
- name: this.DESKTOP_NOTIFS_TABLE,
- columns: [
+ protected tableSchema: CoreAppSchema = {
+ name: 'LocalNotificationsMock',
+ version: 1,
+ tables: [
{
- name: 'id',
- type: 'INTEGER',
- primaryKey: true
+ name: this.DESKTOP_NOTIFS_TABLE,
+ columns: [
+ {
+ name: 'id',
+ type: 'INTEGER',
+ primaryKey: true
+ },
+ {
+ name: 'title',
+ type: 'TEXT'
+ },
+ {
+ name: 'text',
+ type: 'TEXT'
+ },
+ {
+ name: 'at',
+ type: 'INTEGER'
+ },
+ {
+ name: 'data',
+ type: 'TEXT'
+ },
+ {
+ name: 'triggered',
+ type: 'INTEGER'
+ }
+ ],
},
- {
- name: 'title',
- type: 'TEXT'
- },
- {
- name: 'text',
- type: 'TEXT'
- },
- {
- name: 'at',
- type: 'INTEGER'
- },
- {
- name: 'data',
- type: 'TEXT'
- },
- {
- name: 'triggered',
- type: 'INTEGER'
- }
- ]
+ ],
};
protected appDB: SQLiteDB;
protected scheduled: { [i: number]: any } = {};
protected triggered: { [i: number]: any } = {};
protected observers: {[event: string]: Subject};
+ protected dbReady: Promise; // Promise resolved when the app DB is initialized.
protected defaults = {
actions : [],
attachments : [],
@@ -117,7 +124,9 @@ export class LocalNotificationsMock extends LocalNotifications {
super();
this.appDB = appProvider.getDB();
- this.appDB.createTableFromSchema(this.tableSchema);
+ this.dbReady = appProvider.createTablesFromSchema(this.tableSchema).catch(() => {
+ // Ignore errors.
+ });
// Initialize observers.
this.observers = {
@@ -550,20 +559,21 @@ export class LocalNotificationsMock extends LocalNotifications {
*
* @return Promise resolved with the notifications.
*/
- protected getAllNotifications(): Promise {
- return this.appDB.getAllRecords(this.DESKTOP_NOTIFS_TABLE).then((notifications) => {
- notifications.forEach((notification) => {
- notification.trigger = {
- at: new Date(notification.at)
- };
- notification.data = this.textUtils.parseJSON(notification.data);
- notification.triggered = !!notification.triggered;
+ protected async getAllNotifications(): Promise {
+ await this.dbReady;
- this.mergeWithDefaults(notification);
- });
+ const notifications = await this.appDB.getAllRecords(this.DESKTOP_NOTIFS_TABLE);
+ notifications.forEach((notification) => {
+ notification.trigger = {
+ at: new Date(notification.at),
+ };
+ notification.data = this.textUtils.parseJSON(notification.data);
+ notification.triggered = !!notification.triggered;
- return notifications;
+ this.mergeWithDefaults(notification);
});
+
+ return notifications;
}
/**
@@ -889,7 +899,9 @@ export class LocalNotificationsMock extends LocalNotifications {
* @param id ID of the notification.
* @return Promise resolved when done.
*/
- protected removeNotification(id: number): Promise {
+ protected async removeNotification(id: number): Promise {
+ await this.dbReady;
+
return this.appDB.deleteRecords(this.DESKTOP_NOTIFS_TABLE, { id: id });
}
@@ -979,7 +991,9 @@ export class LocalNotificationsMock extends LocalNotifications {
* @param triggered Whether the notification has been triggered.
* @return Promise resolved when stored.
*/
- protected storeNotification(notification: ILocalNotification, triggered: boolean): Promise {
+ protected async storeNotification(notification: ILocalNotification, triggered: boolean): Promise {
+ await this.dbReady;
+
// Only store some of the properties.
const entry = {
id : notification.id,
diff --git a/src/core/login/pages/reconnect/reconnect.html b/src/core/login/pages/reconnect/reconnect.html
index 3876f7e76..bfb460784 100644
--- a/src/core/login/pages/reconnect/reconnect.html
+++ b/src/core/login/pages/reconnect/reconnect.html
@@ -29,7 +29,7 @@
{{ 'core.login.reconnectdescription' | translate }}
-
-
diff --git a/src/core/login/pages/reconnect/reconnect.ts b/src/core/login/pages/reconnect/reconnect.ts
index b735b4db5..9372ed2b7 100644
--- a/src/core/login/pages/reconnect/reconnect.ts
+++ b/src/core/login/pages/reconnect/reconnect.ts
@@ -38,6 +38,7 @@ export class CoreLoginReconnectPage {
site: any;
showForgottenPassword = true;
showSiteAvatar = false;
+ isOAuth = false;
protected infoSiteUrl: string;
protected pageName: string;
@@ -88,6 +89,9 @@ export class CoreLoginReconnectPage {
this.siteUrl = site.infos.siteurl;
this.siteName = site.getSiteName();
+ // If login was OAuth we should only reach this page if the OAuth method ID has changed.
+ this.isOAuth = site.isOAuth();
+
// Show logo instead of avatar if it's a fixed site.
this.showSiteAvatar = this.site.avatar && !this.loginHelper.getFixedSites();
diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts
index b05fa15c9..7cd8a740c 100644
--- a/src/core/login/providers/helper.ts
+++ b/src/core/login/providers/helper.ts
@@ -62,6 +62,11 @@ export interface CoreLoginSSOData {
* Params to page to the page.
*/
pageParams?: any;
+
+ /**
+ * Other params added to the login url.
+ */
+ ssoUrlParams?: {[name: string]: any};
}
/**
@@ -180,7 +185,8 @@ export class CoreLoginHelperProvider {
}).then((data) => {
siteData = data;
- return this.handleSSOLoginAuthentication(siteData.siteUrl, siteData.token, siteData.privateToken);
+ return this.handleSSOLoginAuthentication(siteData.siteUrl, siteData.token, siteData.privateToken,
+ this.getOAuthIdFromParams(data.ssoUrlParams));
}).then(() => {
if (siteData.pageName) {
// State defined, go to that state instead of site initial page.
@@ -396,6 +402,16 @@ export class CoreLoginHelperProvider {
return 'core.mainmenu.' + (config && config.tool_mobile_forcelogout == '1' ? 'logout' : 'changesite');
}
+ /**
+ * Get the OAuth ID of some URL params (if it has an OAuth ID).
+ *
+ * @param params Params.
+ * @return OAuth ID.
+ */
+ getOAuthIdFromParams(params: {[name: string]: any}): number {
+ return params && typeof params.oauthsso != 'undefined' ? Number(params.oauthsso) : undefined;
+ }
+
/**
* Get the site policy.
*
@@ -548,11 +564,12 @@ export class CoreLoginHelperProvider {
* @param siteUrl Site's URL.
* @param token User's token.
* @param privateToken User's private token.
+ * @param oauthId OAuth ID. Only if the authentication was using an OAuth method.
* @return Promise resolved when the user is authenticated with the token.
*/
- handleSSOLoginAuthentication(siteUrl: string, token: string, privateToken?: string): Promise {
+ handleSSOLoginAuthentication(siteUrl: string, token: string, privateToken?: string, oauthId?: number): Promise {
// Always create a new site to prevent overriding data if another user credentials were introduced.
- return this.sitesProvider.newSite(siteUrl, token, privateToken);
+ return this.sitesProvider.newSite(siteUrl, token, privateToken, true, oauthId);
}
/**
@@ -778,15 +795,16 @@ export class CoreLoginHelperProvider {
return false;
}
- const service = this.sitesProvider.determineService(siteUrl),
- params = this.urlUtils.extractUrlParams(provider.url);
- let loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams);
+ const params = this.urlUtils.extractUrlParams(provider.url);
if (!params.id) {
return false;
}
- loginUrl += '&oauthsso=' + params.id;
+ const service = this.sitesProvider.determineService(siteUrl);
+ const loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams, {
+ oauthsso: params.id,
+ });
if (this.appProvider.isLinux()) {
// In Linux desktop app, always use embedded browser.
@@ -924,8 +942,12 @@ export class CoreLoginHelperProvider {
* @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used.
* @param pageName Name of the page to go once authenticated. If not defined, site initial page.
* @param pageParams Params of the state to go once authenticated.
+ * @param urlParams Other params to add to the URL.
+ * @return Login Url.
*/
- prepareForSSOLogin(siteUrl: string, service?: string, launchUrl?: string, pageName?: string, pageParams?: any): string {
+ prepareForSSOLogin(siteUrl: string, service?: string, launchUrl?: string, pageName?: string, pageParams?: any,
+ urlParams?: {[name: string]: any}): string {
+
service = service || CoreConfigConstants.wsextservice;
launchUrl = launchUrl || siteUrl + '/local/mobile/launch.php';
@@ -935,13 +957,18 @@ export class CoreLoginHelperProvider {
loginUrl += '&passport=' + passport;
loginUrl += '&urlscheme=' + CoreConfigConstants.customurlscheme;
+ if (urlParams) {
+ loginUrl = this.urlUtils.addParamsToUrl(loginUrl, urlParams);
+ }
+
// Store the siteurl and passport in CoreConfigProvider for persistence.
// We are "configuring" the app to wait for an SSO. CoreConfigProvider shouldn't be used as a temporary storage.
this.configProvider.set(CoreConstants.LOGIN_LAUNCH_DATA, JSON.stringify({
siteUrl: siteUrl,
passport: passport,
pageName: pageName || '',
- pageParams: pageParams || {}
+ pageParams: pageParams || {},
+ ssoUrlParams: urlParams || {},
}));
return loginUrl;
@@ -1059,6 +1086,41 @@ export class CoreLoginHelperProvider {
});
}
} else {
+ if (currentSite.isOAuth()) {
+ // User authenticated using an OAuth method. Check if it's still valid.
+ const identityProviders = this.getValidIdentityProviders(result.config);
+ const providerToUse = identityProviders.find((provider) => {
+ const params = this.urlUtils.extractUrlParams(provider.url);
+
+ return params.id == currentSite.getOAuthId();
+ });
+
+ if (providerToUse) {
+ if (!this.appProvider.isSSOAuthenticationOngoing() && !this.isSSOConfirmShown && !this.waitingForBrowser) {
+ // Open browser to perform the OAuth.
+ this.isSSOConfirmShown = true;
+
+ const confirmMessage = this.translate.instant('core.login.' +
+ (currentSite.isLoggedOut() ? 'loggedoutssodescription' : 'reconnectssodescription'));
+
+ this.domUtils.showConfirm(confirmMessage).then(() => {
+ this.waitingForBrowser = true;
+ this.sitesProvider.unsetCurrentSite(); // Unset current site to make authentication work fine.
+
+ this.openBrowserForOAuthLogin(siteUrl, providerToUse, result.config.launchurl, data.pageName,
+ data.params);
+ }).catch(() => {
+ // User cancelled, logout him.
+ this.sitesProvider.logout();
+ }).finally(() => {
+ this.isSSOConfirmShown = false;
+ });
+ }
+
+ return;
+ }
+ }
+
const info = currentSite.getInfo();
if (typeof info != 'undefined' && typeof info.username != 'undefined') {
const rootNavCtrl = this.appProvider.getRootNavController(),
@@ -1331,7 +1393,8 @@ export class CoreLoginHelperProvider {
token: params[1],
privateToken: params[2],
pageName: data.pageName,
- pageParams: data.pageParams
+ pageParams: data.pageParams,
+ ssoUrlParams: data.ssoUrlParams,
};
} else {
this.logger.debug('Invalid signature in the URL request yours: ' + params[0] + ' mine: '
diff --git a/src/core/pushnotifications/providers/pushnotifications.ts b/src/core/pushnotifications/providers/pushnotifications.ts
index 755300520..fea3bbc0d 100644
--- a/src/core/pushnotifications/providers/pushnotifications.ts
+++ b/src/core/pushnotifications/providers/pushnotifications.ts
@@ -18,7 +18,7 @@ import { Badge } from '@ionic-native/badge';
import { Push, PushObject, PushOptions } from '@ionic-native/push';
import { Device } from '@ionic-native/device';
import { TranslateService } from '@ngx-translate/core';
-import { CoreAppProvider } from '@providers/app';
+import { CoreAppProvider, CoreAppSchema } from '@providers/app';
import { CoreInitDelegate } from '@providers/init';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
@@ -31,7 +31,7 @@ import { CoreConfigProvider } from '@providers/config';
import { CoreConstants } from '@core/constants';
import { CoreConfigConstants } from '../../../configconstants';
import { ILocalNotification } from '@ionic-native/local-notifications';
-import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
+import { SQLiteDB } from '@classes/sqlitedb';
import { CoreSite } from '@classes/site';
import { CoreFilterProvider } from '@core/filter/providers/filter';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
@@ -84,54 +84,59 @@ export class CorePushNotificationsProvider {
protected logger;
protected pushID: string;
protected appDB: SQLiteDB;
+ protected dbReady: Promise; // Promise resolved when the app DB is initialized.
static COMPONENT = 'CorePushNotificationsProvider';
// Variables for database. The name still contains the name "addon" for backwards compatibility.
static BADGE_TABLE = 'addon_pushnotifications_badge';
static PENDING_UNREGISTER_TABLE = 'addon_pushnotifications_pending_unregister';
static REGISTERED_DEVICES_TABLE = 'addon_pushnotifications_registered_devices';
- protected appTablesSchema: SQLiteDBTableSchema[] = [
- {
- name: CorePushNotificationsProvider.BADGE_TABLE,
- columns: [
- {
- name: 'siteid',
- type: 'TEXT'
- },
- {
- name: 'addon',
- type: 'TEXT'
- },
- {
- name: 'number',
- type: 'INTEGER'
- }
- ],
- primaryKeys: ['siteid', 'addon']
- },
- {
- name: CorePushNotificationsProvider.PENDING_UNREGISTER_TABLE,
- columns: [
- {
- name: 'siteid',
- type: 'TEXT',
- primaryKey: true
- },
- {
- name: 'siteurl',
- type: 'TEXT'
- },
- {
- name: 'token',
- type: 'TEXT'
- },
- {
- name: 'info',
- type: 'TEXT'
- }
- ]
- }
- ];
+ protected appTablesSchema: CoreAppSchema = {
+ name: 'CorePushNotificationsProvider',
+ version: 1,
+ tables: [
+ {
+ name: CorePushNotificationsProvider.BADGE_TABLE,
+ columns: [
+ {
+ name: 'siteid',
+ type: 'TEXT'
+ },
+ {
+ name: 'addon',
+ type: 'TEXT'
+ },
+ {
+ name: 'number',
+ type: 'INTEGER'
+ },
+ ],
+ primaryKeys: ['siteid', 'addon'],
+ },
+ {
+ name: CorePushNotificationsProvider.PENDING_UNREGISTER_TABLE,
+ columns: [
+ {
+ name: 'siteid',
+ type: 'TEXT',
+ primaryKey: true
+ },
+ {
+ name: 'siteurl',
+ type: 'TEXT'
+ },
+ {
+ name: 'token',
+ type: 'TEXT'
+ },
+ {
+ name: 'info',
+ type: 'TEXT'
+ },
+ ],
+ },
+ ],
+ };
protected siteSchema: CoreSiteSchema = {
name: 'AddonPushNotificationsProvider', // The name still contains "Addon" for backwards compatibility.
version: 1,
@@ -182,7 +187,9 @@ export class CorePushNotificationsProvider {
private filterProvider: CoreFilterProvider, private filterDelegate: CoreFilterDelegate) {
this.logger = logger.getInstance('CorePushNotificationsProvider');
this.appDB = appProvider.getDB();
- this.appDB.createTablesFromSchema(this.appTablesSchema);
+ this.dbReady = appProvider.createTablesFromSchema(this.appTablesSchema).catch(() => {
+ // Ignore errors.
+ });
this.sitesProvider.registerSiteSchema(this.siteSchema);
platform.ready().then(() => {
@@ -211,10 +218,14 @@ export class CorePushNotificationsProvider {
* @param siteId Site ID.
* @return Resolved when done.
*/
- cleanSiteCounters(siteId: string): Promise {
- return this.appDB.deleteRecords(CorePushNotificationsProvider.BADGE_TABLE, {siteid: siteId} ).finally(() => {
+ async cleanSiteCounters(siteId: string): Promise {
+ await this.dbReady;
+
+ try {
+ await this.appDB.deleteRecords(CorePushNotificationsProvider.BADGE_TABLE, {siteid: siteId} );
+ } finally {
this.updateAppCounter();
- });
+ }
}
/**
@@ -532,11 +543,13 @@ export class CorePushNotificationsProvider {
* @param site Site to unregister from.
* @return Promise resolved when device is unregistered.
*/
- unregisterDeviceOnMoodle(site: CoreSite): Promise {
+ async unregisterDeviceOnMoodle(site: CoreSite): Promise {
if (!site || !this.appProvider.isMobile()) {
return Promise.reject(null);
}
+ await this.dbReady;
+
this.logger.debug(`Unregister device on Moodle: '${site.id}'`);
const data = {
@@ -544,9 +557,11 @@ export class CorePushNotificationsProvider {
uuid: this.device.uuid
};
- return site.write('core_user_remove_user_device', data).then((response) => {
+ try {
+ const response = await site.write('core_user_remove_user_device', data);
+
if (!response || !response.removed) {
- return Promise.reject(null);
+ throw null;
}
const promises = [];
@@ -561,22 +576,19 @@ export class CorePushNotificationsProvider {
return Promise.all(promises).catch(() => {
// Ignore errors.
});
- }).catch((error) => {
- if (this.utils.isWebServiceError(error)) {
- // It's a WebService error, can't unregister.
- return Promise.reject(error);
+ } catch (error) {
+ if (!this.utils.isWebServiceError(error)) {
+ // Store the pending unregister so it's retried again later.
+ await this.appDB.insertRecord(CorePushNotificationsProvider.PENDING_UNREGISTER_TABLE, {
+ siteid: site.id,
+ siteurl: site.getURL(),
+ token: site.getToken(),
+ info: JSON.stringify(site.getInfo()),
+ });
}
- // Store the pending unregister so it's retried again later.
- return this.appDB.insertRecord(CorePushNotificationsProvider.PENDING_UNREGISTER_TABLE, {
- siteid: site.id,
- siteurl: site.getURL(),
- token: site.getToken(),
- info: JSON.stringify(site.getInfo())
- }).then(() => {
- return Promise.reject(error);
- });
- });
+ throw error;
+ }
}
/**
@@ -724,52 +736,53 @@ export class CorePushNotificationsProvider {
* @param forceUnregister Whether to force unregister and register.
* @return Promise resolved when device is registered.
*/
- registerDeviceOnMoodle(siteId?: string, forceUnregister?: boolean): Promise {
+ async registerDeviceOnMoodle(siteId?: string, forceUnregister?: boolean): Promise {
this.logger.debug('Register device on Moodle.');
if (!this.canRegisterOnMoodle()) {
return Promise.reject(null);
}
- const data = this.getRegisterData();
- let result,
- site: CoreSite;
+ await this.dbReady;
- return this.sitesProvider.getSite(siteId).then((s) => {
- site = s;
+ const data = this.getRegisterData();
+ let result;
+
+ const site = await this.sitesProvider.getSite(siteId);
+
+ try {
if (forceUnregister) {
- return {unregister: true, register: true};
+ result = {unregister: true, register: true};
} else {
// Check if the device is already registered.
- return this.shouldRegister(data, site);
+ result = await this.shouldRegister(data, site);
}
- }).then((res) => {
- result = res;
if (result.unregister) {
// Unregister the device first.
- return this.unregisterDeviceOnMoodle(site).catch(() => {
+ await this.unregisterDeviceOnMoodle(site).catch(() => {
// Ignore errors.
});
}
- }).then(() => {
+
if (result.register) {
// Now register the device.
- return site.write('core_user_add_user_device', this.utils.clone(data)).then((response) => {
- // Insert the device in the local DB.
- return site.getDb().insertRecord(CorePushNotificationsProvider.REGISTERED_DEVICES_TABLE, data)
- .catch((error) => {
- // Ignore errors.
- });
- });
+ await site.write('core_user_add_user_device', this.utils.clone(data));
+
+ // Insert the device in the local DB.
+ try {
+ await site.getDb().insertRecord(CorePushNotificationsProvider.REGISTERED_DEVICES_TABLE, data);
+ } catch (err) {
+ // Ignore errors.
+ }
}
- }).finally(() => {
+ } finally {
// Remove pending unregisters for this site.
- this.appDB.deleteRecords(CorePushNotificationsProvider.PENDING_UNREGISTER_TABLE, {siteid: site.id}).catch(() => {
+ await this.appDB.deleteRecords(CorePushNotificationsProvider.PENDING_UNREGISTER_TABLE, {siteid: site.id}).catch(() => {
// Ignore errors.
});
- });
+ }
}
/**
@@ -779,12 +792,16 @@ export class CorePushNotificationsProvider {
* @param addon Registered addon name. If not defined it will store the site total.
* @return Promise resolved with the stored badge counter for the addon or site or 0 if none.
*/
- protected getAddonBadge(siteId?: string, addon: string = 'site'): Promise {
- return this.appDB.getRecord(CorePushNotificationsProvider.BADGE_TABLE, {siteid: siteId, addon: addon}).then((entry) => {
- return (entry && entry.number) || 0;
- }).catch(() => {
+ protected async getAddonBadge(siteId?: string, addon: string = 'site'): Promise {
+ await this.dbReady;
+
+ try {
+ const entry = await this.appDB.getRecord(CorePushNotificationsProvider.BADGE_TABLE, {siteid: siteId, addon: addon});
+
+ return (entry && entry.number) || 0;
+ } catch (err) {
return 0;
- });
+ }
}
/**
@@ -793,30 +810,26 @@ export class CorePushNotificationsProvider {
* @param siteId If defined, retry only for that site if needed. Otherwise, retry all pending unregisters.
* @return Promise resolved when done.
*/
- retryUnregisters(siteId?: string): Promise {
- let promise;
+ async retryUnregisters(siteId?: string): Promise {
+ await this.dbReady;
+
+ let results;
if (siteId) {
// Check if the site has a pending unregister.
- promise = this.appDB.getRecords(CorePushNotificationsProvider.PENDING_UNREGISTER_TABLE, {siteid: siteId});
+ results = await this.appDB.getRecords(CorePushNotificationsProvider.PENDING_UNREGISTER_TABLE, {siteid: siteId});
} else {
// Get all pending unregisters.
- promise = this.appDB.getAllRecords(CorePushNotificationsProvider.PENDING_UNREGISTER_TABLE);
+ results = await this.appDB.getAllRecords(CorePushNotificationsProvider.PENDING_UNREGISTER_TABLE);
}
- return promise.then((results) => {
- const promises = [];
+ return Promise.all(results.map((result) => {
+ // Create a temporary site to unregister.
+ const tmpSite = this.sitesFactory.makeSite(result.siteid, result.siteurl, result.token,
+ this.textUtils.parseJSON(result.info, {}));
- results.forEach((result) => {
- // Create a temporary site to unregister.
- const tmpSite = this.sitesFactory.makeSite(result.siteid, result.siteurl, result.token,
- this.textUtils.parseJSON(result.info, {}));
-
- promises.push(this.unregisterDeviceOnMoodle(tmpSite));
- });
-
- return Promise.all(promises);
- });
+ return this.unregisterDeviceOnMoodle(tmpSite);
+ }));
}
/**
@@ -827,7 +840,9 @@ export class CorePushNotificationsProvider {
* @param addon Registered addon name. If not defined it will store the site total.
* @return Promise resolved with the stored badge counter for the addon or site.
*/
- protected saveAddonBadge(value: number, siteId?: string, addon: string = 'site'): Promise {
+ protected async saveAddonBadge(value: number, siteId?: string, addon: string = 'site'): Promise {
+ await this.dbReady;
+
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const entry = {
@@ -836,9 +851,9 @@ export class CorePushNotificationsProvider {
number: value
};
- return this.appDB.insertRecord(CorePushNotificationsProvider.BADGE_TABLE, entry).then(() => {
- return value;
- });
+ await this.appDB.insertRecord(CorePushNotificationsProvider.BADGE_TABLE, entry);
+
+ return value;
}
/**
diff --git a/src/core/sharedfiles/providers/sharedfiles.ts b/src/core/sharedfiles/providers/sharedfiles.ts
index f95c1dc29..91f971436 100644
--- a/src/core/sharedfiles/providers/sharedfiles.ts
+++ b/src/core/sharedfiles/providers/sharedfiles.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import { Injectable } from '@angular/core';
-import { CoreAppProvider } from '@providers/app';
+import { CoreAppProvider, CoreAppSchema } from '@providers/app';
import { CoreEventsProvider } from '@providers/events';
import { CoreFileProvider } from '@providers/file';
import { CoreLoggerProvider } from '@providers/logger';
@@ -21,7 +21,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { Md5 } from 'ts-md5/dist/md5';
-import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
+import { SQLiteDB } from '@classes/sqlitedb';
/**
* Service to share files with the app.
@@ -32,19 +32,26 @@ export class CoreSharedFilesProvider {
// Variables for the database.
protected SHARED_FILES_TABLE = 'shared_files';
- protected tableSchema: SQLiteDBTableSchema = {
- name: this.SHARED_FILES_TABLE,
- columns: [
+ protected tableSchema: CoreAppSchema = {
+ name: 'CoreSharedFilesProvider',
+ version: 1,
+ tables: [
{
- name: 'id',
- type: 'TEXT',
- primaryKey: true
- }
- ]
+ name: this.SHARED_FILES_TABLE,
+ columns: [
+ {
+ name: 'id',
+ type: 'TEXT',
+ primaryKey: true
+ },
+ ],
+ },
+ ],
};
protected logger;
protected appDB: SQLiteDB;
+ protected dbReady: Promise; // Promise resolved when the app DB is initialized.
constructor(logger: CoreLoggerProvider, private fileProvider: CoreFileProvider, appProvider: CoreAppProvider,
private textUtils: CoreTextUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider,
@@ -52,7 +59,9 @@ export class CoreSharedFilesProvider {
this.logger = logger.getInstance('CoreSharedFilesProvider');
this.appDB = appProvider.getDB();
- this.appDB.createTableFromSchema(this.tableSchema);
+ this.dbReady = appProvider.createTablesFromSchema(this.tableSchema).catch(() => {
+ // Ignore errors.
+ });
}
/**
@@ -189,7 +198,9 @@ export class CoreSharedFilesProvider {
* @param fileId File ID.
* @return Resolved if treated, rejected otherwise.
*/
- protected isFileTreated(fileId: string): Promise {
+ protected async isFileTreated(fileId: string): Promise {
+ await this.dbReady;
+
return this.appDB.getRecord(this.SHARED_FILES_TABLE, { id: fileId });
}
@@ -199,12 +210,16 @@ export class CoreSharedFilesProvider {
* @param fileId File ID.
* @return Promise resolved when marked.
*/
- protected markAsTreated(fileId: string): Promise {
- // Check if it's already marked.
- return this.isFileTreated(fileId).catch(() => {
+ protected async markAsTreated(fileId: string): Promise {
+ await this.dbReady;
+
+ try {
+ // Check if it's already marked.
+ await this.isFileTreated(fileId);
+ } catch (err) {
// Doesn't exist, insert it.
- return this.appDB.insertRecord(this.SHARED_FILES_TABLE, { id: fileId });
- });
+ await this.appDB.insertRecord(this.SHARED_FILES_TABLE, { id: fileId });
+ }
}
/**
@@ -243,7 +258,9 @@ export class CoreSharedFilesProvider {
* @param fileId File ID.
* @return Resolved when unmarked.
*/
- protected unmarkAsTreated(fileId: string): Promise {
+ protected async unmarkAsTreated(fileId: string): Promise {
+ await this.dbReady;
+
return this.appDB.deleteRecords(this.SHARED_FILES_TABLE, { id: fileId });
}
}
diff --git a/src/providers/app.ts b/src/providers/app.ts
index f2071b076..7a50ff553 100644
--- a/src/providers/app.ts
+++ b/src/providers/app.ts
@@ -21,7 +21,7 @@ import { StatusBar } from '@ionic-native/status-bar';
import { CoreDbProvider } from './db';
import { CoreLoggerProvider } from './logger';
import { CoreEventsProvider } from './events';
-import { SQLiteDB } from '@classes/sqlitedb';
+import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
import { CoreConfigConstants } from '../configconstants';
/**
@@ -49,6 +49,37 @@ export interface CoreRedirectData {
timemodified?: number;
}
+/**
+ * App DB schema and migration function.
+ */
+export interface CoreAppSchema {
+ /**
+ * Name of the schema.
+ */
+ name: string;
+
+ /**
+ * Latest version of the schema (integer greater than 0).
+ */
+ version: number;
+
+ /**
+ * Tables to create when installing or upgrading the schema.
+ */
+ tables?: SQLiteDBTableSchema[];
+
+ /**
+ * Migrates the schema to the latest version.
+ *
+ * Called when installing and upgrading the schema, after creating the defined tables.
+ *
+ * @param db The affected DB.
+ * @param oldVersion Old version of the schema or 0 if not installed.
+ * @return Promise resolved when done.
+ */
+ migrate?(db: SQLiteDB, oldVersion: number): Promise;
+}
+
/**
* Factory to provide some global functionalities, like access to the global app database.
* @description
@@ -71,12 +102,33 @@ export class CoreAppProvider {
protected mainMenuOpen: number;
protected forceOffline = false;
+ // Variables for DB.
+ protected createVersionsTableReady: Promise;
+ protected SCHEMA_VERSIONS_TABLE = 'schema_versions';
+ protected versionsTableSchema: SQLiteDBTableSchema = {
+ name: this.SCHEMA_VERSIONS_TABLE,
+ columns: [
+ {
+ name: 'name',
+ type: 'TEXT',
+ primaryKey: true,
+ },
+ {
+ name: 'version',
+ type: 'INTEGER',
+ },
+ ],
+ };
+
constructor(dbProvider: CoreDbProvider, private platform: Platform, private keyboard: Keyboard, private appCtrl: App,
private network: Network, logger: CoreLoggerProvider, private events: CoreEventsProvider, zone: NgZone,
private menuCtrl: MenuController, private statusBar: StatusBar) {
this.logger = logger.getInstance('CoreAppProvider');
this.db = dbProvider.getDB(this.DBNAME);
+ // Create the schema versions table.
+ this.createVersionsTableReady = this.db.createTableFromSchema(this.versionsTableSchema);
+
this.keyboard.onKeyboardShow().subscribe((data) => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
zone.run(() => {
@@ -133,6 +185,47 @@ export class CoreAppProvider {
}
}
+ /**
+ * Install and upgrade a certain schema.
+ *
+ * @param schema The schema to create.
+ * @return Promise resolved when done.
+ */
+ async createTablesFromSchema(schema: CoreAppSchema): Promise {
+ this.logger.debug(`Apply schema to app DB: ${schema.name}`);
+
+ let oldVersion;
+
+ try {
+ // Wait for the schema versions table to be created.
+ await this.createVersionsTableReady;
+
+ // Fetch installed version of the schema.
+ const entry = await this.db.getRecord(this.SCHEMA_VERSIONS_TABLE, {name: schema.name});
+ oldVersion = entry.version;
+ } catch (error) {
+ // No installed version yet.
+ oldVersion = 0;
+ }
+
+ if (oldVersion >= schema.version) {
+ // Version already installed, nothing else to do.
+ return;
+ }
+
+ this.logger.debug(`Migrating schema '${schema.name}' of app DB from version ${oldVersion} to ${schema.version}`);
+
+ if (schema.tables) {
+ await this.db.createTablesFromSchema(schema.tables);
+ }
+ if (schema.migrate) {
+ await schema.migrate(this.db, oldVersion);
+ }
+
+ // Set installed version.
+ await this.db.insertRecord(this.SCHEMA_VERSIONS_TABLE, {name: schema.name, version: schema.version});
+ }
+
/**
* Get the application global database.
*
diff --git a/src/providers/config.ts b/src/providers/config.ts
index 38039bff6..709b5ce8e 100644
--- a/src/providers/config.ts
+++ b/src/providers/config.ts
@@ -13,8 +13,8 @@
// limitations under the License.
import { Injectable } from '@angular/core';
-import { CoreAppProvider } from './app';
-import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
+import { CoreAppProvider, CoreAppSchema } from './app';
+import { SQLiteDB } from '@classes/sqlitedb';
/**
* Factory to provide access to dynamic and permanent config and settings.
@@ -24,24 +24,34 @@ import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
export class CoreConfigProvider {
protected appDB: SQLiteDB;
protected TABLE_NAME = 'core_config';
- protected tableSchema: SQLiteDBTableSchema = {
- name: this.TABLE_NAME,
- columns: [
+ protected tableSchema: CoreAppSchema = {
+ name: 'CoreConfigProvider',
+ version: 1,
+ tables: [
{
- name: 'name',
- type: 'TEXT',
- unique: true,
- notNull: true
+ name: this.TABLE_NAME,
+ columns: [
+ {
+ name: 'name',
+ type: 'TEXT',
+ unique: true,
+ notNull: true
+ },
+ {
+ name: 'value'
+ },
+ ],
},
- {
- name: 'value'
- }
- ]
+ ],
};
+ protected dbReady: Promise; // Promise resolved when the app DB is initialized.
+
constructor(appProvider: CoreAppProvider) {
this.appDB = appProvider.getDB();
- this.appDB.createTableFromSchema(this.tableSchema);
+ this.dbReady = appProvider.createTablesFromSchema(this.tableSchema).catch(() => {
+ // Ignore errors.
+ });
}
/**
@@ -50,7 +60,9 @@ export class CoreConfigProvider {
* @param name The config name.
* @return Promise resolved when done.
*/
- delete(name: string): Promise {
+ async delete(name: string): Promise {
+ await this.dbReady;
+
return this.appDB.deleteRecords(this.TABLE_NAME, { name: name });
}
@@ -61,16 +73,20 @@ export class CoreConfigProvider {
* @param defaultValue Default value to use if the entry is not found.
* @return Resolves upon success along with the config data. Reject on failure.
*/
- get(name: string, defaultValue?: any): Promise {
- return this.appDB.getRecord(this.TABLE_NAME, { name: name }).then((entry) => {
+ async get(name: string, defaultValue?: any): Promise {
+ await this.dbReady;
+
+ try {
+ const entry = await this.appDB.getRecord(this.TABLE_NAME, { name: name });
+
return entry.value;
- }).catch((error) => {
+ } catch (error) {
if (typeof defaultValue != 'undefined') {
return defaultValue;
}
- return Promise.reject(error);
- });
+ throw error;
+ }
}
/**
@@ -80,7 +96,9 @@ export class CoreConfigProvider {
* @param value The config value. Can only store number or strings.
* @return Promise resolved when done.
*/
- set(name: string, value: number | string): Promise {
+ async set(name: string, value: number | string): Promise {
+ await this.dbReady;
+
return this.appDB.insertRecord(this.TABLE_NAME, { name: name, value: value });
}
}
diff --git a/src/providers/cron.ts b/src/providers/cron.ts
index dbf9ce3e1..f0b130e9f 100644
--- a/src/providers/cron.ts
+++ b/src/providers/cron.ts
@@ -14,12 +14,12 @@
import { Injectable, NgZone } from '@angular/core';
import { Network } from '@ionic-native/network';
-import { CoreAppProvider } from './app';
+import { CoreAppProvider, CoreAppSchema } from './app';
import { CoreConfigProvider } from './config';
import { CoreLoggerProvider } from './logger';
import { CoreUtilsProvider } from './utils/utils';
import { CoreConstants } from '@core/constants';
-import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
+import { SQLiteDB } from '@classes/sqlitedb';
/**
* Interface that all cron handlers must implement.
@@ -92,23 +92,30 @@ export class CoreCronDelegate {
// Variables for database.
protected CRON_TABLE = 'cron';
- protected tableSchema: SQLiteDBTableSchema = {
- name: this.CRON_TABLE,
- columns: [
+ protected tableSchema: CoreAppSchema = {
+ name: 'CoreCronDelegate',
+ version: 1,
+ tables: [
{
- name: 'id',
- type: 'TEXT',
- primaryKey: true
+ name: this.CRON_TABLE,
+ columns: [
+ {
+ name: 'id',
+ type: 'TEXT',
+ primaryKey: true
+ },
+ {
+ name: 'value',
+ type: 'INTEGER'
+ },
+ ],
},
- {
- name: 'value',
- type: 'INTEGER'
- }
- ]
+ ],
};
protected logger;
protected appDB: SQLiteDB;
+ protected dbReady: Promise; // Promise resolved when the app DB is initialized.
protected handlers: { [s: string]: CoreCronHandler } = {};
protected queuePromise = Promise.resolve();
@@ -117,7 +124,9 @@ export class CoreCronDelegate {
this.logger = logger.getInstance('CoreCronDelegate');
this.appDB = this.appProvider.getDB();
- this.appDB.createTableFromSchema(this.tableSchema);
+ this.dbReady = appProvider.createTablesFromSchema(this.tableSchema).catch(() => {
+ // Ignore errors.
+ });
// When the app is re-connected, start network handlers that were stopped.
network.onConnect().subscribe(() => {
@@ -306,16 +315,19 @@ export class CoreCronDelegate {
* @param name Handler's name.
* @return Promise resolved with the handler's last execution time.
*/
- protected getHandlerLastExecutionTime(name: string): Promise {
+ protected async getHandlerLastExecutionTime(name: string): Promise {
+ await this.dbReady;
+
const id = this.getHandlerLastExecutionId(name);
- return this.appDB.getRecord(this.CRON_TABLE, { id: id }).then((entry) => {
+ try {
+ const entry = await this.appDB.getRecord(this.CRON_TABLE, { id: id });
const time = parseInt(entry.value, 10);
return isNaN(time) ? 0 : time;
- }).catch(() => {
+ } catch (err) {
return 0; // Not set, return 0.
- });
+ }
}
/**
@@ -471,7 +483,9 @@ export class CoreCronDelegate {
* @param time Time to set.
* @return Promise resolved when the execution time is saved.
*/
- protected setHandlerLastExecutionTime(name: string, time: number): Promise {
+ protected async setHandlerLastExecutionTime(name: string, time: number): Promise {
+ await this.dbReady;
+
const id = this.getHandlerLastExecutionId(name),
entry = {
id: id,
diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts
index 9e8fa5790..8c51e421f 100644
--- a/src/providers/filepool.ts
+++ b/src/providers/filepool.ts
@@ -14,7 +14,7 @@
import { Injectable, NgZone } from '@angular/core';
import { Network } from '@ionic-native/network';
-import { CoreAppProvider } from './app';
+import { CoreAppProvider, CoreAppSchema } from './app';
import { CoreEventsProvider } from './events';
import { CoreFileProvider } from './file';
import { CoreInitDelegate } from './init';
@@ -28,7 +28,7 @@ import { CoreTextUtilsProvider } from './utils/text';
import { CoreTimeUtilsProvider } from './utils/time';
import { CoreUrlUtilsProvider } from './utils/url';
import { CoreUtilsProvider } from './utils/utils';
-import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
+import { SQLiteDB } from '@classes/sqlitedb';
import { CoreConstants } from '@core/constants';
import { Md5 } from 'ts-md5/dist/md5';
@@ -224,58 +224,62 @@ export class CoreFilepoolProvider {
protected FILES_TABLE = 'filepool_files'; // Downloaded files.
protected LINKS_TABLE = 'filepool_files_links'; // Links between downloaded files and components.
protected PACKAGES_TABLE = 'filepool_packages'; // Downloaded packages (sets of files).
- protected appTablesSchema: SQLiteDBTableSchema[] = [
- {
- name: this.QUEUE_TABLE,
- columns: [
- {
- name: 'siteId',
- type: 'TEXT'
- },
- {
- name: 'fileId',
- type: 'TEXT'
- },
- {
- name: 'added',
- type: 'INTEGER'
- },
- {
- name: 'priority',
- type: 'INTEGER'
- },
- {
- name: 'url',
- type: 'TEXT'
- },
- {
- name: 'revision',
- type: 'INTEGER'
- },
- {
- name: 'timemodified',
- type: 'INTEGER'
- },
- {
- name: 'isexternalfile',
- type: 'INTEGER'
- },
- {
- name: 'repositorytype',
- type: 'TEXT'
- },
- {
- name: 'path',
- type: 'TEXT'
- },
- {
- name: 'links',
- type: 'TEXT'
- }
- ],
- primaryKeys: ['siteId', 'fileId']
- }
- ];
+ protected appTablesSchema: CoreAppSchema = {
+ name: 'CoreFilepoolProvider',
+ version: 1,
+ tables: [
+ {
+ name: this.QUEUE_TABLE,
+ columns: [
+ {
+ name: 'siteId',
+ type: 'TEXT'
+ },
+ {
+ name: 'fileId',
+ type: 'TEXT'
+ },
+ {
+ name: 'added',
+ type: 'INTEGER'
+ },
+ {
+ name: 'priority',
+ type: 'INTEGER'
+ },
+ {
+ name: 'url',
+ type: 'TEXT'
+ },
+ {
+ name: 'revision',
+ type: 'INTEGER'
+ },
+ {
+ name: 'timemodified',
+ type: 'INTEGER'
+ },
+ {
+ name: 'isexternalfile',
+ type: 'INTEGER'
+ },
+ {
+ name: 'repositorytype',
+ type: 'TEXT'
+ },
+ {
+ name: 'path',
+ type: 'TEXT'
+ },
+ {
+ name: 'links',
+ type: 'TEXT'
+ },
+ ],
+ primaryKeys: ['siteId', 'fileId'],
+ },
+ ],
+ };
protected siteSchema: CoreSiteSchema = {
name: 'CoreFilepoolProvider',
version: 1,
@@ -392,6 +396,7 @@ export class CoreFilepoolProvider {
protected logger;
protected appDB: SQLiteDB;
+ protected dbReady: Promise; // Promise resolved when the app DB is initialized.
protected tokenRegex = new RegExp('(\\?|&)token=([A-Za-z0-9]*)');
protected queueState: string;
protected urlAttributes = [
@@ -415,7 +420,9 @@ export class CoreFilepoolProvider {
this.logger = logger.getInstance('CoreFilepoolProvider');
this.appDB = this.appProvider.getDB();
- this.appDB.createTablesFromSchema(this.appTablesSchema);
+ this.dbReady = appProvider.createTablesFromSchema(this.appTablesSchema).catch(() => {
+ // Ignore errors.
+ });
this.sitesProvider.registerSiteSchema(this.siteSchema);
@@ -567,11 +574,13 @@ export class CoreFilepoolProvider {
* @param link The link to add for the file.
* @return Promise resolved when the file is downloaded.
*/
- protected addToQueue(siteId: string, fileId: string, url: string, priority: number, revision: number, timemodified: number,
- filePath: string, onProgress?: (event: any) => any, options: any = {}, link?: any): Promise {
+ protected async addToQueue(siteId: string, fileId: string, url: string, priority: number, revision: number,
+ timemodified: number, filePath: string, onProgress?: (event: any) => any, options: any = {}, link?: any): Promise {
+ await this.dbReady;
+
this.logger.debug(`Adding ${fileId} to the queue`);
- return this.appDB.insertRecord(this.QUEUE_TABLE, {
+ await this.appDB.insertRecord(this.QUEUE_TABLE, {
siteId: siteId,
fileId: fileId,
url: url,
@@ -583,13 +592,13 @@ export class CoreFilepoolProvider {
repositorytype: options.repositorytype,
links: JSON.stringify(link ? [link] : []),
added: Date.now()
- }).then(() => {
- // Check if the queue is running.
- this.checkQueueProcessing();
- this.notifyFileDownloading(siteId, fileId);
-
- return this.getQueuePromise(siteId, fileId, true, onProgress);
});
+
+ // Check if the queue is running.
+ this.checkQueueProcessing();
+ this.notifyFileDownloading(siteId, fileId);
+
+ return this.getQueuePromise(siteId, fileId, true, onProgress);
}
/**
@@ -608,9 +617,11 @@ export class CoreFilepoolProvider {
* @param alreadyFixed Whether the URL has already been fixed.
* @return Resolved on success.
*/
- addToQueueByUrl(siteId: string, fileUrl: string, component?: string, componentId?: string | number, timemodified: number = 0,
- filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {}, revision?: number,
- alreadyFixed?: boolean): Promise {
+ async addToQueueByUrl(siteId: string, fileUrl: string, component?: string, componentId?: string | number,
+ timemodified: number = 0, filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {},
+ revision?: number, alreadyFixed?: boolean): Promise {
+ await this.dbReady;
+
let fileId,
link,
queueDeferred;
@@ -2309,16 +2320,17 @@ export class CoreFilepoolProvider {
* @param fileUrl The file URL.
* @return Resolved with file object from DB on success, rejected otherwise.
*/
- protected hasFileInQueue(siteId: string, fileId: string): Promise {
- return this.appDB.getRecord(this.QUEUE_TABLE, { siteId: siteId, fileId: fileId }).then((entry) => {
- if (typeof entry === 'undefined') {
- return Promise.reject(null);
- }
- // Convert the links to an object.
- entry.links = this.textUtils.parseJSON(entry.links, []);
+ protected async hasFileInQueue(siteId: string, fileId: string): Promise {
+ await this.dbReady;
- return entry;
- });
+ const entry = await this.appDB.getRecord(this.QUEUE_TABLE, { siteId: siteId, fileId: fileId });
+ if (typeof entry === 'undefined') {
+ throw null;
+ }
+ // Convert the links to an object.
+ entry.links = this.textUtils.parseJSON(entry.links, []);
+
+ return entry;
}
/**
@@ -2546,19 +2558,25 @@ export class CoreFilepoolProvider {
*
* @return Resolved on success. Rejected on failure.
*/
- protected processImportantQueueItem(): Promise {
- return this.appDB.getRecords(this.QUEUE_TABLE, undefined, 'priority DESC, added ASC', undefined, 0, 1).then((items) => {
- const item = items.pop();
- if (!item) {
- return Promise.reject(this.ERR_QUEUE_IS_EMPTY);
- }
- // Convert the links to an object.
- item.links = this.textUtils.parseJSON(item.links, []);
+ protected async processImportantQueueItem(): Promise {
+ await this.dbReady;
- return this.processQueueItem(item);
- }, () => {
- return Promise.reject(this.ERR_QUEUE_IS_EMPTY);
- });
+ let items;
+
+ try {
+ items = await this.appDB.getRecords(this.QUEUE_TABLE, undefined, 'priority DESC, added ASC', undefined, 0, 1);
+ } catch (err) {
+ throw this.ERR_QUEUE_IS_EMPTY;
+ }
+
+ const item = items.pop();
+ if (!item) {
+ throw this.ERR_QUEUE_IS_EMPTY;
+ }
+ // Convert the links to an object.
+ item.links = this.textUtils.parseJSON(item.links, []);
+
+ return this.processQueueItem(item);
}
/**
@@ -2685,7 +2703,9 @@ export class CoreFilepoolProvider {
* @param fileId The file ID.
* @return Resolved on success. Rejected on failure. It is advised to silently ignore failures.
*/
- protected removeFromQueue(siteId: string, fileId: string): Promise {
+ protected async removeFromQueue(siteId: string, fileId: string): Promise {
+ await this.dbReady;
+
return this.appDB.deleteRecords(this.QUEUE_TABLE, { siteId: siteId, fileId: fileId });
}
@@ -3003,27 +3023,26 @@ export class CoreFilepoolProvider {
*
* @return Promise resolved when done.
*/
- treatExtensionInQueue(): Promise {
+ async treatExtensionInQueue(): Promise {
+ await this.dbReady;
+
this.logger.debug('Treat extensions in queue');
- return this.appDB.getAllRecords(this.QUEUE_TABLE).then((entries) => {
- const promises = [];
- entries.forEach((entry) => {
+ const entries = await this.appDB.getAllRecords(this.QUEUE_TABLE);
- // For files in the queue, we only need to remove the extension from the fileId.
- // After downloading, additional info will be added.
- const fileId = entry.fileId;
- entry.fileId = this.mimeUtils.removeExtension(fileId);
+ return Promise.all(entries.map((entry) => {
- if (fileId == entry.fileId) {
- return;
- }
+ // For files in the queue, we only need to remove the extension from the fileId.
+ // After downloading, additional info will be added.
+ const fileId = entry.fileId;
+ entry.fileId = this.mimeUtils.removeExtension(fileId);
- promises.push(this.appDB.updateRecords(this.QUEUE_TABLE, { fileId: entry.fileId }, { fileId: fileId }));
- });
+ if (fileId == entry.fileId) {
+ return;
+ }
- return Promise.all(promises);
- });
+ return this.appDB.updateRecords(this.QUEUE_TABLE, { fileId: entry.fileId }, { fileId: fileId });
+ }));
}
/**
diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts
index 3967f8cfe..78f2ccb08 100644
--- a/src/providers/local-notifications.ts
+++ b/src/providers/local-notifications.ts
@@ -17,13 +17,13 @@ import { Platform, Alert, AlertController } from 'ionic-angular';
import { LocalNotifications, ILocalNotification } from '@ionic-native/local-notifications';
import { Push } from '@ionic-native/push';
import { TranslateService } from '@ngx-translate/core';
-import { CoreAppProvider } from './app';
+import { CoreAppProvider, CoreAppSchema } from './app';
import { CoreConfigProvider } from './config';
import { CoreEventsProvider } from './events';
import { CoreLoggerProvider } from './logger';
import { CoreTextUtilsProvider } from './utils/text';
import { CoreUtilsProvider } from './utils/utils';
-import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
+import { SQLiteDB } from '@classes/sqlitedb';
import { CoreConstants } from '@core/constants';
import { CoreConfigConstants } from '../configconstants';
import { Subject, Subscription } from 'rxjs';
@@ -40,56 +40,61 @@ export class CoreLocalNotificationsProvider {
protected SITES_TABLE = 'notification_sites'; // Store to asigne unique codes to each site.
protected COMPONENTS_TABLE = 'notification_components'; // Store to asigne unique codes to each component.
protected TRIGGERED_TABLE = 'notifications_triggered'; // Store to prevent re-triggering notifications.
- protected tablesSchema: SQLiteDBTableSchema[] = [
- {
- name: this.SITES_TABLE,
- columns: [
- {
- name: 'id',
- type: 'TEXT',
- primaryKey: true
- },
- {
- name: 'code',
- type: 'INTEGER',
- notNull: true
- }
- ]
- },
- {
- name: this.COMPONENTS_TABLE,
- columns: [
- {
- name: 'id',
- type: 'TEXT',
- primaryKey: true
- },
- {
- name: 'code',
- type: 'INTEGER',
- notNull: true
- }
- ]
- },
- {
- name: this.TRIGGERED_TABLE,
- columns: [
- {
- name: 'id',
- type: 'INTEGER',
- primaryKey: true
- },
- {
- name: 'at',
- type: 'INTEGER',
- notNull: true
- }
- ]
- }
- ];
+ protected tablesSchema: CoreAppSchema = {
+ name: 'CoreLocalNotificationsProvider',
+ version: 1,
+ tables: [
+ {
+ name: this.SITES_TABLE,
+ columns: [
+ {
+ name: 'id',
+ type: 'TEXT',
+ primaryKey: true
+ },
+ {
+ name: 'code',
+ type: 'INTEGER',
+ notNull: true
+ },
+ ],
+ },
+ {
+ name: this.COMPONENTS_TABLE,
+ columns: [
+ {
+ name: 'id',
+ type: 'TEXT',
+ primaryKey: true
+ },
+ {
+ name: 'code',
+ type: 'INTEGER',
+ notNull: true
+ },
+ ],
+ },
+ {
+ name: this.TRIGGERED_TABLE,
+ columns: [
+ {
+ name: 'id',
+ type: 'INTEGER',
+ primaryKey: true
+ },
+ {
+ name: 'at',
+ type: 'INTEGER',
+ notNull: true
+ },
+ ],
+ },
+ ],
+ };
protected logger;
protected appDB: SQLiteDB;
+ protected dbReady: Promise; // Promise resolved when the app DB is initialized.
protected codes: { [s: string]: number } = {};
protected codeRequestsQueue = {};
protected observables = {};
@@ -114,7 +119,9 @@ export class CoreLocalNotificationsProvider {
this.logger = logger.getInstance('CoreLocalNotificationsProvider');
this.appDB = appProvider.getDB();
- this.appDB.createTablesFromSchema(this.tablesSchema);
+ this.dbReady = appProvider.createTablesFromSchema(this.tablesSchema).catch(() => {
+ // Ignore errors.
+ });
platform.ready().then(() => {
// Listen to events.
@@ -242,34 +249,35 @@ export class CoreLocalNotificationsProvider {
* @param id ID of the element to get its code.
* @return Promise resolved when the code is retrieved.
*/
- protected getCode(table: string, id: string): Promise {
+ protected async getCode(table: string, id: string): Promise {
+ await this.dbReady;
+
const key = table + '#' + id;
// Check if the code is already in memory.
if (typeof this.codes[key] != 'undefined') {
- return Promise.resolve(this.codes[key]);
+ return this.codes[key];
}
- // Check if we already have a code stored for that ID.
- return this.appDB.getRecord(table, { id: id }).then((entry) => {
+ try {
+ // Check if we already have a code stored for that ID.
+ const entry = await this.appDB.getRecord(table, { id: id });
this.codes[key] = entry.code;
return entry.code;
- }).catch(() => {
+ } catch (err) {
// No code stored for that ID. Create a new code for it.
- return this.appDB.getRecords(table, undefined, 'code DESC').then((entries) => {
- let newCode = 0;
- if (entries.length > 0) {
- newCode = entries[0].code + 1;
- }
+ const entries = await this.appDB.getRecords(table, undefined, 'code DESC');
+ let newCode = 0;
+ if (entries.length > 0) {
+ newCode = entries[0].code + 1;
+ }
- return this.appDB.insertRecord(table, { id: id, code: newCode }).then(() => {
- this.codes[key] = newCode;
+ await this.appDB.insertRecord(table, { id: id, code: newCode });
+ this.codes[key] = newCode;
- return newCode;
- });
- });
- });
+ return newCode;
+ }
}
/**
@@ -352,8 +360,11 @@ export class CoreLocalNotificationsProvider {
* @param notification Notification to check.
* @return Promise resolved with a boolean indicating if promise is triggered (true) or not.
*/
- isTriggered(notification: ILocalNotification): Promise {
- return this.appDB.getRecord(this.TRIGGERED_TABLE, { id: notification.id }).then((stored) => {
+ async isTriggered(notification: ILocalNotification): Promise {
+ await this.dbReady;
+
+ try {
+ const stored = await this.appDB.getRecord(this.TRIGGERED_TABLE, { id: notification.id });
let triggered = (notification.trigger && notification.trigger.at) || 0;
if (typeof triggered != 'number') {
@@ -361,9 +372,9 @@ export class CoreLocalNotificationsProvider {
}
return stored.at === triggered;
- }).catch(() => {
+ } catch (err) {
return this.localNotifications.isTriggered(notification.id);
- });
+ }
}
/**
@@ -477,7 +488,9 @@ export class CoreLocalNotificationsProvider {
* @param id Notification ID.
* @return Promise resolved when it is removed.
*/
- removeTriggered(id: number): Promise {
+ async removeTriggered(id: number): Promise {
+ await this.dbReady;
+
return this.appDB.deleteRecords(this.TRIGGERED_TABLE, { id: id });
}
@@ -714,7 +727,9 @@ export class CoreLocalNotificationsProvider {
* @param notification Triggered notification.
* @return Promise resolved when stored, rejected otherwise.
*/
- trigger(notification: ILocalNotification): Promise {
+ async trigger(notification: ILocalNotification): Promise {
+ await this.dbReady;
+
const entry = {
id: notification.id,
at: notification.trigger && notification.trigger.at ? notification.trigger.at : Date.now()
@@ -730,7 +745,9 @@ export class CoreLocalNotificationsProvider {
* @param newName The new name.
* @return Promise resolved when done.
*/
- updateComponentName(oldName: string, newName: string): Promise {
+ async updateComponentName(oldName: string, newName: string): Promise {
+ await this.dbReady;
+
const oldId = this.COMPONENTS_TABLE + '#' + oldName,
newId = this.COMPONENTS_TABLE + '#' + newName;
diff --git a/src/providers/sites.ts b/src/providers/sites.ts
index adcd01681..dca1e4968 100644
--- a/src/providers/sites.ts
+++ b/src/providers/sites.ts
@@ -15,7 +15,7 @@
import { Injectable, Injector } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';
-import { CoreAppProvider } from './app';
+import { CoreAppProvider, CoreAppSchema } from './app';
import { CoreEventsProvider } from './events';
import { CoreLoggerProvider } from './logger';
import { CoreSitesFactoryProvider } from './sites-factory';
@@ -170,7 +170,7 @@ export const enum CoreSitesReadingStrategy {
* their own database tables. Example:
*
* constructor(sitesProvider: CoreSitesProvider) {
- * this.sitesProvider.createTableFromSchema(this.tableSchema);
+ * this.sitesProvider.registerSiteSchema(this.tableSchema);
*
* This provider will automatically create the tables in the databases of all the instantiated sites, and also to the
* databases of sites instantiated from now on.
@@ -178,62 +178,96 @@ export const enum CoreSitesReadingStrategy {
@Injectable()
export class CoreSitesProvider {
// Variables for the database.
- protected SITES_TABLE = 'sites';
- protected CURRENT_SITE_TABLE = 'current_site';
- protected SCHEMA_VERSIONS_TABLE = 'schema_versions';
- protected appTablesSchema: SQLiteDBTableSchema[] = [
- {
- name: this.SITES_TABLE,
- columns: [
- {
- name: 'id',
- type: 'TEXT',
- primaryKey: true
- },
- {
- name: 'siteUrl',
- type: 'TEXT',
- notNull: true
- },
- {
- name: 'token',
- type: 'TEXT'
- },
- {
- name: 'info',
- type: 'TEXT'
- },
- {
- name: 'privateToken',
- type: 'TEXT'
- },
- {
- name: 'config',
- type: 'TEXT'
- },
- {
- name: 'loggedOut',
- type: 'INTEGER'
+ static SITES_TABLE = 'sites_2';
+ static CURRENT_SITE_TABLE = 'current_site';
+ static SCHEMA_VERSIONS_TABLE = 'schema_versions';
+ protected appTablesSchema: CoreAppSchema = {
+ name: 'CoreSitesProvider',
+ version: 2,
+ tables: [
+ {
+ name: CoreSitesProvider.SITES_TABLE,
+ columns: [
+ {
+ name: 'id',
+ type: 'TEXT',
+ primaryKey: true
+ },
+ {
+ name: 'siteUrl',
+ type: 'TEXT',
+ notNull: true
+ },
+ {
+ name: 'token',
+ type: 'TEXT'
+ },
+ {
+ name: 'info',
+ type: 'TEXT'
+ },
+ {
+ name: 'privateToken',
+ type: 'TEXT'
+ },
+ {
+ name: 'config',
+ type: 'TEXT'
+ },
+ {
+ name: 'loggedOut',
+ type: 'INTEGER'
+ },
+ {
+ name: 'oauthId',
+ type: 'INTEGER'
+ },
+ ],
+ },
+ {
+ name: CoreSitesProvider.CURRENT_SITE_TABLE,
+ columns: [
+ {
+ name: 'id',
+ type: 'INTEGER',
+ primaryKey: true
+ },
+ {
+ name: 'siteId',
+ type: 'TEXT',
+ notNull: true,
+ unique: true
+ },
+ ],
+ },
+ ],
+ async migrate(db: SQLiteDB, oldVersion: number): Promise {
+ if (oldVersion < 2) {
+ const newTable = CoreSitesProvider.SITES_TABLE;
+ const oldTable = 'sites';
+
+ try {
+ // Check if V1 table exists.
+ await db.tableExists(oldTable);
+
+ // Move the records from the old table.
+ const sites = await db.getAllRecords(oldTable);
+ const promises = [];
+
+ sites.forEach((site) => {
+ promises.push(db.insertRecord(newTable, site));
+ });
+
+ await Promise.all(promises);
+
+ // Data moved, drop the old table.
+ await db.dropTable(oldTable);
+ } catch (error) {
+ // Old table does not exist, ignore.
}
- ]
+ }
},
- {
- name: this.CURRENT_SITE_TABLE,
- columns: [
- {
- name: 'id',
- type: 'INTEGER',
- primaryKey: true
- },
- {
- name: 'siteId',
- type: 'TEXT',
- notNull: true,
- unique: true
- }
- ]
- }
- ];
+ };
// Constants to validate a site version.
protected WORKPLACE_APP = 3;
@@ -249,13 +283,14 @@ export class CoreSitesProvider {
protected currentSite: CoreSite;
protected sites: { [s: string]: CoreSite } = {};
protected appDB: SQLiteDB;
+ protected dbReady: Promise; // Promise resolved when the app DB is initialized.
protected siteSchemasMigration: { [siteId: string]: Promise } = {};
// Schemas for site tables. Other providers can add schemas in here.
protected siteSchemas: { [name: string]: CoreSiteSchema } = {};
protected siteTablesSchemas: SQLiteDBTableSchema[] = [
{
- name: this.SCHEMA_VERSIONS_TABLE,
+ name: CoreSitesProvider.SCHEMA_VERSIONS_TABLE,
columns: [
{
name: 'name',
@@ -323,7 +358,9 @@ export class CoreSitesProvider {
this.logger = logger.getInstance('CoreSitesProvider');
this.appDB = appProvider.getDB();
- this.appDB.createTablesFromSchema(this.appTablesSchema);
+ this.dbReady = appProvider.createTablesFromSchema(this.appTablesSchema).catch(() => {
+ // Ignore errors.
+ });
this.registerSiteSchema(this.siteSchema);
}
@@ -600,16 +637,17 @@ export class CoreSitesProvider {
* @param token User's token.
* @param privateToken User's private token.
* @param login Whether to login the user in the site. Defaults to true.
+ * @param oauthId OAuth ID. Only if the authentication was using an OAuth method.
* @return A promise resolved with siteId when the site is added and the user is authenticated.
*/
- newSite(siteUrl: string, token: string, privateToken: string = '', login: boolean = true): Promise {
+ newSite(siteUrl: string, token: string, privateToken: string = '', login: boolean = true, oauthId?: number): Promise {
if (typeof login != 'boolean') {
login = true;
}
// Create a "candidate" site to fetch the site info.
- let candidateSite = this.sitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken),
- isNewSite = true;
+ let candidateSite = this.sitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken, undefined, undefined);
+ let isNewSite = true;
return candidateSite.fetchSiteInfo().then((info) => {
const result = this.isValidMoodleVersion(info);
@@ -627,12 +665,14 @@ export class CoreSitesProvider {
candidateSite.setToken(token);
candidateSite.setPrivateToken(privateToken);
candidateSite.setInfo(info);
+ candidateSite.setOAuthId(oauthId);
} else {
// New site, set site ID and info.
isNewSite = true;
candidateSite.setId(siteId);
candidateSite.setInfo(info);
+ candidateSite.setOAuthId(oauthId);
// Create database tables before login and before any WS call.
return this.migrateSiteSchemas(candidateSite);
@@ -652,7 +692,7 @@ export class CoreSitesProvider {
}
// Add site to sites list.
- this.addSite(siteId, siteUrl, token, info, privateToken, config);
+ this.addSite(siteId, siteUrl, token, info, privateToken, config, oauthId);
this.sites[siteId] = candidateSite;
if (login) {
@@ -857,9 +897,13 @@ export class CoreSitesProvider {
* @param info Site's info.
* @param privateToken User's private token.
* @param config Site config (from tool_mobile_get_config).
+ * @param oauthId OAuth ID. Only if the authentication was using an OAuth method.
* @return Promise resolved when done.
*/
- addSite(id: string, siteUrl: string, token: string, info: any, privateToken: string = '', config?: any): Promise {
+ async addSite(id: string, siteUrl: string, token: string, info: any, privateToken: string = '', config?: any,
+ oauthId?: number): Promise {
+ await this.dbReady;
+
const entry = {
id: id,
siteUrl: siteUrl,
@@ -867,10 +911,11 @@ export class CoreSitesProvider {
info: info ? JSON.stringify(info) : info,
privateToken: privateToken,
config: config ? JSON.stringify(config) : config,
- loggedOut: 0
+ loggedOut: 0,
+ oauthId: oauthId,
};
- return this.appDB.insertRecord(this.SITES_TABLE, entry);
+ return this.appDB.insertRecord(CoreSitesProvider.SITES_TABLE, entry);
}
/**
@@ -1070,29 +1115,32 @@ export class CoreSitesProvider {
* @param siteId ID of the site to delete.
* @return Promise to be resolved when the site is deleted.
*/
- deleteSite(siteId: string): Promise {
+ async deleteSite(siteId: string): Promise {
+ await this.dbReady;
+
this.logger.debug(`Delete site ${siteId}`);
if (typeof this.currentSite != 'undefined' && this.currentSite.id == siteId) {
this.logout();
}
- return this.getSite(siteId).then((site: CoreSite) => {
- return site.deleteDB().then(() => {
- // Site DB deleted, now delete the app from the list of sites.
- delete this.sites[siteId];
+ const site = await this.getSite(siteId);
- return this.appDB.deleteRecords(this.SITES_TABLE, { id: siteId }).then(() => {
- // Site deleted from sites list, now delete the folder.
- return site.deleteFolder();
- }, () => {
- // DB remove shouldn't fail, but we'll go ahead even if it does.
- return site.deleteFolder();
- }).then(() => {
- this.eventsProvider.trigger(CoreEventsProvider.SITE_DELETED, site, siteId);
- });
- });
- });
+ await site.deleteDB();
+
+ // Site DB deleted, now delete the app from the list of sites.
+ delete this.sites[siteId];
+
+ try {
+ await this.appDB.deleteRecords(CoreSitesProvider.SITES_TABLE, { id: siteId });
+ } catch (err) {
+ // DB remove shouldn't fail, but we'll go ahead even if it does.
+ }
+
+ // Site deleted from sites list, now delete the folder.
+ await site.deleteFolder();
+
+ this.eventsProvider.trigger(CoreEventsProvider.SITE_DELETED, site, siteId);
}
/**
@@ -1100,10 +1148,12 @@ export class CoreSitesProvider {
*
* @return Promise resolved with true if there are sites and false if there aren't.
*/
- hasSites(): Promise {
- return this.appDB.countRecords(this.SITES_TABLE).then((count) => {
- return count > 0;
- });
+ async hasSites(): Promise {
+ await this.dbReady;
+
+ const count = await this.appDB.countRecords(CoreSitesProvider.SITES_TABLE);
+
+ return count > 0;
}
/**
@@ -1112,18 +1162,24 @@ export class CoreSitesProvider {
* @param siteId The site ID. If not defined, current site (if available).
* @return Promise resolved with the site.
*/
- getSite(siteId?: string): Promise {
+ async getSite(siteId?: string): Promise {
+ await this.dbReady;
+
if (!siteId) {
- return this.currentSite ? Promise.resolve(this.currentSite) : Promise.reject(null);
+ if (this.currentSite) {
+ return this.currentSite;
+ }
+
+ throw null;
} else if (this.currentSite && this.currentSite.getId() == siteId) {
- return Promise.resolve(this.currentSite);
+ return this.currentSite;
} else if (typeof this.sites[siteId] != 'undefined') {
- return Promise.resolve(this.sites[siteId]);
+ return this.sites[siteId];
} else {
// Retrieve and create the site.
- return this.appDB.getRecord(this.SITES_TABLE, { id: siteId }).then((data) => {
- return this.makeSiteFromSiteListEntry(data);
- });
+ const data = await this.appDB.getRecord(CoreSitesProvider.SITES_TABLE, { id: siteId });
+
+ return this.makeSiteFromSiteListEntry(data);
}
}
@@ -1144,6 +1200,7 @@ export class CoreSitesProvider {
site = this.sitesFactory.makeSite(entry.id, entry.siteUrl, entry.token,
info, entry.privateToken, config, entry.loggedOut == 1);
+ site.setOAuthId(entry.oauthId);
return this.migrateSiteSchemas(site).then(() => {
// Set site after migrating schemas, or a call to getSite could get the site while tables are being created.
@@ -1199,27 +1256,29 @@ export class CoreSitesProvider {
* @param ids IDs of the sites to get. If not defined, return all sites.
* @return Promise resolved when the sites are retrieved.
*/
- getSites(ids?: string[]): Promise {
- return this.appDB.getAllRecords(this.SITES_TABLE).then((sites) => {
- const formattedSites = [];
- sites.forEach((site) => {
- if (!ids || ids.indexOf(site.id) > -1) {
- // Parse info.
- const siteInfo = site.info ? this.textUtils.parseJSON(site.info) : site.info,
- basicInfo: CoreSiteBasicInfo = {
- id: site.id,
- siteUrl: site.siteUrl,
- fullName: siteInfo && siteInfo.fullname,
- siteName: CoreConfigConstants.sitename ? CoreConfigConstants.sitename : siteInfo && siteInfo.sitename,
- avatar: siteInfo && siteInfo.userpictureurl,
- siteHomeId: siteInfo && siteInfo.siteid || 1
- };
- formattedSites.push(basicInfo);
- }
- });
+ async getSites(ids?: string[]): Promise {
+ await this.dbReady;
- return formattedSites;
+ const sites = await this.appDB.getAllRecords(CoreSitesProvider.SITES_TABLE);
+
+ const formattedSites = [];
+ sites.forEach((site) => {
+ if (!ids || ids.indexOf(site.id) > -1) {
+ // Parse info.
+ const siteInfo = site.info ? this.textUtils.parseJSON(site.info) : site.info;
+ const basicInfo: CoreSiteBasicInfo = {
+ id: site.id,
+ siteUrl: site.siteUrl,
+ fullName: siteInfo && siteInfo.fullname,
+ siteName: CoreConfigConstants.sitename ? CoreConfigConstants.sitename : siteInfo && siteInfo.sitename,
+ avatar: siteInfo && siteInfo.userpictureurl,
+ siteHomeId: siteInfo && siteInfo.siteid || 1,
+ };
+ formattedSites.push(basicInfo);
+ }
});
+
+ return formattedSites;
}
/**
@@ -1257,11 +1316,13 @@ export class CoreSitesProvider {
*
* @return Promise resolved when the sites IDs are retrieved.
*/
- getLoggedInSitesIds(): Promise {
- return this.appDB.getRecords(this.SITES_TABLE, {loggedOut : 0}).then((sites) => {
- return sites.map((site) => {
- return site.id;
- });
+ async getLoggedInSitesIds(): Promise {
+ await this.dbReady;
+
+ const sites = await this.appDB.getRecords(CoreSitesProvider.SITES_TABLE, {loggedOut : 0});
+
+ return sites.map((site) => {
+ return site.id;
});
}
@@ -1270,11 +1331,13 @@ export class CoreSitesProvider {
*
* @return Promise resolved when the sites IDs are retrieved.
*/
- getSitesIds(): Promise {
- return this.appDB.getAllRecords(this.SITES_TABLE).then((sites) => {
- return sites.map((site) => {
- return site.id;
- });
+ async getSitesIds(): Promise {
+ await this.dbReady;
+
+ const sites = await this.appDB.getAllRecords(CoreSitesProvider.SITES_TABLE);
+
+ return sites.map((site) => {
+ return site.id;
});
}
@@ -1284,15 +1347,17 @@ export class CoreSitesProvider {
* @param siteid ID of the site the user is accessing.
* @return Promise resolved when current site is stored.
*/
- login(siteId: string): Promise {
+ async login(siteId: string): Promise {
+ await this.dbReady;
+
const entry = {
id: 1,
siteId: siteId
};
- return this.appDB.insertRecord(this.CURRENT_SITE_TABLE, entry).then(() => {
- this.eventsProvider.trigger(CoreEventsProvider.LOGIN, {}, siteId);
- });
+ await this.appDB.insertRecord(CoreSitesProvider.CURRENT_SITE_TABLE, entry);
+
+ this.eventsProvider.trigger(CoreEventsProvider.LOGIN, {}, siteId);
}
/**
@@ -1300,7 +1365,9 @@ export class CoreSitesProvider {
*
* @return Promise resolved when the user is logged out.
*/
- logout(): Promise {
+ async logout(): Promise {
+ await this.dbReady;
+
let siteId;
const promises = [];
@@ -1314,12 +1381,14 @@ export class CoreSitesProvider {
promises.push(this.setSiteLoggedOut(siteId, true));
}
- promises.push(this.appDB.deleteRecords(this.CURRENT_SITE_TABLE, { id: 1 }));
+ promises.push(this.appDB.deleteRecords(CoreSitesProvider.CURRENT_SITE_TABLE, { id: 1 }));
}
- return Promise.all(promises).finally(() => {
+ try {
+ await Promise.all(promises);
+ } finally {
this.eventsProvider.trigger(CoreEventsProvider.LOGOUT, {}, siteId);
- });
+ }
}
/**
@@ -1327,21 +1396,24 @@ export class CoreSitesProvider {
*
* @return Promise resolved if a session is restored.
*/
- restoreSession(): Promise {
+ async restoreSession(): Promise {
if (this.sessionRestored) {
return Promise.reject(null);
}
+ await this.dbReady;
+
this.sessionRestored = true;
- return this.appDB.getRecord(this.CURRENT_SITE_TABLE, { id: 1 }).then((currentSite) => {
+ try {
+ const currentSite = await this.appDB.getRecord(CoreSitesProvider.CURRENT_SITE_TABLE, { id: 1 });
const siteId = currentSite.siteId;
this.logger.debug(`Restore session in site ${siteId}`);
return this.loadSite(siteId);
- }).catch(() => {
+ } catch (err) {
// No current session.
- });
+ }
}
/**
@@ -1351,17 +1423,18 @@ export class CoreSitesProvider {
* @param loggedOut True to set the site as logged out, false otherwise.
* @return Promise resolved when done.
*/
- setSiteLoggedOut(siteId: string, loggedOut: boolean): Promise {
- return this.getSite(siteId).then((site) => {
- const newValues = {
- token: '', // Erase the token for security.
- loggedOut: loggedOut ? 1 : 0
- };
+ async setSiteLoggedOut(siteId: string, loggedOut: boolean): Promise {
+ await this.dbReady;
- site.setLoggedOut(loggedOut);
+ const site = await this.getSite(siteId);
+ const newValues = {
+ token: '', // Erase the token for security.
+ loggedOut: loggedOut ? 1 : 0
+ };
- return this.appDB.updateRecords(this.SITES_TABLE, newValues, { id: siteId });
- });
+ site.setLoggedOut(loggedOut);
+
+ return this.appDB.updateRecords(CoreSitesProvider.SITES_TABLE, newValues, { id: siteId });
}
/**
@@ -1396,20 +1469,21 @@ export class CoreSitesProvider {
* @param privateToken User's private token.
* @return A promise resolved when the site is updated.
*/
- updateSiteTokenBySiteId(siteId: string, token: string, privateToken: string = ''): Promise {
- return this.getSite(siteId).then((site) => {
- const newValues = {
- token: token,
- privateToken: privateToken,
- loggedOut: 0
- };
+ async updateSiteTokenBySiteId(siteId: string, token: string, privateToken: string = ''): Promise {
+ await this.dbReady;
- site.token = token;
- site.privateToken = privateToken;
- site.setLoggedOut(false); // Token updated means the user authenticated again, not logged out anymore.
+ const site = await this.getSite(siteId);
+ const newValues = {
+ token: token,
+ privateToken: privateToken,
+ loggedOut: 0,
+ };
- return this.appDB.updateRecords(this.SITES_TABLE, newValues, { id: siteId });
- });
+ site.token = token;
+ site.privateToken = privateToken;
+ site.setLoggedOut(false); // Token updated means the user authenticated again, not logged out anymore.
+
+ return this.appDB.updateRecords(CoreSitesProvider.SITES_TABLE, newValues, { id: siteId });
}
/**
@@ -1418,39 +1492,49 @@ export class CoreSitesProvider {
* @param siteid Site's ID.
* @return A promise resolved when the site is updated.
*/
- updateSiteInfo(siteId: string): Promise {
- return this.getSite(siteId).then((site) => {
- return site.fetchSiteInfo().then((info) => {
- site.setInfo(info);
+ async updateSiteInfo(siteId: string): Promise {
+ await this.dbReady;
- const versionCheck = this.isValidMoodleVersion(info);
- if (versionCheck != this.VALID_VERSION) {
- // The Moodle version is not supported, reject.
- return this.treatInvalidAppVersion(versionCheck, site.getURL(), site.getId());
- }
+ const site = await this.getSite(siteId);
- // Try to get the site config.
- return this.getSiteConfig(site).catch(() => {
- // Error getting config, keep the current one.
- }).then((config) => {
- const newValues: any = {
- info: JSON.stringify(info),
- loggedOut: site.isLoggedOut() ? 1 : 0
- };
+ try {
- if (typeof config != 'undefined') {
- site.setConfig(config);
- newValues.config = JSON.stringify(config);
- }
+ const info = await site.fetchSiteInfo();
+ site.setInfo(info);
- return this.appDB.updateRecords(this.SITES_TABLE, newValues, { id: siteId }).finally(() => {
- this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, info, siteId);
- });
- });
- }).catch((error) => {
- // Ignore that we cannot fetch site info. Probably the auth token is invalid.
- });
- });
+ const versionCheck = this.isValidMoodleVersion(info);
+ if (versionCheck != this.VALID_VERSION) {
+ // The Moodle version is not supported, reject.
+ return this.treatInvalidAppVersion(versionCheck, site.getURL(), site.getId());
+ }
+
+ // Try to get the site config.
+ let config;
+
+ try {
+ config = await this.getSiteConfig(site);
+ } catch (error) {
+ // Error getting config, keep the current one.
+ }
+
+ const newValues: any = {
+ info: JSON.stringify(info),
+ loggedOut: site.isLoggedOut() ? 1 : 0,
+ };
+
+ if (typeof config != 'undefined') {
+ site.setConfig(config);
+ newValues.config = JSON.stringify(config);
+ }
+
+ try {
+ await this.appDB.updateRecords(CoreSitesProvider.SITES_TABLE, newValues, { id: siteId });
+ } finally {
+ this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, info, siteId);
+ }
+ } catch (error) {
+ // Ignore that we cannot fetch site info. Probably the auth token is invalid.
+ }
}
/**
@@ -1476,11 +1560,13 @@ export class CoreSitesProvider {
* @param username If set, it will return only the sites where the current user has this username.
* @return Promise resolved with the site IDs (array).
*/
- getSiteIdsFromUrl(url: string, prioritize?: boolean, username?: string): Promise {
+ async getSiteIdsFromUrl(url: string, prioritize?: boolean, username?: string): Promise {
+ await this.dbReady;
+
// If prioritize is true, check current site first.
if (prioritize && this.currentSite && this.currentSite.containsUrl(url)) {
if (!username || this.currentSite.getInfo().username == username) {
- return Promise.resolve([this.currentSite.getId()]);
+ return [this.currentSite.getId()];
}
}
@@ -1489,18 +1575,19 @@ export class CoreSitesProvider {
// URL doesn't have http(s) protocol. Check if it has any protocol.
if (this.urlUtils.isAbsoluteURL(url)) {
// It has some protocol. Return empty array.
- return Promise.resolve([]);
+ return [];
} else {
// No protocol, probably a relative URL. Return current site.
if (this.currentSite) {
- return Promise.resolve([this.currentSite.getId()]);
+ return [this.currentSite.getId()];
} else {
- return Promise.resolve([]);
+ return [];
}
}
}
- return this.appDB.getAllRecords(this.SITES_TABLE).then((siteEntries) => {
+ try {
+ const siteEntries = await this.appDB.getAllRecords(CoreSitesProvider.SITES_TABLE);
const ids = [];
const promises = [];
@@ -1516,13 +1603,13 @@ export class CoreSitesProvider {
}
});
- return Promise.all(promises).then(() => {
- return ids;
- });
- }).catch(() => {
+ await Promise.all(promises);
+
+ return ids;
+ } catch (error) {
// Shouldn't happen.
return [];
- });
+ }
}
/**
@@ -1530,10 +1617,12 @@ export class CoreSitesProvider {
*
* @return Promise resolved with the site ID.
*/
- getStoredCurrentSiteId(): Promise {
- return this.appDB.getRecord(this.CURRENT_SITE_TABLE, { id: 1 }).then((currentSite) => {
- return currentSite.siteId;
- });
+ async getStoredCurrentSiteId(): Promise {
+ await this.dbReady;
+
+ const currentSite = await this.appDB.getRecord(CoreSitesProvider.CURRENT_SITE_TABLE, { id: 1 });
+
+ return currentSite.siteId;
}
/**
@@ -1580,6 +1669,7 @@ export class CoreSitesProvider {
* Create a table in all the sites databases.
*
* @param table Table schema.
+ * @deprecated. Please use registerSiteSchema instead.
*/
createTableFromSchema(table: SQLiteDBTableSchema): void {
this.createTablesFromSchema([table]);
@@ -1589,6 +1679,7 @@ export class CoreSitesProvider {
* Create several tables in all the sites databases.
*
* @param tables List of tables schema.
+ * @deprecated. Please use registerSiteSchema instead.
*/
createTablesFromSchema(tables: SQLiteDBTableSchema[]): void {
// Add the tables to the list of schemas. This list is to create all the tables in new sites.
@@ -1677,7 +1768,7 @@ export class CoreSitesProvider {
const db = site.getDb();
// Fetch installed versions of the schema.
- return db.getAllRecords(this.SCHEMA_VERSIONS_TABLE).then((records) => {
+ return db.getAllRecords(CoreSitesProvider.SCHEMA_VERSIONS_TABLE).then((records) => {
const versions = {};
records.forEach((record) => {
versions[record.name] = record.version;
@@ -1702,7 +1793,8 @@ export class CoreSitesProvider {
}
// Set installed version.
- promise = promise.then(() => db.insertRecord(this.SCHEMA_VERSIONS_TABLE, {name, version: schema.version}));
+ promise = promise.then(() => db.insertRecord(CoreSitesProvider.SCHEMA_VERSIONS_TABLE,
+ {name, version: schema.version}));
promises.push(promise);
}
diff --git a/src/providers/urlschemes.ts b/src/providers/urlschemes.ts
index 9b8fe5e76..b7ed851c3 100644
--- a/src/providers/urlschemes.ts
+++ b/src/providers/urlschemes.ts
@@ -22,7 +22,7 @@ import { CoreDomUtilsProvider } from './utils/dom';
import { CoreTextUtilsProvider } from './utils/text';
import { CoreUrlUtilsProvider } from './utils/url';
import { CoreUtilsProvider } from './utils/utils';
-import { CoreLoginHelperProvider } from '@core/login/providers/helper';
+import { CoreLoginHelperProvider, CoreLoginSSOData } from '@core/login/providers/helper';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins';
@@ -32,21 +32,7 @@ import { CoreConstants } from '@core/constants';
/**
* All params that can be in a custom URL scheme.
*/
-export interface CoreCustomURLSchemesParams {
- /**
- * The site's URL.
- */
- siteUrl: string;
-
- /**
- * User's token. If set, user will be authenticated.
- */
- token?: string;
-
- /**
- * User's private token.
- */
- privateToken?: string;
+export interface CoreCustomURLSchemesParams extends CoreLoginSSOData {
/**
* Username.
@@ -57,16 +43,6 @@ export interface CoreCustomURLSchemesParams {
* URL to open once authenticated.
*/
redirect?: any;
-
- /**
- * Name of the page to go once authenticated.
- */
- pageName?: string;
-
- /**
- * Params to pass to the page.
- */
- pageParams?: any;
}
/*
@@ -162,7 +138,8 @@ export class CoreCustomURLSchemesProvider {
}
return promise.then(() => {
- return this.sitesProvider.newSite(data.siteUrl, data.token, data.privateToken, isSSOToken);
+ return this.sitesProvider.newSite(data.siteUrl, data.token, data.privateToken, isSSOToken,
+ this.loginHelper.getOAuthIdFromParams(data.ssoUrlParams));
});
} else {
// Token belongs to current site, no need to create it.