MOBILE-3565 services: Move some DB vars and init to new files

main
Dani Palou 2020-10-28 14:25:18 +01:00
parent 03f9723329
commit 6df1c2109d
16 changed files with 1034 additions and 857 deletions

View File

@ -50,6 +50,11 @@ import { CoreTimeUtilsProvider } from '@services/utils/time';
import { CoreUrlUtilsProvider } from '@services/utils/url'; import { CoreUrlUtilsProvider } from '@services/utils/url';
import { CoreUtilsProvider } from '@services/utils/utils'; import { CoreUtilsProvider } from '@services/utils/utils';
// Import init DB functions of core services.
import { initCoreFilepoolDB } from '@services/filepool.db';
import { initCoreSitesDB } from '@services/sites.db';
import { initCoreSyncDB } from '@services/sync.db';
// Import core modules. // Import core modules.
import { CoreEmulatorModule } from '@core/emulator/emulator.module'; import { CoreEmulatorModule } from '@core/emulator/emulator.module';
import { CoreLoginModule } from '@core/login/login.module'; import { CoreLoginModule } from '@core/login/login.module';
@ -121,6 +126,8 @@ export class AppModule {
// Set the injector. // Set the injector.
setSingletonsInjector(injector); setSingletonsInjector(injector);
this.initCoreServicesDB();
// Register a handler for platform ready. // Register a handler for platform ready.
CoreInit.instance.registerProcess({ CoreInit.instance.registerProcess({
name: 'CorePlatformReady', name: 'CorePlatformReady',
@ -154,4 +161,13 @@ export class AppModule {
CoreInit.instance.executeInitProcesses(); CoreInit.instance.executeInitProcesses();
} }
/**
* Init the DB of core services.
*/
protected initCoreServicesDB(): void {
initCoreFilepoolDB();
initCoreSitesDB();
initCoreSyncDB();
}
} }

View File

@ -36,7 +36,7 @@ import { CoreIonLoadingElement } from './ion-loading';
/** /**
* Class that represents a site (combination of site + user). * Class that represents a site (combination of site + user).
* It will have all the site data and provide utility functions regarding a site. * 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.registerSiteSchema. This will make sure that * To add tables to the site's database, please use registerSiteSchema exported in @services/sites.ts. This will make sure that
* the tables are created in all the sites, not just the current one. * the tables are created in all the sites, not just the current one.
* *
* @todo: Refactor this class to improve "temporary" sites support (not fully authenticated). * @todo: Refactor this class to improve "temporary" sites support (not fully authenticated).

View File

@ -0,0 +1,41 @@
// (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 { SQLiteDBTableSchema } from '@classes/sqlitedb';
/**
* Database variables for CoreApp service.
*/
export const DBNAME = 'MoodleMobile';
export const SCHEMA_VERSIONS_TABLE_NAME = 'schema_versions';
export const SCHEMA_VERSIONS_TABLE_SCHEMA: SQLiteDBTableSchema = {
name: SCHEMA_VERSIONS_TABLE_NAME,
columns: [
{
name: 'name',
type: 'TEXT',
primaryKey: true,
},
{
name: 'version',
type: 'INTEGER',
},
],
};
export type SchemaVersionsDBEntry = {
name: string;
version: number;
};

View File

@ -24,9 +24,7 @@ import { CoreConstants } from '@core/constants';
import { makeSingleton, Keyboard, Network, StatusBar, Platform } from '@singletons/core.singletons'; import { makeSingleton, Keyboard, Network, StatusBar, Platform } from '@singletons/core.singletons';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/app.db';
const DBNAME = 'MoodleMobile';
const SCHEMA_VERSIONS_TABLE = 'schema_versions';
/** /**
* Factory to provide some global functionalities, like access to the global app database. * Factory to provide some global functionalities, like access to the global app database.
@ -57,27 +55,13 @@ export class CoreAppProvider {
// Variables for DB. // Variables for DB.
protected createVersionsTableReady: Promise<void>; protected createVersionsTableReady: Promise<void>;
protected versionsTableSchema: SQLiteDBTableSchema = {
name: SCHEMA_VERSIONS_TABLE,
columns: [
{
name: 'name',
type: 'TEXT',
primaryKey: true,
},
{
name: 'version',
type: 'INTEGER',
},
],
};
constructor(appRef: ApplicationRef, zone: NgZone) { constructor(appRef: ApplicationRef, zone: NgZone) {
this.logger = CoreLogger.getInstance('CoreAppProvider'); this.logger = CoreLogger.getInstance('CoreAppProvider');
this.db = CoreDB.instance.getDB(DBNAME); this.db = CoreDB.instance.getDB(DBNAME);
// Create the schema versions table. // Create the schema versions table.
this.createVersionsTableReady = this.db.createTableFromSchema(this.versionsTableSchema); this.createVersionsTableReady = this.db.createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA);
Keyboard.instance.onKeyboardShow().subscribe((data) => { Keyboard.instance.onKeyboardShow().subscribe((data) => {
// Execute the callback in the Angular zone, so change detection doesn't stop working. // Execute the callback in the Angular zone, so change detection doesn't stop working.
@ -175,7 +159,7 @@ export class CoreAppProvider {
await this.createVersionsTableReady; await this.createVersionsTableReady;
// Fetch installed version of the schema. // Fetch installed version of the schema.
const entry = await this.db.getRecord<SchemaVersionsDBEntry>(SCHEMA_VERSIONS_TABLE, { name: schema.name }); const entry = await this.db.getRecord<SchemaVersionsDBEntry>(SCHEMA_VERSIONS_TABLE_NAME, { name: schema.name });
oldVersion = entry.version; oldVersion = entry.version;
} catch (error) { } catch (error) {
@ -198,7 +182,7 @@ export class CoreAppProvider {
} }
// Set installed version. // Set installed version.
await this.db.insertRecord(SCHEMA_VERSIONS_TABLE, { name: schema.name, version: schema.version }); await this.db.insertRecord(SCHEMA_VERSIONS_TABLE_NAME, { name: schema.name, version: schema.version });
} }
/** /**
@ -741,8 +725,3 @@ export type WindowForAutomatedTests = Window & {
appProvider?: CoreAppProvider; appProvider?: CoreAppProvider;
appRef?: ApplicationRef; appRef?: ApplicationRef;
}; };
type SchemaVersionsDBEntry = {
name: string;
version: number;
};

View File

@ -0,0 +1,47 @@
// (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 { CoreAppSchema } from '@services/app';
/**
* Database variables for for CoreConfig service.
*/
export const CONFIG_TABLE_NAME = 'core_config';
export const APP_SCHEMA: CoreAppSchema = {
name: 'CoreConfigProvider',
version: 1,
tables: [
{
name: CONFIG_TABLE_NAME,
columns: [
{
name: 'name',
type: 'TEXT',
unique: true,
notNull: true,
},
{
name: 'value',
},
],
},
],
};
export type ConfigDBEntry = {
name: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any;
};

View File

@ -14,11 +14,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreApp, CoreAppSchema } from '@services/app'; import { CoreApp } from '@services/app';
import { SQLiteDB } from '@classes/sqlitedb'; import { SQLiteDB } from '@classes/sqlitedb';
import { makeSingleton } from '@singletons/core.singletons'; import { makeSingleton } from '@singletons/core.singletons';
import { CONFIG_TABLE_NAME, APP_SCHEMA, ConfigDBEntry } from '@services/config.db';
const TABLE_NAME = 'core_config';
/** /**
* Factory to provide access to dynamic and permanent config and settings. * Factory to provide access to dynamic and permanent config and settings.
@ -28,32 +27,11 @@ const TABLE_NAME = 'core_config';
export class CoreConfigProvider { export class CoreConfigProvider {
protected appDB: SQLiteDB; protected appDB: SQLiteDB;
protected tableSchema: CoreAppSchema = {
name: 'CoreConfigProvider',
version: 1,
tables: [
{
name: TABLE_NAME,
columns: [
{
name: 'name',
type: 'TEXT',
unique: true,
notNull: true,
},
{
name: 'value',
},
],
},
],
};
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized. protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
constructor() { constructor() {
this.appDB = CoreApp.instance.getDB(); this.appDB = CoreApp.instance.getDB();
this.dbReady = CoreApp.instance.createTablesFromSchema(this.tableSchema).catch(() => { this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => {
// Ignore errors. // Ignore errors.
}); });
} }
@ -67,7 +45,7 @@ export class CoreConfigProvider {
async delete(name: string): Promise<void> { async delete(name: string): Promise<void> {
await this.dbReady; await this.dbReady;
await this.appDB.deleteRecords(TABLE_NAME, { name }); await this.appDB.deleteRecords(CONFIG_TABLE_NAME, { name });
} }
/** /**
@ -81,7 +59,7 @@ export class CoreConfigProvider {
await this.dbReady; await this.dbReady;
try { try {
const entry = await this.appDB.getRecord<ConfigDBEntry>(TABLE_NAME, { name }); const entry = await this.appDB.getRecord<ConfigDBEntry>(CONFIG_TABLE_NAME, { name });
return entry.value; return entry.value;
} catch (error) { } catch (error) {
@ -103,15 +81,9 @@ export class CoreConfigProvider {
async set(name: string, value: number | string): Promise<void> { async set(name: string, value: number | string): Promise<void> {
await this.dbReady; await this.dbReady;
await this.appDB.insertRecord(TABLE_NAME, { name, value }); await this.appDB.insertRecord(CONFIG_TABLE_NAME, { name, value });
} }
} }
export class CoreConfig extends makeSingleton(CoreConfigProvider) {} export class CoreConfig extends makeSingleton(CoreConfigProvider) {}
type ConfigDBEntry = {
name: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any;
};

View File

@ -0,0 +1,45 @@
// (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 { CoreAppSchema } from '@services/app';
/**
* Database variables for CoreCron service.
*/
export const CRON_TABLE_NAME = 'cron';
export const APP_SCHEMA: CoreAppSchema = {
name: 'CoreCronDelegate',
version: 1,
tables: [
{
name: CRON_TABLE_NAME,
columns: [
{
name: 'id',
type: 'TEXT',
primaryKey: true,
},
{
name: 'value',
type: 'INTEGER',
},
],
},
],
};
export type CronDBEntry = {
id: string;
value: number;
};

View File

@ -14,7 +14,7 @@
import { Injectable, NgZone } from '@angular/core'; import { Injectable, NgZone } from '@angular/core';
import { CoreApp, CoreAppProvider, CoreAppSchema } from '@services/app'; import { CoreApp, CoreAppProvider } from '@services/app';
import { CoreConfig } from '@services/config'; import { CoreConfig } from '@services/config';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@core/constants'; import { CoreConstants } from '@core/constants';
@ -23,8 +23,7 @@ import { CoreError } from '@classes/errors/error';
import { makeSingleton, Network } from '@singletons/core.singletons'; import { makeSingleton, Network } from '@singletons/core.singletons';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { APP_SCHEMA, CRON_TABLE_NAME, CronDBEntry } from '@services/cron.db';
const CRON_TABLE = 'cron';
/* /*
* Service to handle cron processes. The registered processes will be executed every certain time. * Service to handle cron processes. The registered processes will be executed every certain time.
@ -37,28 +36,6 @@ export class CoreCronDelegate {
static readonly MIN_INTERVAL = 300000; // Minimum interval is 5 minutes. static readonly MIN_INTERVAL = 300000; // Minimum interval is 5 minutes.
static readonly MAX_TIME_PROCESS = 120000; // Max time a process can block the queue. Defaults to 2 minutes. static readonly MAX_TIME_PROCESS = 120000; // Max time a process can block the queue. Defaults to 2 minutes.
// Variables for database.
protected tableSchema: CoreAppSchema = {
name: 'CoreCronDelegate',
version: 1,
tables: [
{
name: CRON_TABLE,
columns: [
{
name: 'id',
type: 'TEXT',
primaryKey: true,
},
{
name: 'value',
type: 'INTEGER',
},
],
},
],
};
protected logger: CoreLogger; protected logger: CoreLogger;
protected appDB: SQLiteDB; protected appDB: SQLiteDB;
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized. protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
@ -69,7 +46,7 @@ export class CoreCronDelegate {
this.logger = CoreLogger.getInstance('CoreCronDelegate'); this.logger = CoreLogger.getInstance('CoreCronDelegate');
this.appDB = CoreApp.instance.getDB(); this.appDB = CoreApp.instance.getDB();
this.dbReady = CoreApp.instance.createTablesFromSchema(this.tableSchema).catch(() => { this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => {
// Ignore errors. // Ignore errors.
}); });
@ -268,7 +245,7 @@ export class CoreCronDelegate {
const id = this.getHandlerLastExecutionId(name); const id = this.getHandlerLastExecutionId(name);
try { try {
const entry = await this.appDB.getRecord<CronDBEntry>(CRON_TABLE, { id }); const entry = await this.appDB.getRecord<CronDBEntry>(CRON_TABLE_NAME, { id });
const time = Number(entry.value); const time = Number(entry.value);
@ -431,7 +408,7 @@ export class CoreCronDelegate {
value: time, value: time,
}; };
await this.appDB.insertRecord(CRON_TABLE, entry); await this.appDB.insertRecord(CRON_TABLE_NAME, entry);
} }
/** /**
@ -562,8 +539,3 @@ export interface CoreCronHandler {
export type WindowForAutomatedTests = Window & { export type WindowForAutomatedTests = Window & {
cronProvider?: CoreCronDelegate; cronProvider?: CoreCronDelegate;
}; };
type CronDBEntry = {
id: string;
value: number;
};

View File

@ -0,0 +1,367 @@
// (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 { CoreAppSchema } from '@services/app';
import { CoreSiteSchema, registerSiteSchema } from '@services/sites';
/**
* Database variables for CoreFilepool service.
*/
export const QUEUE_TABLE_NAME = 'filepool_files_queue'; // Queue of files to download.
export const FILES_TABLE_NAME = 'filepool_files'; // Downloaded files.
export const LINKS_TABLE_NAME = 'filepool_files_links'; // Links between downloaded files and components.
export const PACKAGES_TABLE_NAME = 'filepool_packages'; // Downloaded packages (sets of files).
export const APP_SCHEMA: CoreAppSchema = {
name: 'CoreFilepoolProvider',
version: 1,
tables: [
{
name: QUEUE_TABLE_NAME,
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'],
},
],
};
export const SITE_SCHEMA: CoreSiteSchema = {
name: 'CoreFilepoolProvider',
version: 1,
tables: [
{
name: FILES_TABLE_NAME,
columns: [
{
name: 'fileId',
type: 'TEXT',
primaryKey: true,
},
{
name: 'url',
type: 'TEXT',
notNull: true,
},
{
name: 'revision',
type: 'INTEGER',
},
{
name: 'timemodified',
type: 'INTEGER',
},
{
name: 'stale',
type: 'INTEGER',
},
{
name: 'downloadTime',
type: 'INTEGER',
},
{
name: 'isexternalfile',
type: 'INTEGER',
},
{
name: 'repositorytype',
type: 'TEXT',
},
{
name: 'path',
type: 'TEXT',
},
{
name: 'extension',
type: 'TEXT',
},
],
},
{
name: LINKS_TABLE_NAME,
columns: [
{
name: 'fileId',
type: 'TEXT',
},
{
name: 'component',
type: 'TEXT',
},
{
name: 'componentId',
type: 'TEXT',
},
],
primaryKeys: ['fileId', 'component', 'componentId'],
},
{
name: PACKAGES_TABLE_NAME,
columns: [
{
name: 'id',
type: 'TEXT',
primaryKey: true,
},
{
name: 'component',
type: 'TEXT',
},
{
name: 'componentId',
type: 'TEXT',
},
{
name: 'status',
type: 'TEXT',
},
{
name: 'previous',
type: 'TEXT',
},
{
name: 'updated',
type: 'INTEGER',
},
{
name: 'downloadTime',
type: 'INTEGER',
},
{
name: 'previousDownloadTime',
type: 'INTEGER',
},
{
name: 'extra',
type: 'TEXT',
},
],
},
],
};
/**
* File options.
*/
export type CoreFilepoolFileOptions = {
revision?: number; // File's revision.
timemodified?: number; // File's timemodified.
isexternalfile?: number; // 1 if it's a external file (from an external repository), 0 otherwise.
repositorytype?: string; // Type of the repository this file belongs to.
};
/**
* Entry from filepool.
*/
export type CoreFilepoolFileEntry = CoreFilepoolFileOptions & {
/**
* The fileId to identify the file.
*/
fileId: string;
/**
* File's URL.
*/
url: string;
/**
* 1 if file is stale (needs to be updated), 0 otherwise.
*/
stale: number;
/**
* Timestamp when this file was downloaded.
*/
downloadTime: number;
/**
* File's path.
*/
path: string;
/**
* File's extension.
*/
extension: string;
};
/**
* DB data for entry from file's queue.
*/
export type CoreFilepoolQueueDBEntry = CoreFilepoolFileOptions & {
/**
* The site the file belongs to.
*/
siteId: string;
/**
* The fileId to identify the file.
*/
fileId: string;
/**
* Timestamp when the file was added to the queue.
*/
added: number;
/**
* The priority of the file.
*/
priority: number;
/**
* File's URL.
*/
url: string;
/**
* File's path.
*/
path?: string;
/**
* File links (to link the file to components and componentIds). Serialized to store on DB.
*/
links: string;
};
/**
* Entry from the file's queue.
*/
export type CoreFilepoolQueueEntry = CoreFilepoolQueueDBEntry & {
/**
* File links (to link the file to components and componentIds).
*/
linksUnserialized?: CoreFilepoolComponentLink[];
};
/**
* Entry from packages table.
*/
export type CoreFilepoolPackageEntry = {
/**
* Package id.
*/
id?: string;
/**
* The component to link the files to.
*/
component?: string;
/**
* An ID to use in conjunction with the component.
*/
componentId?: string | number;
/**
* Package status.
*/
status?: string;
/**
* Package previous status.
*/
previous?: string;
/**
* Timestamp when this package was updated.
*/
updated?: number;
/**
* Timestamp when this package was downloaded.
*/
downloadTime?: number;
/**
* Previous download time.
*/
previousDownloadTime?: number;
/**
* Extra data stored by the package.
*/
extra?: string;
};
/**
* A component link.
*/
export type CoreFilepoolComponentLink = {
/**
* Link's component.
*/
component: string;
/**
* Link's componentId.
*/
componentId?: string | number;
};
/**
* Links table record type.
*/
export type CoreFilepoolLinksRecord = {
fileId: string; // File Id.
component: string; // Component name.
componentId: number | string; // Component Id.
};
export const initCoreFilepoolDB = (): void => {
registerSiteSchema(SITE_SCHEMA);
};

View File

@ -15,12 +15,12 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Md5 } from 'ts-md5/dist/md5'; import { Md5 } from 'ts-md5/dist/md5';
import { CoreApp, CoreAppSchema } from '@services/app'; import { CoreApp } from '@services/app';
import { CoreEvents } from '@singletons/events'; import { CoreEvents } from '@singletons/events';
import { CoreFile } from '@services/file'; import { CoreFile } from '@services/file';
import { CoreInit } from '@services/init'; import { CoreInit } from '@services/init';
import { CorePluginFile } from '@services/plugin-file-delegate'; import { CorePluginFile } from '@services/plugin-file-delegate';
import { CoreSites, CoreSiteSchema } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreWS, CoreWSExternalFile } from '@services/ws'; import { CoreWS, CoreWSExternalFile } from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreMimetypeUtils } from '@services/utils/mimetype';
@ -33,6 +33,20 @@ import { CoreError } from '@classes/errors/error';
import { CoreConstants } from '@core/constants'; import { CoreConstants } from '@core/constants';
import { makeSingleton, Network, NgZone, Translate } from '@singletons/core.singletons'; import { makeSingleton, Network, NgZone, Translate } from '@singletons/core.singletons';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import {
APP_SCHEMA,
FILES_TABLE_NAME,
QUEUE_TABLE_NAME,
PACKAGES_TABLE_NAME,
LINKS_TABLE_NAME,
CoreFilepoolFileEntry,
CoreFilepoolComponentLink,
CoreFilepoolFileOptions,
CoreFilepoolLinksRecord,
CoreFilepoolPackageEntry,
CoreFilepoolQueueEntry,
CoreFilepoolQueueDBEntry,
} from '@services/filepool.db';
/* /*
* Factory for handling downloading files and retrieve downloaded files. * Factory for handling downloading files and retrieve downloaded files.
@ -60,182 +74,6 @@ export class CoreFilepoolProvider {
protected static readonly FILE_UPDATE_UNKNOWN_WHERE_CLAUSE = protected static readonly FILE_UPDATE_UNKNOWN_WHERE_CLAUSE =
'isexternalfile = 1 OR ((revision IS NULL OR revision = 0) AND (timemodified IS NULL OR timemodified = 0))'; 'isexternalfile = 1 OR ((revision IS NULL OR revision = 0) AND (timemodified IS NULL OR timemodified = 0))';
// Variables for database.
protected static readonly QUEUE_TABLE = 'filepool_files_queue'; // Queue of files to download.
protected static readonly FILES_TABLE = 'filepool_files'; // Downloaded files.
protected static readonly LINKS_TABLE = 'filepool_files_links'; // Links between downloaded files and components.
protected static readonly PACKAGES_TABLE = 'filepool_packages'; // Downloaded packages (sets of files).
protected appTablesSchema: CoreAppSchema = {
name: 'CoreFilepoolProvider',
version: 1,
tables: [
{
name: CoreFilepoolProvider.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,
tables: [
{
name: CoreFilepoolProvider.FILES_TABLE,
columns: [
{
name: 'fileId',
type: 'TEXT',
primaryKey: true,
},
{
name: 'url',
type: 'TEXT',
notNull: true,
},
{
name: 'revision',
type: 'INTEGER',
},
{
name: 'timemodified',
type: 'INTEGER',
},
{
name: 'stale',
type: 'INTEGER',
},
{
name: 'downloadTime',
type: 'INTEGER',
},
{
name: 'isexternalfile',
type: 'INTEGER',
},
{
name: 'repositorytype',
type: 'TEXT',
},
{
name: 'path',
type: 'TEXT',
},
{
name: 'extension',
type: 'TEXT',
},
],
},
{
name: CoreFilepoolProvider.LINKS_TABLE,
columns: [
{
name: 'fileId',
type: 'TEXT',
},
{
name: 'component',
type: 'TEXT',
},
{
name: 'componentId',
type: 'TEXT',
},
],
primaryKeys: ['fileId', 'component', 'componentId'],
},
{
name: CoreFilepoolProvider.PACKAGES_TABLE,
columns: [
{
name: 'id',
type: 'TEXT',
primaryKey: true,
},
{
name: 'component',
type: 'TEXT',
},
{
name: 'componentId',
type: 'TEXT',
},
{
name: 'status',
type: 'TEXT',
},
{
name: 'previous',
type: 'TEXT',
},
{
name: 'updated',
type: 'INTEGER',
},
{
name: 'downloadTime',
type: 'INTEGER',
},
{
name: 'previousDownloadTime',
type: 'INTEGER',
},
{
name: 'extra',
type: 'TEXT',
},
],
},
],
};
protected logger: CoreLogger; protected logger: CoreLogger;
protected appDB: SQLiteDB; protected appDB: SQLiteDB;
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized. protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
@ -258,12 +96,10 @@ export class CoreFilepoolProvider {
this.logger = CoreLogger.getInstance('CoreFilepoolProvider'); this.logger = CoreLogger.getInstance('CoreFilepoolProvider');
this.appDB = CoreApp.instance.getDB(); this.appDB = CoreApp.instance.getDB();
this.dbReady = CoreApp.instance.createTablesFromSchema(this.appTablesSchema).catch(() => { this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => {
// Ignore errors. // Ignore errors.
}); });
CoreSites.instance.registerSiteSchema(this.siteSchema);
this.init(); this.init();
} }
@ -308,7 +144,7 @@ export class CoreFilepoolProvider {
componentId: componentId || '', componentId: componentId || '',
}; };
await db.insertRecord(CoreFilepoolProvider.LINKS_TABLE, newEntry); await db.insertRecord(LINKS_TABLE_NAME, newEntry);
} }
/** /**
@ -373,7 +209,7 @@ export class CoreFilepoolProvider {
const db = await CoreSites.instance.getSiteDb(siteId); const db = await CoreSites.instance.getSiteDb(siteId);
await db.insertRecord(CoreFilepoolProvider.FILES_TABLE, record); await db.insertRecord(FILES_TABLE_NAME, record);
} }
/** /**
@ -433,7 +269,7 @@ export class CoreFilepoolProvider {
this.logger.debug(`Adding ${fileId} to the queue`); this.logger.debug(`Adding ${fileId} to the queue`);
await this.appDB.insertRecord(CoreFilepoolProvider.QUEUE_TABLE, { await this.appDB.insertRecord(QUEUE_TABLE_NAME, {
siteId, siteId,
fileId, fileId,
url, url,
@ -563,7 +399,7 @@ export class CoreFilepoolProvider {
// Update only when required. // Update only when required.
this.logger.debug(`Updating file ${fileId} which is already in queue`); this.logger.debug(`Updating file ${fileId} which is already in queue`);
return this.appDB.updateRecords(CoreFilepoolProvider.QUEUE_TABLE, newData, primaryKey).then(() => return this.appDB.updateRecords(QUEUE_TABLE_NAME, newData, primaryKey).then(() =>
this.getQueuePromise(siteId, fileId, true, onProgress)); this.getQueuePromise(siteId, fileId, true, onProgress));
} }
@ -692,9 +528,9 @@ export class CoreFilepoolProvider {
const site = await CoreSites.instance.getSite(siteId); const site = await CoreSites.instance.getSite(siteId);
// Get all the packages to be able to "notify" the change in the status. // Get all the packages to be able to "notify" the change in the status.
const entries: CoreFilepoolPackageEntry[] = await site.getDb().getAllRecords(CoreFilepoolProvider.PACKAGES_TABLE); const entries: CoreFilepoolPackageEntry[] = await site.getDb().getAllRecords(PACKAGES_TABLE_NAME);
// Delete all the entries. // Delete all the entries.
await site.getDb().deleteRecords(CoreFilepoolProvider.PACKAGES_TABLE); await site.getDb().deleteRecords(PACKAGES_TABLE_NAME);
entries.forEach((entry) => { entries.forEach((entry) => {
// Trigger module status changed, setting it as not downloaded. // Trigger module status changed, setting it as not downloaded.
@ -712,8 +548,8 @@ export class CoreFilepoolProvider {
const db = await CoreSites.instance.getSiteDb(siteId); const db = await CoreSites.instance.getSiteDb(siteId);
await Promise.all([ await Promise.all([
db.deleteRecords(CoreFilepoolProvider.FILES_TABLE), db.deleteRecords(FILES_TABLE_NAME),
db.deleteRecords(CoreFilepoolProvider.LINKS_TABLE), db.deleteRecords(LINKS_TABLE_NAME),
]); ]);
} }
@ -732,7 +568,7 @@ export class CoreFilepoolProvider {
componentId: this.fixComponentId(componentId), componentId: this.fixComponentId(componentId),
}; };
const count = await db.countRecords(CoreFilepoolProvider.LINKS_TABLE, conditions); const count = await db.countRecords(LINKS_TABLE_NAME, conditions);
if (count <= 0) { if (count <= 0) {
throw new CoreError('Component doesn\'t have files'); throw new CoreError('Component doesn\'t have files');
} }
@ -1257,7 +1093,7 @@ export class CoreFilepoolProvider {
// Minor problem: file will remain in the filesystem once downloaded again. // Minor problem: file will remain in the filesystem once downloaded again.
this.logger.debug('Staled file with no extension ' + entry.fileId); this.logger.debug('Staled file with no extension ' + entry.fileId);
await db.updateRecords(CoreFilepoolProvider.FILES_TABLE, { stale: 1 }, { fileId: entry.fileId }); await db.updateRecords(FILES_TABLE_NAME, { stale: 1 }, { fileId: entry.fileId });
return; return;
} }
@ -1267,7 +1103,7 @@ export class CoreFilepoolProvider {
entry.fileId = CoreMimetypeUtils.instance.removeExtension(fileId); entry.fileId = CoreMimetypeUtils.instance.removeExtension(fileId);
entry.extension = extension; entry.extension = extension;
await db.updateRecords(CoreFilepoolProvider.FILES_TABLE, entry, { fileId }); await db.updateRecords(FILES_TABLE_NAME, entry, { fileId });
if (entry.fileId == fileId) { if (entry.fileId == fileId) {
// File ID hasn't changed, we're done. // File ID hasn't changed, we're done.
this.logger.debug('Removed extesion ' + extension + ' from file ' + entry.fileId); this.logger.debug('Removed extesion ' + extension + ' from file ' + entry.fileId);
@ -1276,7 +1112,7 @@ export class CoreFilepoolProvider {
} }
// Now update the links. // Now update the links.
await db.updateRecords(CoreFilepoolProvider.LINKS_TABLE, { fileId: entry.fileId }, { fileId }); await db.updateRecords(LINKS_TABLE_NAME, { fileId: entry.fileId }, { fileId });
} }
/** /**
@ -1339,7 +1175,7 @@ export class CoreFilepoolProvider {
componentId: this.fixComponentId(componentId), componentId: this.fixComponentId(componentId),
}; };
const items = await db.getRecords<CoreFilepoolLinksRecord>(CoreFilepoolProvider.LINKS_TABLE, conditions); const items = await db.getRecords<CoreFilepoolLinksRecord>(LINKS_TABLE_NAME, conditions);
items.forEach((item) => { items.forEach((item) => {
item.componentId = this.fixComponentId(item.componentId); item.componentId = this.fixComponentId(item.componentId);
}); });
@ -1449,7 +1285,7 @@ export class CoreFilepoolProvider {
*/ */
protected async getFileLinks(siteId: string, fileId: string): Promise<CoreFilepoolLinksRecord[]> { protected async getFileLinks(siteId: string, fileId: string): Promise<CoreFilepoolLinksRecord[]> {
const db = await CoreSites.instance.getSiteDb(siteId); const db = await CoreSites.instance.getSiteDb(siteId);
const items = await db.getRecords<CoreFilepoolLinksRecord>(CoreFilepoolProvider.LINKS_TABLE, { fileId }); const items = await db.getRecords<CoreFilepoolLinksRecord>(LINKS_TABLE_NAME, { fileId });
items.forEach((item) => { items.forEach((item) => {
item.componentId = this.fixComponentId(item.componentId); item.componentId = this.fixComponentId(item.componentId);
@ -1527,7 +1363,7 @@ export class CoreFilepoolProvider {
await Promise.all(items.map(async (item) => { await Promise.all(items.map(async (item) => {
try { try {
const fileEntry = await db.getRecord<CoreFilepoolFileEntry>( const fileEntry = await db.getRecord<CoreFilepoolFileEntry>(
CoreFilepoolProvider.FILES_TABLE, FILES_TABLE_NAME,
{ fileId: item.fileId }, { fileId: item.fileId },
); );
@ -1808,7 +1644,7 @@ export class CoreFilepoolProvider {
const site = await CoreSites.instance.getSite(siteId); const site = await CoreSites.instance.getSite(siteId);
const packageId = this.getPackageId(component, componentId); const packageId = this.getPackageId(component, componentId);
return site.getDb().getRecord(CoreFilepoolProvider.PACKAGES_TABLE, { id: packageId }); return site.getDb().getRecord(PACKAGES_TABLE_NAME, { id: packageId });
} }
/** /**
@ -2258,7 +2094,7 @@ export class CoreFilepoolProvider {
*/ */
protected async hasFileInPool(siteId: string, fileId: string): Promise<CoreFilepoolFileEntry> { protected async hasFileInPool(siteId: string, fileId: string): Promise<CoreFilepoolFileEntry> {
const db = await CoreSites.instance.getSiteDb(siteId); const db = await CoreSites.instance.getSiteDb(siteId);
const entry = await db.getRecord<CoreFilepoolFileEntry>(CoreFilepoolProvider.FILES_TABLE, { fileId }); const entry = await db.getRecord<CoreFilepoolFileEntry>(FILES_TABLE_NAME, { fileId });
if (typeof entry === 'undefined') { if (typeof entry === 'undefined') {
throw new CoreError('File not found in filepool.'); throw new CoreError('File not found in filepool.');
@ -2277,7 +2113,7 @@ export class CoreFilepoolProvider {
protected async hasFileInQueue(siteId: string, fileId: string): Promise<CoreFilepoolQueueEntry> { protected async hasFileInQueue(siteId: string, fileId: string): Promise<CoreFilepoolQueueEntry> {
await this.dbReady; await this.dbReady;
const entry = await this.appDB.getRecord<CoreFilepoolQueueEntry>(CoreFilepoolProvider.QUEUE_TABLE, { siteId, fileId }); const entry = await this.appDB.getRecord<CoreFilepoolQueueEntry>(QUEUE_TABLE_NAME, { siteId, fileId });
if (typeof entry === 'undefined') { if (typeof entry === 'undefined') {
throw new CoreError('File not found in queue.'); throw new CoreError('File not found in queue.');
@ -2301,7 +2137,7 @@ export class CoreFilepoolProvider {
const where = onlyUnknown ? CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE : undefined; const where = onlyUnknown ? CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE : undefined;
await db.updateRecordsWhere(CoreFilepoolProvider.FILES_TABLE, { stale: 1 }, where); await db.updateRecordsWhere(FILES_TABLE_NAME, { stale: 1 }, where);
} }
/** /**
@ -2322,7 +2158,7 @@ export class CoreFilepoolProvider {
const db = await CoreSites.instance.getSiteDb(siteId); const db = await CoreSites.instance.getSiteDb(siteId);
await db.updateRecords(CoreFilepoolProvider.FILES_TABLE, { stale: 1 }, { fileId }); await db.updateRecords(FILES_TABLE_NAME, { stale: 1 }, { fileId });
} }
/** /**
@ -2359,7 +2195,7 @@ export class CoreFilepoolProvider {
whereAndParams[0] += ' AND (' + CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE + ')'; whereAndParams[0] += ' AND (' + CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE + ')';
} }
await db.updateRecordsWhere(CoreFilepoolProvider.FILES_TABLE, { stale: 1 }, whereAndParams[0], whereAndParams[1]); await db.updateRecordsWhere(FILES_TABLE_NAME, { stale: 1 }, whereAndParams[0], whereAndParams[1]);
} }
/** /**
@ -2615,7 +2451,7 @@ export class CoreFilepoolProvider {
try { try {
items = await this.appDB.getRecords<CoreFilepoolQueueEntry>( items = await this.appDB.getRecords<CoreFilepoolQueueEntry>(
CoreFilepoolProvider.QUEUE_TABLE, QUEUE_TABLE_NAME,
undefined, undefined,
'priority DESC, added ASC', 'priority DESC, added ASC',
undefined, undefined,
@ -2760,7 +2596,7 @@ export class CoreFilepoolProvider {
protected async removeFromQueue(siteId: string, fileId: string): Promise<void> { protected async removeFromQueue(siteId: string, fileId: string): Promise<void> {
await this.dbReady; await this.dbReady;
await this.appDB.deleteRecords(CoreFilepoolProvider.QUEUE_TABLE, { siteId, fileId }); await this.appDB.deleteRecords(QUEUE_TABLE_NAME, { siteId, fileId });
} }
/** /**
@ -2797,10 +2633,10 @@ export class CoreFilepoolProvider {
const promises: Promise<unknown>[] = []; const promises: Promise<unknown>[] = [];
// Remove entry from filepool store. // Remove entry from filepool store.
promises.push(db.deleteRecords(CoreFilepoolProvider.FILES_TABLE, conditions)); promises.push(db.deleteRecords(FILES_TABLE_NAME, conditions));
// Remove links. // Remove links.
promises.push(db.deleteRecords(CoreFilepoolProvider.LINKS_TABLE, conditions)); promises.push(db.deleteRecords(LINKS_TABLE_NAME, conditions));
// Remove the file. // Remove the file.
if (CoreFile.instance.isAvailable()) { if (CoreFile.instance.isAvailable()) {
@ -2885,7 +2721,7 @@ export class CoreFilepoolProvider {
const packageId = this.getPackageId(component, componentId); const packageId = this.getPackageId(component, componentId);
// Get current stored data, we'll only update 'status' and 'updated' fields. // Get current stored data, we'll only update 'status' and 'updated' fields.
const entry = <CoreFilepoolPackageEntry> site.getDb().getRecord(CoreFilepoolProvider.PACKAGES_TABLE, { id: packageId }); const entry = <CoreFilepoolPackageEntry> site.getDb().getRecord(PACKAGES_TABLE_NAME, { id: packageId });
const newData: CoreFilepoolPackageEntry = {}; const newData: CoreFilepoolPackageEntry = {};
if (entry.status == CoreConstants.DOWNLOADING) { if (entry.status == CoreConstants.DOWNLOADING) {
// Going back from downloading to previous status, restore previous download time. // Going back from downloading to previous status, restore previous download time.
@ -2895,7 +2731,7 @@ export class CoreFilepoolProvider {
newData.updated = Date.now(); newData.updated = Date.now();
this.logger.debug(`Set previous status '${entry.status}' for package ${component} ${componentId}`); this.logger.debug(`Set previous status '${entry.status}' for package ${component} ${componentId}`);
await site.getDb().updateRecords(CoreFilepoolProvider.PACKAGES_TABLE, newData, { id: packageId }); await site.getDb().updateRecords(PACKAGES_TABLE_NAME, newData, { id: packageId });
// Success updating, trigger event. // Success updating, trigger event.
this.triggerPackageStatusChanged(site.id!, newData.status, component, componentId); this.triggerPackageStatusChanged(site.id!, newData.status, component, componentId);
@ -2973,7 +2809,7 @@ export class CoreFilepoolProvider {
let previousStatus: string | undefined; let previousStatus: string | undefined;
// Search current status to set it as previous status. // Search current status to set it as previous status.
try { try {
const entry = <CoreFilepoolPackageEntry> site.getDb().getRecord(CoreFilepoolProvider.PACKAGES_TABLE, { id: packageId }); const entry = <CoreFilepoolPackageEntry> site.getDb().getRecord(PACKAGES_TABLE_NAME, { id: packageId });
if (typeof extra == 'undefined' || extra === null) { if (typeof extra == 'undefined' || extra === null) {
extra = entry.extra; extra = entry.extra;
} }
@ -3008,7 +2844,7 @@ export class CoreFilepoolProvider {
return; return;
} }
await site.getDb().insertRecord(CoreFilepoolProvider.PACKAGES_TABLE, packageEntry); await site.getDb().insertRecord(PACKAGES_TABLE_NAME, packageEntry);
// Success inserting, trigger event. // Success inserting, trigger event.
this.triggerPackageStatusChanged(siteId, status, component, componentId); this.triggerPackageStatusChanged(siteId, status, component, componentId);
@ -3132,7 +2968,7 @@ export class CoreFilepoolProvider {
const packageId = this.getPackageId(component, componentId); const packageId = this.getPackageId(component, componentId);
await site.getDb().updateRecords( await site.getDb().updateRecords(
CoreFilepoolProvider.PACKAGES_TABLE, PACKAGES_TABLE_NAME,
{ downloadTime: CoreTimeUtils.instance.timestamp() }, { downloadTime: CoreTimeUtils.instance.timestamp() },
{ id: packageId }, { id: packageId },
); );
@ -3142,166 +2978,6 @@ export class CoreFilepoolProvider {
export class CoreFilepool extends makeSingleton(CoreFilepoolProvider) {} export class CoreFilepool extends makeSingleton(CoreFilepoolProvider) {}
/**
* File options.
*/
type CoreFilepoolFileOptions = {
revision?: number; // File's revision.
timemodified?: number; // File's timemodified.
isexternalfile?: number; // 1 if it's a external file (from an external repository), 0 otherwise.
repositorytype?: string; // Type of the repository this file belongs to.
};
/**
* Entry from filepool.
*/
export type CoreFilepoolFileEntry = CoreFilepoolFileOptions & {
/**
* The fileId to identify the file.
*/
fileId: string;
/**
* File's URL.
*/
url: string;
/**
* 1 if file is stale (needs to be updated), 0 otherwise.
*/
stale: number;
/**
* Timestamp when this file was downloaded.
*/
downloadTime: number;
/**
* File's path.
*/
path: string;
/**
* File's extension.
*/
extension: string;
};
/**
* DB data for entry from file's queue.
*/
export type CoreFilepoolQueueDBEntry = CoreFilepoolFileOptions & {
/**
* The site the file belongs to.
*/
siteId: string;
/**
* The fileId to identify the file.
*/
fileId: string;
/**
* Timestamp when the file was added to the queue.
*/
added: number;
/**
* The priority of the file.
*/
priority: number;
/**
* File's URL.
*/
url: string;
/**
* File's path.
*/
path?: string;
/**
* File links (to link the file to components and componentIds). Serialized to store on DB.
*/
links: string;
};
/**
* Entry from the file's queue.
*/
export type CoreFilepoolQueueEntry = CoreFilepoolQueueDBEntry & {
/**
* File links (to link the file to components and componentIds).
*/
linksUnserialized?: CoreFilepoolComponentLink[];
};
/**
* Entry from packages table.
*/
export type CoreFilepoolPackageEntry = {
/**
* Package id.
*/
id?: string;
/**
* The component to link the files to.
*/
component?: string;
/**
* An ID to use in conjunction with the component.
*/
componentId?: string | number;
/**
* Package status.
*/
status?: string;
/**
* Package previous status.
*/
previous?: string;
/**
* Timestamp when this package was updated.
*/
updated?: number;
/**
* Timestamp when this package was downloaded.
*/
downloadTime?: number;
/**
* Previous download time.
*/
previousDownloadTime?: number;
/**
* Extra data stored by the package.
*/
extra?: string;
};
/**
* A component link.
*/
export type CoreFilepoolComponentLink = {
/**
* Link's component.
*/
component: string;
/**
* Link's componentId.
*/
componentId?: string | number;
};
/** /**
* File actions. * File actions.
*/ */
@ -3359,14 +3035,5 @@ type CoreFilepoolPromiseDefer = PromiseDefer<void> & {
onProgress?: CoreFilepoolOnProgressCallback; // On Progress function. onProgress?: CoreFilepoolOnProgressCallback; // On Progress function.
}; };
/**
* Links table record type.
*/
type CoreFilepoolLinksRecord = {
fileId: string; // File Id.
component: string; // Component name.
componentId: number | string; // Component Id.
};
type AnchorOrMediaElement = type AnchorOrMediaElement =
HTMLAnchorElement | HTMLImageElement | HTMLAudioElement | HTMLVideoElement | HTMLSourceElement | HTMLTrackElement; HTMLAnchorElement | HTMLImageElement | HTMLAudioElement | HTMLVideoElement | HTMLSourceElement | HTMLTrackElement;

View File

@ -0,0 +1,80 @@
// (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 { CoreAppSchema } from '@services/app';
import { PromiseDefer } from '@services/utils/utils';
/**
* Database variables for CoreLocalNotifications service.
*/
export const SITES_TABLE_NAME = 'notification_sites'; // Store to asigne unique codes to each site.
export const COMPONENTS_TABLE_NAME = 'notification_components'; // Store to asigne unique codes to each component.
export const TRIGGERED_TABLE_NAME = 'notifications_triggered'; // Store to prevent re-triggering notifications.
export const APP_SCHEMA: CoreAppSchema = {
name: 'CoreLocalNotificationsProvider',
version: 1,
tables: [
{
name: SITES_TABLE_NAME,
columns: [
{
name: 'id',
type: 'TEXT',
primaryKey: true,
},
{
name: 'code',
type: 'INTEGER',
notNull: true,
},
],
},
{
name: COMPONENTS_TABLE_NAME,
columns: [
{
name: 'id',
type: 'TEXT',
primaryKey: true,
},
{
name: 'code',
type: 'INTEGER',
notNull: true,
},
],
},
{
name: TRIGGERED_TABLE_NAME,
columns: [
{
name: 'id',
type: 'INTEGER',
primaryKey: true,
},
{
name: 'at',
type: 'INTEGER',
notNull: true,
},
],
},
],
};
export type CodeRequestsQueueItem = {
table: string;
id: string;
deferreds: PromiseDefer<number>[];
};

View File

@ -16,11 +16,11 @@ import { Injectable } from '@angular/core';
import { Subject, Subscription } from 'rxjs'; import { Subject, Subscription } from 'rxjs';
import { ILocalNotification } from '@ionic-native/local-notifications'; import { ILocalNotification } from '@ionic-native/local-notifications';
import { CoreApp, CoreAppSchema } from '@services/app'; import { CoreApp } from '@services/app';
import { CoreConfig } from '@services/config'; import { CoreConfig } from '@services/config';
import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils, PromiseDefer } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { SQLiteDB } from '@classes/sqlitedb'; import { SQLiteDB } from '@classes/sqlitedb';
import { CoreSite } from '@classes/site'; import { CoreSite } from '@classes/site';
import { CoreQueueRunner } from '@classes/queue-runner'; import { CoreQueueRunner } from '@classes/queue-runner';
@ -28,6 +28,13 @@ import { CoreError } from '@classes/errors/error';
import { CoreConstants } from '@core/constants'; import { CoreConstants } from '@core/constants';
import { makeSingleton, NgZone, Platform, Translate, LocalNotifications, Push, Device } from '@singletons/core.singletons'; import { makeSingleton, NgZone, Platform, Translate, LocalNotifications, Push, Device } from '@singletons/core.singletons';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import {
APP_SCHEMA,
TRIGGERED_TABLE_NAME,
COMPONENTS_TABLE_NAME,
SITES_TABLE_NAME,
CodeRequestsQueueItem,
} from '@services/local-notifications.db';
/** /**
* Service to handle local notifications. * Service to handle local notifications.
@ -35,62 +42,6 @@ import { CoreLogger } from '@singletons/logger';
@Injectable() @Injectable()
export class CoreLocalNotificationsProvider { export class CoreLocalNotificationsProvider {
// Variables for the database.
protected static readonly SITES_TABLE = 'notification_sites'; // Store to asigne unique codes to each site.
protected static readonly COMPONENTS_TABLE = 'notification_components'; // Store to asigne unique codes to each component.
protected static readonly TRIGGERED_TABLE = 'notifications_triggered'; // Store to prevent re-triggering notifications.
protected tablesSchema: CoreAppSchema = {
name: 'CoreLocalNotificationsProvider',
version: 1,
tables: [
{
name: CoreLocalNotificationsProvider.SITES_TABLE,
columns: [
{
name: 'id',
type: 'TEXT',
primaryKey: true,
},
{
name: 'code',
type: 'INTEGER',
notNull: true,
},
],
},
{
name: CoreLocalNotificationsProvider.COMPONENTS_TABLE,
columns: [
{
name: 'id',
type: 'TEXT',
primaryKey: true,
},
{
name: 'code',
type: 'INTEGER',
notNull: true,
},
],
},
{
name: CoreLocalNotificationsProvider.TRIGGERED_TABLE,
columns: [
{
name: 'id',
type: 'INTEGER',
primaryKey: true,
},
{
name: 'at',
type: 'INTEGER',
notNull: true,
},
],
},
],
};
protected logger: CoreLogger; protected logger: CoreLogger;
protected appDB: SQLiteDB; protected appDB: SQLiteDB;
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized. protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
@ -111,7 +62,7 @@ export class CoreLocalNotificationsProvider {
this.logger = CoreLogger.getInstance('CoreLocalNotificationsProvider'); this.logger = CoreLogger.getInstance('CoreLocalNotificationsProvider');
this.queueRunner = new CoreQueueRunner(10); this.queueRunner = new CoreQueueRunner(10);
this.appDB = CoreApp.instance.getDB(); this.appDB = CoreApp.instance.getDB();
this.dbReady = CoreApp.instance.createTablesFromSchema(this.tablesSchema).catch(() => { this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => {
// Ignore errors. // Ignore errors.
}); });
@ -301,7 +252,7 @@ export class CoreLocalNotificationsProvider {
* @return Promise resolved when the component code is retrieved. * @return Promise resolved when the component code is retrieved.
*/ */
protected getComponentCode(component: string): Promise<number> { protected getComponentCode(component: string): Promise<number> {
return this.requestCode(CoreLocalNotificationsProvider.COMPONENTS_TABLE, component); return this.requestCode(COMPONENTS_TABLE_NAME, component);
} }
/** /**
@ -312,7 +263,7 @@ export class CoreLocalNotificationsProvider {
* @return Promise resolved when the site code is retrieved. * @return Promise resolved when the site code is retrieved.
*/ */
protected getSiteCode(siteId: string): Promise<number> { protected getSiteCode(siteId: string): Promise<number> {
return this.requestCode(CoreLocalNotificationsProvider.SITES_TABLE, siteId); return this.requestCode(SITES_TABLE_NAME, siteId);
} }
/** /**
@ -377,7 +328,7 @@ export class CoreLocalNotificationsProvider {
try { try {
const stored = await this.appDB.getRecord<{ id: number; at: number }>( const stored = await this.appDB.getRecord<{ id: number; at: number }>(
CoreLocalNotificationsProvider.TRIGGERED_TABLE, TRIGGERED_TABLE_NAME,
{ id: notification.id }, { id: notification.id },
); );
@ -532,7 +483,7 @@ export class CoreLocalNotificationsProvider {
async removeTriggered(id: number): Promise<void> { async removeTriggered(id: number): Promise<void> {
await this.dbReady; await this.dbReady;
await this.appDB.deleteRecords(CoreLocalNotificationsProvider.TRIGGERED_TABLE, { id: id }); await this.appDB.deleteRecords(TRIGGERED_TABLE_NAME, { id: id });
} }
/** /**
@ -695,7 +646,7 @@ export class CoreLocalNotificationsProvider {
at: notification.trigger && notification.trigger.at ? notification.trigger.at.getTime() : Date.now(), at: notification.trigger && notification.trigger.at ? notification.trigger.at.getTime() : Date.now(),
}; };
return this.appDB.insertRecord(CoreLocalNotificationsProvider.TRIGGERED_TABLE, entry); return this.appDB.insertRecord(TRIGGERED_TABLE_NAME, entry);
} }
/** /**
@ -708,10 +659,10 @@ export class CoreLocalNotificationsProvider {
async updateComponentName(oldName: string, newName: string): Promise<void> { async updateComponentName(oldName: string, newName: string): Promise<void> {
await this.dbReady; await this.dbReady;
const oldId = CoreLocalNotificationsProvider.COMPONENTS_TABLE + '#' + oldName; const oldId = COMPONENTS_TABLE_NAME + '#' + oldName;
const newId = CoreLocalNotificationsProvider.COMPONENTS_TABLE + '#' + newName; const newId = COMPONENTS_TABLE_NAME + '#' + newName;
await this.appDB.updateRecords(CoreLocalNotificationsProvider.COMPONENTS_TABLE, { id: newId }, { id: oldId }); await this.appDB.updateRecords(COMPONENTS_TABLE_NAME, { id: newId }, { id: oldId });
} }
} }
@ -719,9 +670,3 @@ export class CoreLocalNotificationsProvider {
export class CoreLocalNotifications extends makeSingleton(CoreLocalNotificationsProvider) {} export class CoreLocalNotifications extends makeSingleton(CoreLocalNotificationsProvider) {}
export type CoreLocalNotificationsClickCallback<T = unknown> = (value: T) => void; export type CoreLocalNotificationsClickCallback<T = unknown> = (value: T) => void;
type CodeRequestsQueueItem = {
table: string;
id: string;
deferreds: PromiseDefer<number>[];
};

View File

@ -0,0 +1,233 @@
// (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 { CoreAppSchema } from '@services/app';
import { CoreSiteSchema, registerSiteSchema } from '@services/sites';
import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
import { CoreSite } from '@classes/site';
/**
* Database variables for CoreSites service.
*/
export const SITES_TABLE_NAME = 'sites_2';
export const CURRENT_SITE_TABLE_NAME = 'current_site';
export const SCHEMA_VERSIONS_TABLE_NAME = 'schema_versions';
// Schema to register in App DB.
export const APP_SCHEMA: CoreAppSchema = {
name: 'CoreSitesProvider',
version: 2,
tables: [
{
name: SITES_TABLE_NAME,
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: CURRENT_SITE_TABLE_NAME,
columns: [
{
name: 'id',
type: 'INTEGER',
primaryKey: true,
},
{
name: 'siteId',
type: 'TEXT',
notNull: true,
unique: true,
},
],
},
],
async migrate(db: SQLiteDB, oldVersion: number): Promise<void> {
if (oldVersion < 2) {
const newTable = SITES_TABLE_NAME;
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<SiteDBEntry>(oldTable);
const promises: Promise<number>[] = [];
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.
}
}
},
};
// Schema to register for Site DB.
export const SITE_SCHEMA: CoreSiteSchema = {
name: 'CoreSitesProvider',
version: 2,
canBeCleared: [CoreSite.WS_CACHE_TABLE],
tables: [
{
name: CoreSite.WS_CACHE_TABLE,
columns: [
{
name: 'id',
type: 'TEXT',
primaryKey: true,
},
{
name: 'data',
type: 'TEXT',
},
{
name: 'key',
type: 'TEXT',
},
{
name: 'expirationTime',
type: 'INTEGER',
},
{
name: 'component',
type: 'TEXT',
},
{
name: 'componentId',
type: 'INTEGER',
},
],
},
{
name: CoreSite.CONFIG_TABLE,
columns: [
{
name: 'name',
type: 'TEXT',
unique: true,
notNull: true,
},
{
name: 'value',
},
],
},
],
async migrate(db: SQLiteDB, oldVersion: number): Promise<void> {
if (oldVersion && oldVersion < 2) {
const newTable = CoreSite.WS_CACHE_TABLE;
const oldTable = 'wscache';
try {
await db.tableExists(oldTable);
} catch (error) {
// Old table does not exist, ignore.
return;
}
// Cannot use insertRecordsFrom because there are extra fields, so manually code INSERT INTO.
await db.execute(
'INSERT INTO ' + newTable + ' ' +
'SELECT id, data, key, expirationTime, NULL as component, NULL as componentId ' +
'FROM ' + oldTable,
);
try {
await db.dropTable(oldTable);
} catch (error) {
// Error deleting old table, ignore.
}
}
},
};
// Table for site DB to include the schema versions. It's not part of SITE_SCHEMA because it needs to be created first.
export const SCHEMA_VERSIONS_TABLE_SCHEMA: SQLiteDBTableSchema = {
name: SCHEMA_VERSIONS_TABLE_NAME,
columns: [
{
name: 'name',
type: 'TEXT',
primaryKey: true,
},
{
name: 'version',
type: 'INTEGER',
},
],
};
export type SiteDBEntry = {
id: string;
siteUrl: string;
token: string;
info: string;
privateToken: string;
config: string;
loggedOut: number;
oauthId: number;
};
export type CurrentSiteDBEntry = {
id: number;
siteId: string;
};
export type SchemaVersionsDBEntry = {
name: string;
version: number;
};
export const initCoreSitesDB = (): void => {
registerSiteSchema(SITE_SCHEMA);
};

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { Md5 } from 'ts-md5/dist/md5'; import { Md5 } from 'ts-md5/dist/md5';
import { timeout } from 'rxjs/operators'; import { timeout } from 'rxjs/operators';
import { CoreApp, CoreAppSchema, CoreStoreConfig } from '@services/app'; import { CoreApp, CoreStoreConfig } from '@services/app';
import { CoreEvents } from '@singletons/events'; import { CoreEvents } from '@singletons/events';
import { CoreWS } from '@services/ws'; import { CoreWS } from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
@ -38,114 +38,36 @@ import { CoreError } from '@classes/errors/error';
import { CoreSiteError } from '@classes/errors/siteerror'; import { CoreSiteError } from '@classes/errors/siteerror';
import { makeSingleton, Translate, Http } from '@singletons/core.singletons'; import { makeSingleton, Translate, Http } from '@singletons/core.singletons';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import {
APP_SCHEMA,
SCHEMA_VERSIONS_TABLE_SCHEMA,
SITES_TABLE_NAME,
CURRENT_SITE_TABLE_NAME,
SCHEMA_VERSIONS_TABLE_NAME,
SiteDBEntry,
CurrentSiteDBEntry,
SchemaVersionsDBEntry,
} from '@services/sites.db';
const SITES_TABLE = 'sites_2';
const CURRENT_SITE_TABLE = 'current_site'; // Schemas for site tables. Other providers can add schemas in here using the registerSiteSchema function.
const SCHEMA_VERSIONS_TABLE = 'schema_versions'; const siteSchemas: { [name: string]: CoreRegisteredSiteSchema } = {};
export const registerSiteSchema = (schema: CoreSiteSchema): void => {
siteSchemas[schema.name] = schema;
};
/* /*
* Service to manage and interact with sites. * Service to manage and interact with sites.
* It allows creating tables in the databases of all sites. Each service or component should be responsible of creating * It allows creating tables in the databases of all sites. Each service or component should be responsible of creating
* their own database tables. Example: * their own database tables. Example:
* *
* constructor(sitesProvider: CoreSitesProvider) { * import { registerSiteSchema } from '@services/sites';
* this.sitesProvider.registerSiteSchema(this.tableSchema);
* *
* This provider will automatically create the tables in the databases of all the instantiated sites, and also to the * registerSiteSchema(tableSchema);
* databases of sites instantiated from now on.
*/ */
@Injectable() @Injectable()
export class CoreSitesProvider { export class CoreSitesProvider {
// Variables for the database.
protected appTablesSchema: CoreAppSchema = {
name: 'CoreSitesProvider',
version: 2,
tables: [
{
name: 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: 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<void> {
if (oldVersion < 2) {
const newTable = 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<SiteDBEntry>(oldTable);
const promises: Promise<number>[] = [];
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.
}
}
},
};
// Constants to validate a site version. // Constants to validate a site version.
protected readonly WORKPLACE_APP = 3; protected readonly WORKPLACE_APP = 3;
protected readonly MOODLE_APP = 2; protected readonly MOODLE_APP = 2;
@ -162,112 +84,15 @@ export class CoreSitesProvider {
protected appDB: SQLiteDB; protected appDB: SQLiteDB;
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized. protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
protected siteSchemasMigration: { [siteId: string]: Promise<void> } = {}; protected siteSchemasMigration: { [siteId: string]: Promise<void> } = {};
protected pluginsSiteSchemas: { [name: string]: CoreRegisteredSiteSchema } = {};
// Schemas for site tables. Other providers can add schemas in here.
protected siteSchemas: { [name: string]: CoreRegisteredSiteSchema } = {};
protected siteTablesSchemas: SQLiteDBTableSchema[] = [
{
name: SCHEMA_VERSIONS_TABLE,
columns: [
{
name: 'name',
type: 'TEXT',
primaryKey: true,
},
{
name: 'version',
type: 'INTEGER',
},
],
},
];
// Site schema for this provider.
protected siteSchema: CoreSiteSchema = {
name: 'CoreSitesProvider',
version: 2,
canBeCleared: [CoreSite.WS_CACHE_TABLE],
tables: [
{
name: CoreSite.WS_CACHE_TABLE,
columns: [
{
name: 'id',
type: 'TEXT',
primaryKey: true,
},
{
name: 'data',
type: 'TEXT',
},
{
name: 'key',
type: 'TEXT',
},
{
name: 'expirationTime',
type: 'INTEGER',
},
{
name: 'component',
type: 'TEXT',
},
{
name: 'componentId',
type: 'INTEGER',
},
],
},
{
name: CoreSite.CONFIG_TABLE,
columns: [
{
name: 'name',
type: 'TEXT',
unique: true,
notNull: true,
},
{
name: 'value',
},
],
},
],
async migrate(db: SQLiteDB, oldVersion: number): Promise<void> {
if (oldVersion && oldVersion < 2) {
const newTable = CoreSite.WS_CACHE_TABLE;
const oldTable = 'wscache';
try {
await db.tableExists(oldTable);
} catch (error) {
// Old table does not exist, ignore.
return;
}
// Cannot use insertRecordsFrom because there are extra fields, so manually code INSERT INTO.
await db.execute(
'INSERT INTO ' + newTable + ' ' +
'SELECT id, data, key, expirationTime, NULL as component, NULL as componentId ' +
'FROM ' + oldTable,
);
try {
await db.dropTable(oldTable);
} catch (error) {
// Error deleting old table, ignore.
}
}
},
};
constructor() { constructor() {
this.logger = CoreLogger.getInstance('CoreSitesProvider'); this.logger = CoreLogger.getInstance('CoreSitesProvider');
this.appDB = CoreApp.instance.getDB(); this.appDB = CoreApp.instance.getDB();
this.dbReady = CoreApp.instance.createTablesFromSchema(this.appTablesSchema).catch(() => { this.dbReady = CoreApp.instance.createTablesFromSchema(APP_SCHEMA).catch(() => {
// Ignore errors. // Ignore errors.
}); });
this.registerSiteSchema(this.siteSchema);
} }
/** /**
@ -857,7 +682,7 @@ export class CoreSitesProvider {
oauthId, oauthId,
}; };
await this.appDB.insertRecord(SITES_TABLE, entry); await this.appDB.insertRecord(SITES_TABLE_NAME, entry);
} }
/** /**
@ -1084,7 +909,7 @@ export class CoreSitesProvider {
delete this.sites[siteId]; delete this.sites[siteId];
try { try {
await this.appDB.deleteRecords(SITES_TABLE, { id: siteId }); await this.appDB.deleteRecords(SITES_TABLE_NAME, { id: siteId });
} catch (err) { } catch (err) {
// DB remove shouldn't fail, but we'll go ahead even if it does. // DB remove shouldn't fail, but we'll go ahead even if it does.
} }
@ -1103,7 +928,7 @@ export class CoreSitesProvider {
async hasSites(): Promise<boolean> { async hasSites(): Promise<boolean> {
await this.dbReady; await this.dbReady;
const count = await this.appDB.countRecords(SITES_TABLE); const count = await this.appDB.countRecords(SITES_TABLE_NAME);
return count > 0; return count > 0;
} }
@ -1129,7 +954,7 @@ export class CoreSitesProvider {
return this.sites[siteId]; return this.sites[siteId];
} else { } else {
// Retrieve and create the site. // Retrieve and create the site.
const data = await this.appDB.getRecord<SiteDBEntry>(SITES_TABLE, { id: siteId }); const data = await this.appDB.getRecord<SiteDBEntry>(SITES_TABLE_NAME, { id: siteId });
return this.makeSiteFromSiteListEntry(data); return this.makeSiteFromSiteListEntry(data);
} }
@ -1202,7 +1027,7 @@ export class CoreSitesProvider {
async getSites(ids?: string[]): Promise<CoreSiteBasicInfo[]> { async getSites(ids?: string[]): Promise<CoreSiteBasicInfo[]> {
await this.dbReady; await this.dbReady;
const sites = await this.appDB.getAllRecords<SiteDBEntry>(SITES_TABLE); const sites = await this.appDB.getAllRecords<SiteDBEntry>(SITES_TABLE_NAME);
const formattedSites: CoreSiteBasicInfo[] = []; const formattedSites: CoreSiteBasicInfo[] = [];
sites.forEach((site) => { sites.forEach((site) => {
@ -1266,7 +1091,7 @@ export class CoreSitesProvider {
async getLoggedInSitesIds(): Promise<string[]> { async getLoggedInSitesIds(): Promise<string[]> {
await this.dbReady; await this.dbReady;
const sites = await this.appDB.getRecords<SiteDBEntry>(SITES_TABLE, { loggedOut : 0 }); const sites = await this.appDB.getRecords<SiteDBEntry>(SITES_TABLE_NAME, { loggedOut : 0 });
return sites.map((site) => site.id); return sites.map((site) => site.id);
} }
@ -1279,7 +1104,7 @@ export class CoreSitesProvider {
async getSitesIds(): Promise<string[]> { async getSitesIds(): Promise<string[]> {
await this.dbReady; await this.dbReady;
const sites = await this.appDB.getAllRecords<SiteDBEntry>(SITES_TABLE); const sites = await this.appDB.getAllRecords<SiteDBEntry>(SITES_TABLE_NAME);
return sites.map((site) => site.id); return sites.map((site) => site.id);
} }
@ -1298,7 +1123,7 @@ export class CoreSitesProvider {
siteId, siteId,
}; };
await this.appDB.insertRecord(CURRENT_SITE_TABLE, entry); await this.appDB.insertRecord(CURRENT_SITE_TABLE_NAME, entry);
CoreEvents.trigger(CoreEvents.LOGIN, {}, siteId); CoreEvents.trigger(CoreEvents.LOGIN, {}, siteId);
} }
@ -1324,7 +1149,7 @@ export class CoreSitesProvider {
promises.push(this.setSiteLoggedOut(siteId, true)); promises.push(this.setSiteLoggedOut(siteId, true));
} }
promises.push(this.appDB.deleteRecords(CURRENT_SITE_TABLE, { id: 1 })); promises.push(this.appDB.deleteRecords(CURRENT_SITE_TABLE_NAME, { id: 1 }));
} }
try { try {
@ -1349,7 +1174,7 @@ export class CoreSitesProvider {
this.sessionRestored = true; this.sessionRestored = true;
try { try {
const currentSite = await this.appDB.getRecord<CurrentSiteDBEntry>(CURRENT_SITE_TABLE, { id: 1 }); const currentSite = await this.appDB.getRecord<CurrentSiteDBEntry>(CURRENT_SITE_TABLE_NAME, { id: 1 });
const siteId = currentSite.siteId; const siteId = currentSite.siteId;
this.logger.debug(`Restore session in site ${siteId}`); this.logger.debug(`Restore session in site ${siteId}`);
@ -1377,7 +1202,7 @@ export class CoreSitesProvider {
site.setLoggedOut(loggedOut); site.setLoggedOut(loggedOut);
await this.appDB.updateRecords(SITES_TABLE, newValues, { id: siteId }); await this.appDB.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId });
} }
/** /**
@ -1426,7 +1251,7 @@ export class CoreSitesProvider {
site.privateToken = privateToken; site.privateToken = privateToken;
site.setLoggedOut(false); // Token updated means the user authenticated again, not logged out anymore. site.setLoggedOut(false); // Token updated means the user authenticated again, not logged out anymore.
await this.appDB.updateRecords(SITES_TABLE, newValues, { id: siteId }); await this.appDB.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId });
} }
/** /**
@ -1470,7 +1295,7 @@ export class CoreSitesProvider {
} }
try { try {
await this.appDB.updateRecords(SITES_TABLE, newValues, { id: siteId }); await this.appDB.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId });
} finally { } finally {
CoreEvents.trigger(CoreEvents.SITE_UPDATED, info, siteId); CoreEvents.trigger(CoreEvents.SITE_UPDATED, info, siteId);
} }
@ -1529,7 +1354,7 @@ export class CoreSitesProvider {
} }
try { try {
const siteEntries = await this.appDB.getAllRecords<SiteDBEntry>(SITES_TABLE); const siteEntries = await this.appDB.getAllRecords<SiteDBEntry>(SITES_TABLE_NAME);
const ids: string[] = []; const ids: string[] = [];
const promises: Promise<unknown>[] = []; const promises: Promise<unknown>[] = [];
@ -1562,7 +1387,7 @@ export class CoreSitesProvider {
async getStoredCurrentSiteId(): Promise<string> { async getStoredCurrentSiteId(): Promise<string> {
await this.dbReady; await this.dbReady;
const currentSite = await this.appDB.getRecord<CurrentSiteDBEntry>(CURRENT_SITE_TABLE, { id: 1 }); const currentSite = await this.appDB.getRecord<CurrentSiteDBEntry>(CURRENT_SITE_TABLE_NAME, { id: 1 });
return currentSite.siteId; return currentSite.siteId;
} }
@ -1605,32 +1430,6 @@ export class CoreSitesProvider {
return this.getSite(siteId).then((site) => site.isFeatureDisabled(name)); return this.getSite(siteId).then((site) => site.isFeatureDisabled(name));
} }
/**
* Create a table in all the sites databases.
*
* @param table Table schema.
* @deprecated. Please use registerSiteSchema instead.
*/
createTableFromSchema(table: SQLiteDBTableSchema): void {
this.createTablesFromSchema([table]);
}
/**
* 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.
this.siteTablesSchemas = this.siteTablesSchemas.concat(tables);
// Now create these tables in current sites.
for (const id in this.sites) {
this.sites[id].getDb().createTablesFromSchema(tables);
}
}
/** /**
* Check if a WS is available in the current site, if any. * Check if a WS is available in the current site, if any.
* *
@ -1645,40 +1444,29 @@ export class CoreSitesProvider {
} }
/** /**
* Register a site schema. * Register a site schema in current site.
* This function is meant for site plugins to create DB tables in current site. Tables created from within the app
* whould use the registerSiteSchema function exported in this same file.
* *
* @param schema The schema to register. * @param schema The schema to register.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
async registerSiteSchema(schema: CoreSiteSchema): Promise<void> { async registerSiteSchema(schema: CoreSiteSchema): Promise<void> {
if (this.currentSite) { if (!this.currentSite) {
return;
}
try { try {
// Site has already been created, apply the schema directly. // Site has already been created, apply the schema directly.
const schemas: {[name: string]: CoreRegisteredSiteSchema} = {}; const schemas: {[name: string]: CoreRegisteredSiteSchema} = {};
schemas[schema.name] = schema; schemas[schema.name] = schema;
if (!schema.onlyCurrentSite) {
// Apply it to all sites.
const siteIds = await this.getSitesIds();
await Promise.all(siteIds.map(async (siteId) => {
const site = await this.getSite(siteId);
return this.applySiteSchemas(site, schemas);
}));
} else {
// Apply it to the specified site only. // Apply it to the specified site only.
(schema as CoreRegisteredSiteSchema).siteId = this.currentSite.getId(); (schema as CoreRegisteredSiteSchema).siteId = this.currentSite.getId();
await this.applySiteSchemas(this.currentSite, schemas); await this.applySiteSchemas(this.currentSite, schemas);
}
} finally { } finally {
// Add the schema to the list. It's done in the end to prevent a schema being applied twice. this.pluginsSiteSchemas[schema.name] = schema;
this.siteSchemas[schema.name] = schema;
}
} else if (!schema.onlyCurrentSite) {
// Add the schema to the list, it will be applied when the sites are created.
this.siteSchemas[schema.name] = schema;
} }
} }
@ -1700,8 +1488,8 @@ export class CoreSitesProvider {
this.logger.debug(`Migrating all schemas of ${site.id}`); this.logger.debug(`Migrating all schemas of ${site.id}`);
// First create tables not registerd with name/version. // First create tables not registerd with name/version.
const promise = site.getDb().createTablesFromSchema(this.siteTablesSchemas) const promise = site.getDb().createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA)
.then(() => this.applySiteSchemas(site, this.siteSchemas)); .then(() => this.applySiteSchemas(site, siteSchemas));
this.siteSchemasMigration[site.id] = promise; this.siteSchemasMigration[site.id] = promise;
@ -1721,7 +1509,7 @@ export class CoreSitesProvider {
const db = site.getDb(); const db = site.getDb();
// Fetch installed versions of the schema. // Fetch installed versions of the schema.
const records = await db.getAllRecords<SchemaVersionsDBEntry>(SCHEMA_VERSIONS_TABLE); const records = await db.getAllRecords<SchemaVersionsDBEntry>(SCHEMA_VERSIONS_TABLE_NAME);
const versions: {[name: string]: number} = {}; const versions: {[name: string]: number} = {};
records.forEach((record) => { records.forEach((record) => {
@ -1768,7 +1556,7 @@ export class CoreSitesProvider {
} }
// Set installed version. // Set installed version.
await db.insertRecord(SCHEMA_VERSIONS_TABLE, { name, version: schema.version }); await db.insertRecord(SCHEMA_VERSIONS_TABLE_NAME, { name, version: schema.version });
} }
/** /**
@ -1814,13 +1602,13 @@ export class CoreSitesProvider {
*/ */
getSiteTableSchemasToClear(site: CoreSite): string[] { getSiteTableSchemasToClear(site: CoreSite): string[] {
let reset: string[] = []; let reset: string[] = [];
for (const name in this.siteSchemas) { const schemas = Object.values(siteSchemas).concat(Object.values(this.pluginsSiteSchemas));
const schema = this.siteSchemas[name];
schemas.forEach((schema) => {
if (schema.canBeCleared && (!schema.siteId || site.getId() == schema.siteId)) { if (schema.canBeCleared && (!schema.siteId || site.getId() == schema.siteId)) {
reset = reset.concat(schema.canBeCleared); reset = reset.concat(schema.canBeCleared);
} }
} });
return reset; return reset;
} }
@ -1980,12 +1768,6 @@ export type CoreSiteSchema = {
*/ */
canBeCleared?: string[]; canBeCleared?: string[];
/**
* If true, the schema will only be applied to the current site. Otherwise it will be applied to all sites.
* If you're implementing a site plugin, please set it to true.
*/
onlyCurrentSite?: boolean;
/** /**
* Tables to create when installing or upgrading the schema. * Tables to create when installing or upgrading the schema.
*/ */
@ -2088,24 +1870,3 @@ export type CoreSitesLoginTokenResponse = {
debuginfo?: string; debuginfo?: string;
reproductionlink?: string; reproductionlink?: string;
}; };
type SiteDBEntry = {
id: string;
siteUrl: string;
token: string;
info: string;
privateToken: string;
config: string;
loggedOut: number;
oauthId: number;
};
type CurrentSiteDBEntry = {
id: number;
siteId: string;
};
type SchemaVersionsDBEntry = {
name: string;
version: number;
};

View File

@ -0,0 +1,62 @@
// (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 { CoreSiteSchema, registerSiteSchema } from '@services/sites';
/**
* Database variables for CoreSync service.
*/
export const SYNC_TABLE_NAME = 'sync';
export const SITE_SCHEMA: CoreSiteSchema = {
name: 'CoreSyncProvider',
version: 1,
tables: [
{
name: SYNC_TABLE_NAME,
columns: [
{
name: 'component',
type: 'TEXT',
notNull: true,
},
{
name: 'id',
type: 'TEXT',
notNull: true,
},
{
name: 'time',
type: 'INTEGER',
},
{
name: 'warnings',
type: 'TEXT',
},
],
primaryKeys: ['component', 'id'],
},
],
};
export type CoreSyncRecord = {
component: string;
id: string;
time: number;
warnings: string;
};
export const initCoreSyncDB = (): void => {
registerSiteSchema(SITE_SCHEMA);
};

View File

@ -16,8 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreEvents } from '@singletons/events'; import { CoreEvents } from '@singletons/events';
import { CoreSites, CoreSiteSchema } from '@services/sites'; import { CoreSites, CoreSiteSchema } from '@services/sites';
import { makeSingleton } from '@singletons/core.singletons'; import { makeSingleton } from '@singletons/core.singletons';
import { SYNC_TABLE_NAME, CoreSyncRecord } from '@services/sync.db';
const SYNC_TABLE = 'sync';
/* /*
* Service that provides some features regarding synchronization. * Service that provides some features regarding synchronization.
@ -31,7 +30,7 @@ export class CoreSyncProvider {
version: 1, version: 1,
tables: [ tables: [
{ {
name: SYNC_TABLE, name: SYNC_TABLE_NAME,
columns: [ columns: [
{ {
name: 'component', name: 'component',
@ -61,8 +60,6 @@ export class CoreSyncProvider {
protected blockedItems: { [siteId: string]: { [blockId: string]: { [operation: string]: boolean } } } = {}; protected blockedItems: { [siteId: string]: { [blockId: string]: { [operation: string]: boolean } } } = {};
constructor() { constructor() {
CoreSites.instance.registerSiteSchema(this.siteSchema);
// Unblock all blocks on logout. // Unblock all blocks on logout.
CoreEvents.on(CoreEvents.LOGOUT, (data: {siteId: string}) => { CoreEvents.on(CoreEvents.LOGOUT, (data: {siteId: string}) => {
this.clearAllBlocks(data.siteId); this.clearAllBlocks(data.siteId);
@ -133,7 +130,7 @@ export class CoreSyncProvider {
* @return Record if found or reject. * @return Record if found or reject.
*/ */
getSyncRecord(component: string, id: string | number, siteId?: string): Promise<CoreSyncRecord> { getSyncRecord(component: string, id: string | number, siteId?: string): Promise<CoreSyncRecord> {
return CoreSites.instance.getSiteDb(siteId).then((db) => db.getRecord(SYNC_TABLE, { component: component, id: id })); return CoreSites.instance.getSiteDb(siteId).then((db) => db.getRecord(SYNC_TABLE_NAME, { component: component, id: id }));
} }
/** /**
@ -151,7 +148,7 @@ export class CoreSyncProvider {
data.component = component; data.component = component;
data.id = id; data.id = id;
await db.insertRecord(SYNC_TABLE, data); await db.insertRecord(SYNC_TABLE_NAME, data);
} }
/** /**
@ -211,10 +208,3 @@ export class CoreSyncProvider {
} }
export class CoreSync extends makeSingleton(CoreSyncProvider) {} export class CoreSync extends makeSingleton(CoreSyncProvider) {}
export type CoreSyncRecord = {
component: string;
id: string;
time: number;
warnings: string;
};