MOBILE-3977 core: Reuse databases initialization

main
Noel De Martin 2022-02-07 13:46:09 +01:00
parent 7a2a8c3e98
commit 359d7ab5a5
4 changed files with 114 additions and 82 deletions

View File

@ -44,6 +44,7 @@ import { CoreLang } from '@services/lang';
import { CoreSites } from '@services/sites';
import { asyncInstance, AsyncInstance } from '../utils/async-instance';
import { CoreDatabaseTable } from './database/database-table';
import { CoreDatabaseCachingStrategy } from './database/database-table-proxy';
/**
* QR Code type enumeration.
@ -140,7 +141,11 @@ export class CoreSite {
) {
this.logger = CoreLogger.getInstance('CoreSite');
this.siteUrl = CoreUrlUtils.removeUrlParams(this.siteUrl); // Make sure the URL doesn't have params.
this.cacheTable = asyncInstance(() => CoreSites.getCacheTable(this));
this.cacheTable = asyncInstance(() => CoreSites.getSiteTable(CoreSite.WS_CACHE_TABLE, {
siteId: this.getId(),
database: this.getDb(),
config: { cachingStrategy: CoreDatabaseCachingStrategy.None },
}));
this.setInfo(infos);
this.calculateOfflineDisabled();

View File

@ -48,9 +48,10 @@ import {
} from '@services/database/filepool';
import { CoreFileHelper } from './file-helper';
import { CoreUrl } from '@singletons/url';
import { CorePromisedValue } from '@classes/promised-value';
import { CoreDatabaseTable } from '@classes/database/database-table';
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
import { CoreDatabaseCachingStrategy } from '@classes/database/database-table-proxy';
import { lazyMap, LazyMap } from '../utils/lazy-map';
import { asyncInstance, AsyncInstance } from '../utils/async-instance';
/*
* Factory for handling downloading files and retrieve downloaded files.
@ -101,11 +102,20 @@ export class CoreFilepoolProvider {
// Variables for DB.
protected appDB: Promise<SQLiteDB>;
protected resolveAppDB!: (appDB: SQLiteDB) => void;
protected filesTables: Record<string, CorePromisedValue<CoreDatabaseTable<CoreFilepoolFileEntry, 'fileId'>>> = {};
protected filesTables: LazyMap<AsyncInstance<CoreDatabaseTable<CoreFilepoolFileEntry, 'fileId'>>>;
constructor() {
this.appDB = new Promise(resolve => this.resolveAppDB = resolve);
this.logger = CoreLogger.getInstance('CoreFilepoolProvider');
this.filesTables = lazyMap(
siteId => asyncInstance(
() => CoreSites.getSiteTable<CoreFilepoolFileEntry, 'fileId'>(FILES_TABLE_NAME, {
siteId,
config: { cachingStrategy: CoreDatabaseCachingStrategy.Lazy },
primaryKeyColumns: ['fileId'],
}),
),
);
}
/**
@ -128,11 +138,9 @@ export class CoreFilepoolProvider {
return;
}
const filesTable = await this.filesTables[siteId];
await this.filesTables[siteId].destroy();
delete this.filesTables[siteId];
await filesTable.destroy();
});
}
@ -149,33 +157,6 @@ export class CoreFilepoolProvider {
this.resolveAppDB(CoreApp.getDB());
}
/**
* Get files table.
*
* @param siteId Site id.
* @returns Files table.
*/
async getFilesTable(siteId?: string): Promise<CoreDatabaseTable<CoreFilepoolFileEntry, 'fileId'>> {
siteId = siteId ?? CoreSites.getCurrentSiteId();
if (!(siteId in this.filesTables)) {
const filesTable = this.filesTables[siteId] = new CorePromisedValue();
const database = await CoreSites.getSiteDb(siteId);
const table = new CoreDatabaseTableProxy<CoreFilepoolFileEntry, 'fileId'>(
{ cachingStrategy: CoreDatabaseCachingStrategy.Lazy },
database,
FILES_TABLE_NAME,
['fileId'],
);
await table.initialize();
filesTable.resolve(table);
}
return this.filesTables[siteId];
}
/**
* Link a file with a component.
*
@ -262,9 +243,7 @@ export class CoreFilepoolProvider {
...data,
};
const filesTable = await this.getFilesTable(siteId);
await filesTable.insert(record);
await this.filesTables[siteId].insert(record);
}
/**
@ -605,14 +584,13 @@ export class CoreFilepoolProvider {
*/
async clearFilepool(siteId: string): Promise<void> {
const db = await CoreSites.getSiteDb(siteId);
const filesTable = await this.getFilesTable(siteId);
// Read the data first to be able to notify the deletions.
const filesEntries = await filesTable.all();
const filesEntries = await this.filesTables[siteId].all();
const filesLinks = await db.getAllRecords<CoreFilepoolLinksRecord>(LINKS_TABLE_NAME);
await Promise.all([
filesTable.delete(),
this.filesTables[siteId].delete(),
db.deleteRecords(LINKS_TABLE_NAME),
]);
@ -1167,14 +1145,13 @@ export class CoreFilepoolProvider {
}
const db = await CoreSites.getSiteDb(siteId);
const filesTable = await this.getFilesTable(siteId);
const extension = CoreMimetypeUtils.getFileExtension(entry.path);
if (!extension) {
// Files does not have extension. Invalidate file (stale = true).
// Minor problem: file will remain in the filesystem once downloaded again.
this.logger.debug('Staled file with no extension ' + entry.fileId);
await filesTable.update({ stale: 1 }, { fileId: entry.fileId });
await this.filesTables[siteId].update({ stale: 1 }, { fileId: entry.fileId });
return;
}
@ -1184,7 +1161,7 @@ export class CoreFilepoolProvider {
entry.fileId = CoreMimetypeUtils.removeExtension(fileId);
entry.extension = extension;
await filesTable.update(entry, { fileId });
await this.filesTables[siteId].update(entry, { fileId });
if (entry.fileId == fileId) {
// File ID hasn't changed, we're done.
this.logger.debug('Removed extesion ' + extension + ' from file ' + entry.fileId);
@ -1445,13 +1422,12 @@ export class CoreFilepoolProvider {
*/
async getFilesByComponent(siteId: string, component: string, componentId?: string | number): Promise<CoreFilepoolFileEntry[]> {
const db = await CoreSites.getSiteDb(siteId);
const filesTable = await this.getFilesTable(siteId);
const items = await this.getComponentFiles(db, component, componentId);
const files: CoreFilepoolFileEntry[] = [];
await Promise.all(items.map(async (item) => {
try {
const fileEntry = await filesTable.findByPrimaryKey({ fileId: item.fileId });
const fileEntry = await this.filesTables[siteId].findByPrimaryKey({ fileId: item.fileId });
if (!fileEntry) {
return;
@ -2184,9 +2160,7 @@ export class CoreFilepoolProvider {
* @return Resolved with file object from DB on success, rejected otherwise.
*/
protected async hasFileInPool(siteId: string, fileId: string): Promise<CoreFilepoolFileEntry> {
const filesTable = await this.getFilesTable(siteId);
return filesTable.findByPrimaryKey({ fileId });
return this.filesTables[siteId].findByPrimaryKey({ fileId });
}
/**
@ -2218,17 +2192,15 @@ export class CoreFilepoolProvider {
* @return Resolved on success.
*/
async invalidateAllFiles(siteId: string, onlyUnknown: boolean = true): Promise<void> {
const filesTable = await this.getFilesTable(siteId);
onlyUnknown
? await filesTable.updateWhere(
? await this.filesTables[siteId].updateWhere(
{ stale: 1 },
{
sql: CoreFilepoolProvider.FILE_IS_UNKNOWN_SQL,
js: CoreFilepoolProvider.FILE_IS_UNKNOWN_JS,
},
)
: await filesTable.update({ stale: 1 });
: await this.filesTables[siteId].update({ stale: 1 });
}
/**
@ -2247,9 +2219,7 @@ export class CoreFilepoolProvider {
const file = await this.fixPluginfileURL(siteId, fileUrl);
const fileId = this.getFileIdByUrl(CoreFileHelper.getFileUrl(file));
const filesTable = await this.getFilesTable(siteId);
await filesTable.update({ stale: 1 }, { fileId });
await this.filesTables[siteId].update({ stale: 1 }, { fileId });
}
/**
@ -2269,7 +2239,6 @@ export class CoreFilepoolProvider {
onlyUnknown: boolean = true,
): Promise<void> {
const db = await CoreSites.getSiteDb(siteId);
const filesTable = await this.getFilesTable(siteId);
const items = await this.getComponentFiles(db, component, componentId);
if (!items.length) {
@ -2277,6 +2246,8 @@ export class CoreFilepoolProvider {
return;
}
siteId = siteId ?? CoreSites.getCurrentSiteId();
const fileIds = items.map((item) => item.fileId);
const whereAndParams = db.getInOrEqual(fileIds);
@ -2287,7 +2258,7 @@ export class CoreFilepoolProvider {
whereAndParams.sql += ' AND (' + CoreFilepoolProvider.FILE_IS_UNKNOWN_SQL + ')';
}
await filesTable.updateWhere(
await this.filesTables[siteId].updateWhere(
{ stale: 1 },
{
sql: whereAndParams.sql,
@ -2714,7 +2685,6 @@ export class CoreFilepoolProvider {
*/
protected async removeFileById(siteId: string, fileId: string): Promise<void> {
const db = await CoreSites.getSiteDb(siteId);
const filesTable = await this.getFilesTable(siteId);
// Get the path to the file first since it relies on the file object stored in the pool.
// Don't use getFilePath to prevent performing 2 DB requests.
@ -2741,7 +2711,7 @@ export class CoreFilepoolProvider {
const promises: Promise<unknown>[] = [];
// Remove entry from filepool store.
promises.push(filesTable.delete(conditions));
promises.push(this.filesTables[siteId].delete(conditions));
// Remove links.
promises.push(db.deleteRecords(LINKS_TABLE_NAME, conditions));

View File

@ -31,9 +31,8 @@ import {
CoreSiteConfig,
CoreSitePublicConfigResponse,
CoreSiteInfoResponse,
CoreSiteWSCacheRecord,
} from '@classes/site';
import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
import { SQLiteDB, SQLiteDBRecordValues, SQLiteDBTableSchema } from '@classes/sqlitedb';
import { CoreError } from '@classes/errors/error';
import { CoreSiteError } from '@classes/errors/siteerror';
import { makeSingleton, Translate, Http } from '@singletons';
@ -60,7 +59,7 @@ import { CoreAjaxWSError } from '@classes/errors/ajaxwserror';
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
import { CorePromisedValue } from '@classes/promised-value';
import { CoreDatabaseTable } from '@classes/database/database-table';
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
import { CoreDatabaseConfiguration, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS');
@ -89,7 +88,7 @@ export class CoreSitesProvider {
// Variables for DB.
protected appDB: Promise<SQLiteDB>;
protected resolveAppDB!: (appDB: SQLiteDB) => void;
protected cacheTables: Record<string, CorePromisedValue<CoreDatabaseTable<CoreSiteWSCacheRecord>>> = {};
protected siteTables: Record<string, Record<string, CorePromisedValue<CoreDatabaseTable>>> = {};
constructor(@Optional() @Inject(CORE_SITE_SCHEMAS) siteSchemas: CoreSiteSchema[][] = []) {
this.appDB = new Promise(resolve => this.resolveAppDB = resolve);
@ -109,15 +108,17 @@ export class CoreSitesProvider {
*/
initialize(): void {
CoreEvents.on(CoreEvents.SITE_DELETED, async ({ siteId }) => {
if (!siteId || !(siteId in this.cacheTables)) {
if (!siteId || !(siteId in this.siteTables)) {
return;
}
const cacheTable = await this.cacheTables[siteId];
await Promise.all(
Object
.values(this.siteTables[siteId])
.map(promisedTable => promisedTable.then(table => table.destroy())),
);
delete this.cacheTables[siteId];
await cacheTable.destroy();
delete this.siteTables[siteId];
});
}
@ -135,30 +136,46 @@ export class CoreSitesProvider {
}
/**
* Get cache table.
* Get site table.
*
* @param siteId Site id.
* @returns cache table.
* @param tableName Site table name.
* @param options Options to configure table initialization.
* @returns Site table.
*/
async getCacheTable(site: CoreSite): Promise<CoreDatabaseTable<CoreSiteWSCacheRecord>> {
if (!site.id) {
throw new CoreError('Can\'t get cache table for site without id');
async getSiteTable<
DBRecord extends SQLiteDBRecordValues,
PrimaryKeyColumn extends keyof DBRecord
>(
tableName: string,
options: Partial<{
siteId: string;
config: Partial<CoreDatabaseConfiguration>;
database: SQLiteDB;
primaryKeyColumns: PrimaryKeyColumn[];
}> = {},
): Promise<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>> {
const siteId = options.siteId ?? this.getCurrentSiteId();
if (!(siteId in this.siteTables)) {
this.siteTables[siteId] = {};
}
if (!(site.id in this.cacheTables)) {
const promisedTable = this.cacheTables[site.id] = new CorePromisedValue();
const table = new CoreDatabaseTableProxy<CoreSiteWSCacheRecord>(
{ cachingStrategy: CoreDatabaseCachingStrategy.None },
site.getDb(),
CoreSite.WS_CACHE_TABLE,
if (!(tableName in this.siteTables[siteId])) {
const promisedTable = this.siteTables[siteId][tableName] = new CorePromisedValue();
const database = options.database ?? await this.getSiteDb(siteId);
const table = new CoreDatabaseTableProxy<DBRecord, PrimaryKeyColumn>(
options.config ?? {},
database,
tableName,
options.primaryKeyColumns,
);
await table.initialize();
promisedTable.resolve(table);
promisedTable.resolve(table as unknown as CoreDatabaseTable);
}
return this.cacheTables[site.id];
return this.siteTables[siteId][tableName] as unknown as Promise<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>>;
}
/**

View File

@ -0,0 +1,40 @@
// (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.
/**
* Lazy map.
*
* Lazy maps are empty by default, but entries are generated lazily when accessed.
*/
export type LazyMap<T> = Record<string, T>;
/**
* Create a map that will initialize entries lazily with the given constructor.
*
* @param lazyConstructor Constructor to use the first time an entry is accessed.
* @returns Lazy map.
*/
export function lazyMap<T>(lazyConstructor: (key: string) => T): LazyMap<T> {
const instances = {};
return new Proxy(instances, {
get(target, property, receiver) {
if (!(property in instances)) {
target[property] = lazyConstructor(property.toString());
}
return Reflect.get(target, property, receiver);
},
});
}