MOBILE-3977 core: Reuse databases initialization
parent
7a2a8c3e98
commit
359d7ab5a5
|
@ -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();
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue