diff --git a/src/core/core.module.ts b/src/core/core.module.ts index df3bcffb0..55dd2b93d 100644 --- a/src/core/core.module.ts +++ b/src/core/core.module.ts @@ -18,10 +18,7 @@ import { ApplicationInitStatus, Injector, NgModule } from '@angular/core'; import { CoreApplicationInitStatus } from './classes/application-init-status'; import { CoreFeaturesModule } from './features/features.module'; import { CoreInterceptor } from './classes/interceptor'; -import { CORE_SITE_SCHEMAS } from './services/sites'; -import { SITE_SCHEMA as FILEPOOL_SITE_SCHEMA } from './services/db/filepool'; -import { SITE_SCHEMA as SITES_SITE_SCHEMA } from './services/db/sites'; -import { SITE_SCHEMA as SYNC_SITE_SCHEMA } from './services/db/sync'; +import { getDatabaseProviders } from './services/database'; import { getInitializerProviders } from './initializers'; @NgModule({ @@ -31,15 +28,7 @@ import { getInitializerProviders } from './initializers'; providers: [ { provide: HTTP_INTERCEPTORS, useClass: CoreInterceptor, multi: true }, { provide: ApplicationInitStatus, useClass: CoreApplicationInitStatus, deps: [Injector] }, - { - provide: CORE_SITE_SCHEMAS, - useValue: [ - FILEPOOL_SITE_SCHEMA, - SITES_SITE_SCHEMA, - SYNC_SITE_SCHEMA, - ], - multi: true, - }, + ...getDatabaseProviders(), ...getInitializerProviders(), ], }) diff --git a/src/core/features/course/course.module.ts b/src/core/features/course/course.module.ts index d63ef7568..728272224 100644 --- a/src/core/features/course/course.module.ts +++ b/src/core/features/course/course.module.ts @@ -16,7 +16,7 @@ import { NgModule } from '@angular/core'; import { CORE_SITE_SCHEMAS } from '@services/sites'; -import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/db/course'; +import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/database/course'; @NgModule({ providers: [ diff --git a/src/core/features/course/services/course-offline.ts b/src/core/features/course/services/course-offline.ts index 406f149ea..3be751813 100644 --- a/src/core/features/course/services/course-offline.ts +++ b/src/core/features/course/services/course-offline.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { makeSingleton } from '@singletons'; import { CoreSites } from '@services/sites'; -import { CoreCourseManualCompletionDBRecord, MANUAL_COMPLETION_TABLE } from './db/course'; +import { CoreCourseManualCompletionDBRecord, MANUAL_COMPLETION_TABLE } from './database/course'; import { CoreStatusWithWarningsWSResponse } from '@services/ws'; /** diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index 0ae0d5c57..40fa21bce 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -26,7 +26,7 @@ import { CoreConstants } from '@/core/constants'; import { makeSingleton, Translate } from '@singletons'; import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile } from '@services/ws'; -import { CoreCourseStatusDBRecord, COURSE_STATUS_TABLE } from './db/course'; +import { CoreCourseStatusDBRecord, COURSE_STATUS_TABLE } from './database/course'; import { CoreCourseOffline } from './course-offline'; import { CoreError } from '@classes/errors/error'; import { diff --git a/src/core/features/course/services/db/course.ts b/src/core/features/course/services/database/course.ts similarity index 100% rename from src/core/features/course/services/db/course.ts rename to src/core/features/course/services/database/course.ts diff --git a/src/core/initializers/initialize-databases.ts b/src/core/initializers/initialize-databases.ts new file mode 100644 index 000000000..462aeaeef --- /dev/null +++ b/src/core/initializers/initialize-databases.ts @@ -0,0 +1,31 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreApp } from '@services/app'; +import { CoreConfig } from '@services/config'; +import { CoreCron } from '@services/cron'; +import { CoreFilepool } from '@services/filepool'; +import { CoreLocalNotifications } from '@services/local-notifications'; +import { CoreSites } from '@services/sites'; + +export default async function(): Promise { + await Promise.all([ + CoreApp.instance.initialiseDatabase(), + CoreConfig.instance.initialiseDatabase(), + CoreCron.instance.initialiseDatabase(), + CoreFilepool.instance.initialiseDatabase(), + CoreLocalNotifications.instance.initialiseDatabase(), + CoreSites.instance.initialiseDatabase(), + ]); +} diff --git a/src/core/services/app.ts b/src/core/services/app.ts index f6ea7a4fb..945b9dc4b 100644 --- a/src/core/services/app.ts +++ b/src/core/services/app.ts @@ -25,7 +25,15 @@ import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { makeSingleton, Keyboard, Network, StatusBar, Platform, Device } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreColors } from '@singletons/colors'; -import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/db/app'; +import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app'; + +/** + * Object responsible of managing schema versions. + */ +type SchemaVersionsManager = { + get(schemaName: string): Promise; + set(schemaName: string, version: number): Promise; +}; /** * Factory to provide some global functionalities, like access to the global app database. @@ -55,14 +63,13 @@ export class CoreAppProvider { protected forceOffline = false; // Variables for DB. - protected createVersionsTableReady: Promise; + protected schemaVersionsManager: Promise; + protected resolveSchemaVersionsManager!: (schemaVersionsManager: SchemaVersionsManager) => void; constructor(protected router: Router) { - this.logger = CoreLogger.getInstance('CoreAppProvider'); + this.schemaVersionsManager = new Promise(resolve => this.resolveSchemaVersionsManager = resolve); this.db = CoreDB.instance.getDB(DBNAME); - - // Create the schema versions table. - this.createVersionsTableReady = this.db.createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA); + this.logger = CoreLogger.getInstance('CoreAppProvider'); // @todo // this.platform.registerBackButtonAction(() => { @@ -79,6 +86,30 @@ export class CoreAppProvider { return !!navigator.webdriver; } + /** + * Initialise database. + */ + async initialiseDatabase(): Promise { + await this.db.createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA); + + this.resolveSchemaVersionsManager({ + get: async name => { + try { + // Fetch installed version of the schema. + const entry = await this.db.getRecord(SCHEMA_VERSIONS_TABLE_NAME, { name }); + + return entry.version; + } catch (error) { + // No installed version yet. + return 0; + } + }, + set: async (name, version) => { + await this.db.insertRecord(SCHEMA_VERSIONS_TABLE_NAME, { name, version }); + }, + }); + } + /** * Check if the browser supports mediaDevices.getUserMedia. * @@ -115,20 +146,8 @@ export class CoreAppProvider { async createTablesFromSchema(schema: CoreAppSchema): Promise { this.logger.debug(`Apply schema to app DB: ${schema.name}`); - let oldVersion: number; - - 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(SCHEMA_VERSIONS_TABLE_NAME, { name: schema.name }); - - oldVersion = entry.version; - } catch (error) { - // No installed version yet. - oldVersion = 0; - } + const schemaVersionsManager = await this.schemaVersionsManager; + const oldVersion = await schemaVersionsManager.get(schema.name); if (oldVersion >= schema.version) { // Version already installed, nothing else to do. @@ -145,7 +164,7 @@ export class CoreAppProvider { } // Set installed version. - await this.db.insertRecord(SCHEMA_VERSIONS_TABLE_NAME, { name: schema.name, version: schema.version }); + schemaVersionsManager.set(schema.name, schema.version); } /** diff --git a/src/core/services/config.ts b/src/core/services/config.ts index a5979f4d6..a39e593ba 100644 --- a/src/core/services/config.ts +++ b/src/core/services/config.ts @@ -17,7 +17,7 @@ import { Injectable } from '@angular/core'; import { CoreApp } from '@services/app'; import { SQLiteDB } from '@classes/sqlitedb'; import { makeSingleton } from '@singletons'; -import { CONFIG_TABLE_NAME, APP_SCHEMA, ConfigDBEntry } from '@services/db/config'; +import { CONFIG_TABLE_NAME, APP_SCHEMA, ConfigDBEntry } from '@services/database/config'; /** * Factory to provide access to dynamic and permanent config and settings. @@ -26,14 +26,24 @@ import { CONFIG_TABLE_NAME, APP_SCHEMA, ConfigDBEntry } from '@services/db/confi @Injectable({ providedIn: 'root' }) export class CoreConfigProvider { - protected appDB: SQLiteDB; - protected dbReady: Promise; // Promise resolved when the app DB is initialized. + protected appDB: Promise; + protected resolveAppDB!: (appDB: SQLiteDB) => void; constructor() { - this.appDB = CoreApp.instance.getDB(); - this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => { + this.appDB = new Promise(resolve => this.resolveAppDB = resolve); + } + + /** + * Initialise database. + */ + async initialiseDatabase(): Promise { + try { + await CoreApp.instance.createTablesFromSchema(APP_SCHEMA); + } catch (e) { // Ignore errors. - }); + } + + this.resolveAppDB(CoreApp.instance.getDB()); } /** @@ -43,9 +53,9 @@ export class CoreConfigProvider { * @return Promise resolved when done. */ async delete(name: string): Promise { - await this.dbReady; + const db = await this.appDB; - await this.appDB.deleteRecords(CONFIG_TABLE_NAME, { name }); + await db.deleteRecords(CONFIG_TABLE_NAME, { name }); } /** @@ -56,10 +66,10 @@ export class CoreConfigProvider { * @return Resolves upon success along with the config data. Reject on failure. */ async get(name: string, defaultValue?: T): Promise { - await this.dbReady; + const db = await this.appDB; try { - const entry = await this.appDB.getRecord(CONFIG_TABLE_NAME, { name }); + const entry = await db.getRecord(CONFIG_TABLE_NAME, { name }); return entry.value; } catch (error) { @@ -79,9 +89,9 @@ export class CoreConfigProvider { * @return Promise resolved when done. */ async set(name: string, value: number | string): Promise { - await this.dbReady; + const db = await this.appDB; - await this.appDB.insertRecord(CONFIG_TABLE_NAME, { name, value }); + await db.insertRecord(CONFIG_TABLE_NAME, { name, value }); } } diff --git a/src/core/services/cron.ts b/src/core/services/cron.ts index 705b80cd7..bf799d90c 100644 --- a/src/core/services/cron.ts +++ b/src/core/services/cron.ts @@ -23,7 +23,7 @@ import { CoreError } from '@classes/errors/error'; import { makeSingleton, Network } from '@singletons'; import { CoreLogger } from '@singletons/logger'; -import { APP_SCHEMA, CRON_TABLE_NAME, CronDBEntry } from '@services/db/cron'; +import { APP_SCHEMA, CRON_TABLE_NAME, CronDBEntry } from '@services/database/cron'; /* * Service to handle cron processes. The registered processes will be executed every certain time. @@ -37,18 +37,16 @@ export class CoreCronDelegate { static readonly MAX_TIME_PROCESS = 120000; // Max time a process can block the queue. Defaults to 2 minutes. protected logger: CoreLogger; - protected appDB: SQLiteDB; - protected dbReady: Promise; // Promise resolved when the app DB is initialized. protected handlers: { [s: string]: CoreCronHandler } = {}; protected queuePromise: Promise = Promise.resolve(); - constructor(zone: NgZone) { - this.logger = CoreLogger.getInstance('CoreCronDelegate'); + // Variables for DB. + protected appDB: Promise; + protected resolveAppDB!: (appDB: SQLiteDB) => void; - this.appDB = CoreApp.instance.getDB(); - this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => { - // Ignore errors. - }); + constructor(zone: NgZone) { + this.appDB = new Promise(resolve => this.resolveAppDB = resolve); + this.logger = CoreLogger.getInstance('CoreCronDelegate'); // When the app is re-connected, start network handlers that were stopped. Network.instance.onConnect().subscribe(() => { @@ -59,6 +57,19 @@ export class CoreCronDelegate { }); } + /** + * Initialise database. + */ + async initialiseDatabase(): Promise { + try { + await CoreApp.instance.createTablesFromSchema(APP_SCHEMA); + } catch (e) { + // Ignore errors. + } + + this.resolveAppDB(CoreApp.instance.getDB()); + } + /** * Try to execute a handler. It will schedule the next execution once done. * If the handler cannot be executed or it fails, it will be re-executed after mmCoreCronMinInterval. @@ -235,12 +246,11 @@ export class CoreCronDelegate { * @return Promise resolved with the handler's last execution time. */ protected async getHandlerLastExecutionTime(name: string): Promise { - await this.dbReady; - + const db = await this.appDB; const id = this.getHandlerLastExecutionId(name); try { - const entry = await this.appDB.getRecord(CRON_TABLE_NAME, { id }); + const entry = await db.getRecord(CRON_TABLE_NAME, { id }); const time = Number(entry.value); @@ -395,15 +405,14 @@ export class CoreCronDelegate { * @return Promise resolved when the execution time is saved. */ protected async setHandlerLastExecutionTime(name: string, time: number): Promise { - await this.dbReady; - + const db = await this.appDB; const id = this.getHandlerLastExecutionId(name); const entry = { id, value: time, }; - await this.appDB.insertRecord(CRON_TABLE_NAME, entry); + await db.insertRecord(CRON_TABLE_NAME, entry); } /** diff --git a/src/core/services/db/app.ts b/src/core/services/database/app.ts similarity index 100% rename from src/core/services/db/app.ts rename to src/core/services/database/app.ts diff --git a/src/core/services/db/config.ts b/src/core/services/database/config.ts similarity index 100% rename from src/core/services/db/config.ts rename to src/core/services/database/config.ts diff --git a/src/core/services/db/cron.ts b/src/core/services/database/cron.ts similarity index 100% rename from src/core/services/db/cron.ts rename to src/core/services/database/cron.ts diff --git a/src/core/services/db/filepool.ts b/src/core/services/database/filepool.ts similarity index 100% rename from src/core/services/db/filepool.ts rename to src/core/services/database/filepool.ts diff --git a/src/core/services/database/index.ts b/src/core/services/database/index.ts new file mode 100644 index 000000000..ab8d9bd68 --- /dev/null +++ b/src/core/services/database/index.ts @@ -0,0 +1,32 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Provider } from '@angular/core'; +import { CORE_SITE_SCHEMAS } from '@services/sites'; + +import { SITE_SCHEMA as FILEPOOL_SITE_SCHEMA } from './filepool'; +import { SITE_SCHEMA as SITES_SITE_SCHEMA } from './sites'; +import { SITE_SCHEMA as SYNC_SITE_SCHEMA } from './sync'; + +export function getDatabaseProviders(): Provider[] { + return [{ + provide: CORE_SITE_SCHEMAS, + useValue: [ + FILEPOOL_SITE_SCHEMA, + SITES_SITE_SCHEMA, + SYNC_SITE_SCHEMA, + ], + multi: true, + }]; +} diff --git a/src/core/services/db/local-notifications.ts b/src/core/services/database/local-notifications.ts similarity index 100% rename from src/core/services/db/local-notifications.ts rename to src/core/services/database/local-notifications.ts diff --git a/src/core/services/db/sites.ts b/src/core/services/database/sites.ts similarity index 100% rename from src/core/services/db/sites.ts rename to src/core/services/database/sites.ts diff --git a/src/core/services/db/sync.ts b/src/core/services/database/sync.ts similarity index 100% rename from src/core/services/db/sync.ts rename to src/core/services/database/sync.ts diff --git a/src/core/services/filepool.ts b/src/core/services/filepool.ts index 9ab08dcb2..092a3ed54 100644 --- a/src/core/services/filepool.ts +++ b/src/core/services/filepool.ts @@ -45,7 +45,7 @@ import { CoreFilepoolPackageEntry, CoreFilepoolQueueEntry, CoreFilepoolQueueDBEntry, -} from '@services/db/filepool'; +} from '@services/database/filepool'; /* * Factory for handling downloading files and retrieve downloaded files. @@ -74,8 +74,6 @@ export class CoreFilepoolProvider { 'isexternalfile = 1 OR ((revision IS NULL OR revision = 0) AND (timemodified IS NULL OR timemodified = 0))'; protected logger: CoreLogger; - protected appDB: SQLiteDB; - protected dbReady: Promise; // Promise resolved when the app DB is initialized. protected queueState = CoreFilepoolProvider.QUEUE_PAUSED; protected urlAttributes: RegExp[] = [ new RegExp('(\\?|&)token=([A-Za-z0-9]*)'), @@ -91,17 +89,30 @@ export class CoreFilepoolProvider { protected packagesPromises: { [s: string]: { [s: string]: Promise } } = {}; protected filePromises: { [s: string]: { [s: string]: Promise } } = {}; + // Variables for DB. + protected appDB: Promise; + protected resolveAppDB!: (appDB: SQLiteDB) => void; + constructor() { + this.appDB = new Promise(resolve => this.resolveAppDB = resolve); this.logger = CoreLogger.getInstance('CoreFilepoolProvider'); - this.appDB = CoreApp.instance.getDB(); - this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => { - // Ignore errors. - }); - this.init(); } + /** + * Initialise database. + */ + async initialiseDatabase(): Promise { + try { + await CoreApp.instance.createTablesFromSchema(APP_SCHEMA); + } catch (e) { + // Ignore errors. + } + + this.resolveAppDB(CoreApp.instance.getDB()); + } + /** * Init some properties. */ @@ -264,11 +275,11 @@ export class CoreFilepoolProvider { options: CoreFilepoolFileOptions = {}, link?: CoreFilepoolComponentLink, ): Promise { - await this.dbReady; - this.logger.debug(`Adding ${fileId} to the queue`); - await this.appDB.insertRecord(QUEUE_TABLE_NAME, { + const db = await this.appDB; + + await db.insertRecord(QUEUE_TABLE_NAME, { siteId, fileId, url, @@ -318,8 +329,6 @@ export class CoreFilepoolProvider { revision?: number, alreadyFixed?: boolean, ): Promise { - await this.dbReady; - if (!CoreFile.instance.isAvailable()) { throw new CoreError('File system cannot be used.'); } @@ -398,7 +407,9 @@ export class CoreFilepoolProvider { // Update only when required. this.logger.debug(`Updating file ${fileId} which is already in queue`); - return this.appDB.updateRecords(QUEUE_TABLE_NAME, newData, primaryKey).then(() => + const db = await this.appDB; + + return db.updateRecords(QUEUE_TABLE_NAME, newData, primaryKey).then(() => this.getQueuePromise(siteId, fileId, true, onProgress)); } @@ -2110,9 +2121,8 @@ export class CoreFilepoolProvider { * @return Resolved with file object from DB on success, rejected otherwise. */ protected async hasFileInQueue(siteId: string, fileId: string): Promise { - await this.dbReady; - - const entry = await this.appDB.getRecord(QUEUE_TABLE_NAME, { siteId, fileId }); + const db = await this.appDB; + const entry = await db.getRecord(QUEUE_TABLE_NAME, { siteId, fileId }); if (typeof entry === 'undefined') { throw new CoreError('File not found in queue.'); @@ -2444,12 +2454,11 @@ export class CoreFilepoolProvider { * @return Resolved on success. Rejected on failure. */ protected async processImportantQueueItem(): Promise { - await this.dbReady; - let items: CoreFilepoolQueueEntry[]; + const db = await this.appDB; try { - items = await this.appDB.getRecords( + items = await db.getRecords( QUEUE_TABLE_NAME, undefined, 'priority DESC, added ASC', @@ -2593,9 +2602,9 @@ export class CoreFilepoolProvider { * @return Resolved on success. Rejected on failure. It is advised to silently ignore failures. */ protected async removeFromQueue(siteId: string, fileId: string): Promise { - await this.dbReady; + const db = await this.appDB; - await this.appDB.deleteRecords(QUEUE_TABLE_NAME, { siteId, fileId }); + await db.deleteRecords(QUEUE_TABLE_NAME, { siteId, fileId }); } /** diff --git a/src/core/services/local-notifications.ts b/src/core/services/local-notifications.ts index 87de10d11..0dac0d8af 100644 --- a/src/core/services/local-notifications.ts +++ b/src/core/services/local-notifications.ts @@ -34,7 +34,7 @@ import { COMPONENTS_TABLE_NAME, SITES_TABLE_NAME, CodeRequestsQueueItem, -} from '@services/db/local-notifications'; +} from '@services/database/local-notifications'; /** * Service to handle local notifications. @@ -43,8 +43,6 @@ import { export class CoreLocalNotificationsProvider { protected logger: CoreLogger; - protected appDB: SQLiteDB; - protected dbReady: Promise; // Promise resolved when the app DB is initialized. protected codes: { [s: string]: number } = {}; protected codeRequestsQueue: {[key: string]: CodeRequestsQueueItem} = {}; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -58,17 +56,31 @@ export class CoreLocalNotificationsProvider { protected updateSubscription?: Subscription; protected queueRunner: CoreQueueRunner; // Queue to decrease the number of concurrent calls to the plugin (see MOBILE-3477). + // Variables for DB. + protected appDB: Promise; + protected resolveAppDB!: (appDB: SQLiteDB) => void; + constructor() { + this.appDB = new Promise(resolve => this.resolveAppDB = resolve); this.logger = CoreLogger.getInstance('CoreLocalNotificationsProvider'); this.queueRunner = new CoreQueueRunner(10); - this.appDB = CoreApp.instance.getDB(); - this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => { - // Ignore errors. - }); this.init(); } + /** + * Initialise database. + */ + async initialiseDatabase(): Promise { + try { + await CoreApp.instance.createTablesFromSchema(APP_SCHEMA); + } catch (e) { + // Ignore errors. + } + + this.resolveAppDB(CoreApp.instance.getDB()); + } + /** * Init some properties. */ @@ -212,8 +224,6 @@ export class CoreLocalNotificationsProvider { * @return Promise resolved when the code is retrieved. */ protected async getCode(table: string, id: string): Promise { - await this.dbReady; - const key = table + '#' + id; // Check if the code is already in memory. @@ -221,23 +231,25 @@ export class CoreLocalNotificationsProvider { return this.codes[key]; } + const db = await this.appDB; + try { // Check if we already have a code stored for that ID. - const entry = await this.appDB.getRecord<{id: string; code: number}>(table, { id: id }); + const entry = await db.getRecord<{id: string; code: number}>(table, { id: id }); this.codes[key] = entry.code; return entry.code; } catch (err) { // No code stored for that ID. Create a new code for it. - const entries = await this.appDB.getRecords<{id: string; code: number}>(table, undefined, 'code DESC'); + const entries = await db.getRecords<{id: string; code: number}>(table, undefined, 'code DESC'); let newCode = 0; if (entries.length > 0) { newCode = entries[0].code + 1; } - await this.appDB.insertRecord(table, { id: id, code: newCode }); + await db.insertRecord(table, { id: id, code: newCode }); this.codes[key] = newCode; return newCode; @@ -324,10 +336,10 @@ export class CoreLocalNotificationsProvider { * @return Promise resolved with a boolean indicating if promise is triggered (true) or not. */ async isTriggered(notification: ILocalNotification, useQueue: boolean = true): Promise { - await this.dbReady; + const db = await this.appDB; try { - const stored = await this.appDB.getRecord<{ id: number; at: number }>( + const stored = await db.getRecord<{ id: number; at: number }>( TRIGGERED_TABLE_NAME, { id: notification.id }, ); @@ -481,9 +493,9 @@ export class CoreLocalNotificationsProvider { * @return Promise resolved when it is removed. */ async removeTriggered(id: number): Promise { - await this.dbReady; + const db = await this.appDB; - await this.appDB.deleteRecords(TRIGGERED_TABLE_NAME, { id: id }); + await db.deleteRecords(TRIGGERED_TABLE_NAME, { id: id }); } /** @@ -639,14 +651,13 @@ export class CoreLocalNotificationsProvider { * @return Promise resolved when stored, rejected otherwise. */ async trigger(notification: ILocalNotification): Promise { - await this.dbReady; - + const db = await this.appDB; const entry = { id: notification.id, at: notification.trigger && notification.trigger.at ? notification.trigger.at.getTime() : Date.now(), }; - return this.appDB.insertRecord(TRIGGERED_TABLE_NAME, entry); + return db.insertRecord(TRIGGERED_TABLE_NAME, entry); } /** @@ -657,12 +668,12 @@ export class CoreLocalNotificationsProvider { * @return Promise resolved when done. */ async updateComponentName(oldName: string, newName: string): Promise { - await this.dbReady; + const db = await this.appDB; const oldId = COMPONENTS_TABLE_NAME + '#' + oldName; const newId = COMPONENTS_TABLE_NAME + '#' + newName; - await this.appDB.updateRecords(COMPONENTS_TABLE_NAME, { id: newId }, { id: oldId }); + await db.updateRecords(COMPONENTS_TABLE_NAME, { id: newId }, { id: oldId }); } } diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index e635d681d..513c11f9c 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -47,7 +47,7 @@ import { SiteDBEntry, CurrentSiteDBEntry, SchemaVersionsDBEntry, -} from '@services/db/sites'; +} from '@services/database/sites'; import { CoreArray } from '../singletons/array'; export const CORE_SITE_SCHEMAS = new InjectionToken('CORE_SITE_SCHEMAS'); @@ -61,25 +61,27 @@ export const CORE_SITE_SCHEMAS = new InjectionToken('CORE_SITE_SCHEMAS'); export class CoreSitesProvider { // Constants to validate a site version. - protected readonly WORKPLACE_APP = 3; - protected readonly MOODLE_APP = 2; - protected readonly VALID_VERSION = 1; - protected readonly INVALID_VERSION = -1; + protected static readonly WORKPLACE_APP = 3; + protected static readonly MOODLE_APP = 2; + protected static readonly VALID_VERSION = 1; + protected static readonly INVALID_VERSION = -1; protected isWPApp = false; - protected logger: CoreLogger; protected services = {}; protected sessionRestored = false; 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 } = {}; protected siteSchemas: { [name: string]: CoreRegisteredSiteSchema } = {}; protected pluginsSiteSchemas: { [name: string]: CoreRegisteredSiteSchema } = {}; + // Variables for DB. + protected appDB: Promise; + protected resolveAppDB!: (appDB: SQLiteDB) => void; + constructor(@Optional() @Inject(CORE_SITE_SCHEMAS) siteSchemas: CoreSiteSchema[][] = []) { + this.appDB = new Promise(resolve => this.resolveAppDB = resolve); this.logger = CoreLogger.getInstance('CoreSitesProvider'); this.siteSchemas = CoreArray.flatten(siteSchemas).reduce( (siteSchemas, schema) => { @@ -89,10 +91,19 @@ export class CoreSitesProvider { }, this.siteSchemas, ); - this.appDB = CoreApp.instance.getDB(); - this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => { + } + + /** + * Initialise database. + */ + async initialiseDatabase(): Promise { + try { + await CoreApp.instance.createTablesFromSchema(APP_SCHEMA); + } catch (e) { // Ignore errors. - }); + } + + this.resolveAppDB(CoreApp.instance.getDB()); } /** @@ -429,7 +440,7 @@ export class CoreSitesProvider { const info = await candidateSite.fetchSiteInfo(); const result = this.isValidMoodleVersion(info); - if (result != this.VALID_VERSION) { + if (result != CoreSitesProvider.VALID_VERSION) { return this.treatInvalidAppVersion(result, siteUrl); } @@ -491,7 +502,7 @@ export class CoreSitesProvider { } catch (error) { // Error invaliddevice is returned by Workplace server meaning the same as connecttoworkplaceapp. if (error && error.errorcode == 'invaliddevice') { - return this.treatInvalidAppVersion(this.WORKPLACE_APP, siteUrl); + return this.treatInvalidAppVersion(CoreSitesProvider.WORKPLACE_APP, siteUrl); } throw error; @@ -512,11 +523,11 @@ export class CoreSitesProvider { let translateParams; switch (result) { - case this.MOODLE_APP: + case CoreSitesProvider.MOODLE_APP: errorKey = 'core.login.connecttomoodleapp'; errorCode = 'connecttomoodleapp'; break; - case this.WORKPLACE_APP: + case CoreSitesProvider.WORKPLACE_APP: errorKey = 'core.login.connecttoworkplaceapp'; errorCode = 'connecttoworkplaceapp'; break; @@ -581,7 +592,7 @@ export class CoreSitesProvider { */ protected isValidMoodleVersion(info: CoreSiteInfoResponse): number { if (!info) { - return this.INVALID_VERSION; + return CoreSitesProvider.INVALID_VERSION; } const version31 = 2016052300; @@ -606,7 +617,7 @@ export class CoreSitesProvider { } // Couldn't validate it. - return this.INVALID_VERSION; + return CoreSitesProvider.INVALID_VERSION; } /** @@ -623,14 +634,14 @@ export class CoreSitesProvider { } if (!this.isWPApp && isWorkplace) { - return this.WORKPLACE_APP; + return CoreSitesProvider.WORKPLACE_APP; } if (this.isWPApp && !isWorkplace) { - return this.MOODLE_APP; + return CoreSitesProvider.MOODLE_APP; } - return this.VALID_VERSION; + return CoreSitesProvider.VALID_VERSION; } /** @@ -669,8 +680,7 @@ export class CoreSitesProvider { config?: CoreSiteConfig, oauthId?: number, ): Promise { - await this.dbReady; - + const db = await this.appDB; const entry = { id, siteUrl, @@ -682,7 +692,7 @@ export class CoreSitesProvider { oauthId, }; - await this.appDB.insertRecord(SITES_TABLE_NAME, entry); + await db.insertRecord(SITES_TABLE_NAME, entry); } /** @@ -893,8 +903,6 @@ export class CoreSitesProvider { * @return Promise to be resolved when the site is deleted. */ async deleteSite(siteId: string): Promise { - await this.dbReady; - this.logger.debug(`Delete site ${siteId}`); if (typeof this.currentSite != 'undefined' && this.currentSite.id == siteId) { @@ -909,7 +917,9 @@ export class CoreSitesProvider { delete this.sites[siteId]; try { - await this.appDB.deleteRecords(SITES_TABLE_NAME, { id: siteId }); + const db = await this.appDB; + + await db.deleteRecords(SITES_TABLE_NAME, { id: siteId }); } catch (err) { // DB remove shouldn't fail, but we'll go ahead even if it does. } @@ -926,9 +936,8 @@ export class CoreSitesProvider { * @return Promise resolved with true if there are sites and false if there aren't. */ async hasSites(): Promise { - await this.dbReady; - - const count = await this.appDB.countRecords(SITES_TABLE_NAME); + const db = await this.appDB; + const count = await db.countRecords(SITES_TABLE_NAME); return count > 0; } @@ -940,8 +949,6 @@ export class CoreSitesProvider { * @return Promise resolved with the site. */ async getSite(siteId?: string): Promise { - await this.dbReady; - if (!siteId) { if (this.currentSite) { return this.currentSite; @@ -954,7 +961,8 @@ export class CoreSitesProvider { return this.sites[siteId]; } else { // Retrieve and create the site. - const data = await this.appDB.getRecord(SITES_TABLE_NAME, { id: siteId }); + const db = await this.appDB; + const data = await db.getRecord(SITES_TABLE_NAME, { id: siteId }); return this.makeSiteFromSiteListEntry(data); } @@ -1025,9 +1033,8 @@ export class CoreSitesProvider { * @return Promise resolved when the sites are retrieved. */ async getSites(ids?: string[]): Promise { - await this.dbReady; - - const sites = await this.appDB.getAllRecords(SITES_TABLE_NAME); + const db = await this.appDB; + const sites = await db.getAllRecords(SITES_TABLE_NAME); const formattedSites: CoreSiteBasicInfo[] = []; sites.forEach((site) => { @@ -1089,9 +1096,8 @@ export class CoreSitesProvider { * @return Promise resolved when the sites IDs are retrieved. */ async getLoggedInSitesIds(): Promise { - await this.dbReady; - - const sites = await this.appDB.getRecords(SITES_TABLE_NAME, { loggedOut : 0 }); + const db = await this.appDB; + const sites = await db.getRecords(SITES_TABLE_NAME, { loggedOut : 0 }); return sites.map((site) => site.id); } @@ -1102,9 +1108,8 @@ export class CoreSitesProvider { * @return Promise resolved when the sites IDs are retrieved. */ async getSitesIds(): Promise { - await this.dbReady; - - const sites = await this.appDB.getAllRecords(SITES_TABLE_NAME); + const db = await this.appDB; + const sites = await db.getAllRecords(SITES_TABLE_NAME); return sites.map((site) => site.id); } @@ -1116,14 +1121,13 @@ export class CoreSitesProvider { * @return Promise resolved when current site is stored. */ async login(siteId: string): Promise { - await this.dbReady; - + const db = await this.appDB; const entry = { id: 1, siteId, }; - await this.appDB.insertRecord(CURRENT_SITE_TABLE_NAME, entry); + await db.insertRecord(CURRENT_SITE_TABLE_NAME, entry); CoreEvents.trigger(CoreEvents.LOGIN, {}, siteId); } @@ -1134,12 +1138,11 @@ export class CoreSitesProvider { * @return Promise resolved when the user is logged out. */ async logout(): Promise { - await this.dbReady; - let siteId: string | undefined; const promises: Promise[] = []; if (this.currentSite) { + const db = await this.appDB; const siteConfig = this.currentSite.getStoredConfig(); siteId = this.currentSite.getId(); @@ -1149,7 +1152,7 @@ export class CoreSitesProvider { promises.push(this.setSiteLoggedOut(siteId, true)); } - promises.push(this.appDB.deleteRecords(CURRENT_SITE_TABLE_NAME, { id: 1 })); + promises.push(db.deleteRecords(CURRENT_SITE_TABLE_NAME, { id: 1 })); } try { @@ -1169,12 +1172,12 @@ export class CoreSitesProvider { return Promise.reject(new CoreError('Session already restored.')); } - await this.dbReady; + const db = await this.appDB; this.sessionRestored = true; try { - const currentSite = await this.appDB.getRecord(CURRENT_SITE_TABLE_NAME, { id: 1 }); + const currentSite = await db.getRecord(CURRENT_SITE_TABLE_NAME, { id: 1 }); const siteId = currentSite.siteId; this.logger.debug(`Restore session in site ${siteId}`); @@ -1192,8 +1195,7 @@ export class CoreSitesProvider { * @return Promise resolved when done. */ async setSiteLoggedOut(siteId: string, loggedOut: boolean): Promise { - await this.dbReady; - + const db = await this.appDB; const site = await this.getSite(siteId); const newValues = { token: '', // Erase the token for security. @@ -1202,7 +1204,7 @@ export class CoreSitesProvider { site.setLoggedOut(loggedOut); - await this.appDB.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId }); + await db.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId }); } /** @@ -1238,8 +1240,7 @@ export class CoreSitesProvider { * @return A promise resolved when the site is updated. */ async updateSiteTokenBySiteId(siteId: string, token: string, privateToken: string = ''): Promise { - await this.dbReady; - + const db = await this.appDB; const site = await this.getSite(siteId); const newValues = { token, @@ -1251,7 +1252,7 @@ export class CoreSitesProvider { site.privateToken = privateToken; site.setLoggedOut(false); // Token updated means the user authenticated again, not logged out anymore. - await this.appDB.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId }); + await db.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId }); } /** @@ -1261,8 +1262,6 @@ export class CoreSitesProvider { * @return A promise resolved when the site is updated. */ async updateSiteInfo(siteId: string): Promise { - await this.dbReady; - const site = await this.getSite(siteId); try { @@ -1270,7 +1269,7 @@ export class CoreSitesProvider { site.setInfo(info); const versionCheck = this.isValidMoodleVersion(info); - if (versionCheck != this.VALID_VERSION) { + if (versionCheck != CoreSitesProvider.VALID_VERSION) { // The Moodle version is not supported, reject. return this.treatInvalidAppVersion(versionCheck, site.getURL(), site.getId()); } @@ -1295,7 +1294,9 @@ export class CoreSitesProvider { } try { - await this.appDB.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId }); + const db = await this.appDB; + + await db.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId }); } finally { CoreEvents.trigger(CoreEvents.SITE_UPDATED, info, siteId); } @@ -1328,8 +1329,6 @@ export class CoreSitesProvider { * @return Promise resolved with the site IDs (array). */ 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) { @@ -1354,7 +1353,8 @@ export class CoreSitesProvider { } try { - const siteEntries = await this.appDB.getAllRecords(SITES_TABLE_NAME); + const db = await this.appDB; + const siteEntries = await db.getAllRecords(SITES_TABLE_NAME); const ids: string[] = []; const promises: Promise[] = []; @@ -1385,9 +1385,8 @@ export class CoreSitesProvider { * @return Promise resolved with the site ID. */ async getStoredCurrentSiteId(): Promise { - await this.dbReady; - - const currentSite = await this.appDB.getRecord(CURRENT_SITE_TABLE_NAME, { id: 1 }); + const db = await this.appDB; + const currentSite = await db.getRecord(CURRENT_SITE_TABLE_NAME, { id: 1 }); return currentSite.siteId; } diff --git a/src/core/services/sync.ts b/src/core/services/sync.ts index b650e2998..7c3c7ca22 100644 --- a/src/core/services/sync.ts +++ b/src/core/services/sync.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreEvents } from '@singletons/events'; import { CoreSites } from '@services/sites'; import { makeSingleton } from '@singletons'; -import { SYNC_TABLE_NAME, CoreSyncRecord } from '@services/db/sync'; +import { SYNC_TABLE_NAME, CoreSyncRecord } from '@services/database/sync'; /* * Service that provides some features regarding synchronization.