Merge pull request #3122 from NoelDeMartin/MOBILE-3833

MOBILE-3833 core: Improve database config updates
main
Dani Palou 2022-02-16 15:01:59 +01:00 committed by GitHub
commit 426814a247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 169 additions and 41 deletions

View File

@ -23,6 +23,7 @@ import {
CoreDatabaseConditions, CoreDatabaseConditions,
GetDBRecordPrimaryKey, GetDBRecordPrimaryKey,
CoreDatabaseQueryOptions, CoreDatabaseQueryOptions,
CoreDatabaseTableConstructor,
} from './database-table'; } from './database-table';
import { CoreDebugDatabaseTable } from './debug-database-table'; import { CoreDebugDatabaseTable } from './debug-database-table';
import { CoreEagerDatabaseTable } from './eager-database-table'; import { CoreEagerDatabaseTable } from './eager-database-table';
@ -42,6 +43,14 @@ export class CoreDatabaseTableProxy<
protected config: CoreDatabaseConfiguration; protected config: CoreDatabaseConfiguration;
protected target = asyncInstance<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>>(); protected target = asyncInstance<CoreDatabaseTable<DBRecord, PrimaryKeyColumn>>();
protected environmentObserver?: CoreEventObserver; protected environmentObserver?: CoreEventObserver;
protected targetConstructors: Record<
CoreDatabaseCachingStrategy,
CoreDatabaseTableConstructor<DBRecord, PrimaryKeyColumn, PrimaryKey>
> = {
[CoreDatabaseCachingStrategy.Eager]: CoreEagerDatabaseTable,
[CoreDatabaseCachingStrategy.Lazy]: CoreLazyDatabaseTable,
[CoreDatabaseCachingStrategy.None]: CoreDatabaseTable,
};
constructor( constructor(
config: Partial<CoreDatabaseConfiguration>, config: Partial<CoreDatabaseConfiguration>,
@ -58,7 +67,13 @@ export class CoreDatabaseTableProxy<
* @inheritdoc * @inheritdoc
*/ */
async initialize(): Promise<void> { async initialize(): Promise<void> {
this.environmentObserver = CoreEvents.on(CoreConfigProvider.ENVIRONMENT_UPDATED, () => this.updateTarget()); this.environmentObserver = CoreEvents.on(CoreConfigProvider.ENVIRONMENT_UPDATED, async () => {
if (!(await this.shouldUpdateTarget())) {
return;
}
this.updateTarget();
});
await this.updateTarget(); await this.updateTarget();
} }
@ -202,6 +217,20 @@ export class CoreDatabaseTableProxy<
this.target.setInstance(newTarget); this.target.setInstance(newTarget);
} }
/**
* Check whether the underlying target should be updated.
*
* @returns Whether target should be updated.
*/
protected async shouldUpdateTarget(): Promise<boolean> {
const config = await this.getRuntimeConfig();
const target = await this.target.getInstance();
const originalTarget = target instanceof CoreDebugDatabaseTable ? target.getTarget() : target;
return (config.debug && target === originalTarget)
|| originalTarget?.constructor !== this.targetConstructors[config.cachingStrategy];
}
/** /**
* Create proxy target. * Create proxy target.
* *
@ -221,14 +250,9 @@ export class CoreDatabaseTableProxy<
* @returns Database table. * @returns Database table.
*/ */
protected createTable(cachingStrategy: CoreDatabaseCachingStrategy): CoreDatabaseTable<DBRecord, PrimaryKeyColumn> { protected createTable(cachingStrategy: CoreDatabaseCachingStrategy): CoreDatabaseTable<DBRecord, PrimaryKeyColumn> {
switch (cachingStrategy) { const DatabaseTable = this.targetConstructors[cachingStrategy];
case CoreDatabaseCachingStrategy.Eager:
return new CoreEagerDatabaseTable(this.database, this.tableName, this.primaryKeyColumns); return new DatabaseTable(this.database, this.tableName, this.primaryKeyColumns);
case CoreDatabaseCachingStrategy.Lazy:
return new CoreLazyDatabaseTable(this.database, this.tableName, this.primaryKeyColumns);
case CoreDatabaseCachingStrategy.None:
return new CoreDatabaseTable(this.database, this.tableName, this.primaryKeyColumns);
}
} }
} }

View File

@ -336,6 +336,23 @@ export class CoreDatabaseTable<
} }
/**
* CoreDatabaseTable constructor.
*/
export type CoreDatabaseTableConstructor<
DBRecord extends SQLiteDBRecordValues = SQLiteDBRecordValues,
PrimaryKeyColumn extends keyof DBRecord = 'id',
PrimaryKey extends GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn> = GetDBRecordPrimaryKey<DBRecord, PrimaryKeyColumn>
> = {
new (
database: SQLiteDB,
tableName: string,
primaryKeyColumns?: PrimaryKeyColumn[]
): CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey>;
};
/** /**
* Infer primary key type from database record and primary key column types. * Infer primary key type from database record and primary key column types.
*/ */

View File

@ -43,6 +43,13 @@ export class CoreDebugDatabaseTable<
this.logger = CoreLogger.getInstance(`CoreDatabase[${this.tableName}]`); this.logger = CoreLogger.getInstance(`CoreDatabase[${this.tableName}]`);
} }
/**
* Get underlying table instance.
*/
getTarget(): CoreDatabaseTable<DBRecord, PrimaryKeyColumn, PrimaryKey> {
return this.target;
}
/** /**
* @inheritdoc * @inheritdoc
*/ */

View File

@ -333,7 +333,14 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.viewLeft = true; this.viewLeft = true;
CoreEvents.trigger(CoreEvents.LOGIN_SITE_UNCHECKED, { config: this.siteConfig }, this.siteId); CoreEvents.trigger(
CoreEvents.LOGIN_SITE_UNCHECKED,
{
config: this.siteConfig,
loginSuccessful: !!this.siteId,
},
this.siteId,
);
this.valueChangeSubscription?.unsubscribe(); this.valueChangeSubscription?.unsubscribe();
} }

View File

@ -57,6 +57,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
protected viewLeft = false; protected viewLeft = false;
protected eventThrown = false; protected eventThrown = false;
protected redirectData?: CoreRedirectPayload; protected redirectData?: CoreRedirectPayload;
protected loginSuccessful = false;
constructor( constructor(
protected fb: FormBuilder, protected fb: FormBuilder,
@ -117,7 +118,14 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.viewLeft = true; this.viewLeft = true;
CoreEvents.trigger(CoreEvents.LOGIN_SITE_UNCHECKED, { config: this.siteConfig }, this.siteId); CoreEvents.trigger(
CoreEvents.LOGIN_SITE_UNCHECKED,
{
config: this.siteConfig,
loginSuccessful: this.loginSuccessful,
},
this.siteId,
);
} }
/** /**
@ -214,6 +222,8 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
this.credForm.controls['password'].reset(); this.credForm.controls['password'].reset();
// Go to the site initial page. // Go to the site initial page.
this.loginSuccessful = true;
await CoreNavigator.navigateToSiteHome({ await CoreNavigator.navigateToSiteHome({
params: this.redirectData, params: this.redirectData,
}); });

View File

@ -108,19 +108,11 @@ export class CoreStylesService {
* Listen events. * Listen events.
*/ */
protected listenEvents(): void { protected listenEvents(): void {
let addingSite: string | undefined;
// When a new site is added to the app, add its styles. // When a new site is added to the app, add its styles.
CoreEvents.on(CoreEvents.SITE_ADDED, async (data) => { CoreEvents.on(CoreEvents.SITE_ADDED, async (data) => {
addingSite = data.siteId;
try { try {
await this.addSite(data.siteId); await this.addSite(data.siteId);
if (addingSite == data.siteId) {
addingSite = undefined;
}
// User has logged in, remove tmp styles and enable loaded styles. // User has logged in, remove tmp styles and enable loaded styles.
if (data.siteId == CoreSites.getCurrentSiteId()) { if (data.siteId == CoreSites.getCurrentSiteId()) {
this.unloadTmpStyles(); this.unloadTmpStyles();
@ -164,10 +156,10 @@ export class CoreStylesService {
}); });
// Unload temporary styles when site config is "unchecked" in login. // Unload temporary styles when site config is "unchecked" in login.
CoreEvents.on(CoreEvents.LOGIN_SITE_UNCHECKED, (data) => { CoreEvents.on(CoreEvents.LOGIN_SITE_UNCHECKED, ({ loginSuccessful }) => {
if (data.siteId && data.siteId === addingSite) { if (loginSuccessful) {
// The tmp styles are from a site that is being added permanently. // The tmp styles have been added for a site we've logged into, so we'll wait for the final
// Wait for the final site styles to be loaded before removing the tmp styles so there is no blink effect. // site styles to be loaded before removing the tmp styles so there is no blink effect.
return; return;
} }

View File

@ -120,6 +120,19 @@ export class CoreConfigProvider {
} }
} }
/**
* Get an app setting directly from the database, without using any optimizations..
*
* @param name The config name.
* @return Resolves upon success along with the config data. Reject on failure.
*/
async getFromDB<T>(name: string): Promise<T> {
const db = CoreApp.getDB();
const record = await db.getRecord<ConfigDBEntry>(CONFIG_TABLE_NAME, { name });
return record.value;
}
/** /**
* Check whether the given app setting exists. * Check whether the given app setting exists.
* *
@ -151,9 +164,14 @@ export class CoreConfigProvider {
* Update config with the given values. * Update config with the given values.
* *
* @param config Config updates. * @param config Config updates.
* @param reset Whether to reset environment before applying the patch.
*/ */
patchEnvironment(config: Partial<EnvironmentConfig>): void { patchEnvironment(config: Partial<EnvironmentConfig>, reset: boolean = false): void {
this.defaultEnvironment = this.defaultEnvironment ?? CoreConstants.CONFIG; this.defaultEnvironment = this.defaultEnvironment ?? { ...CoreConstants.CONFIG };
if (reset) {
this.resetEnvironmentSilently();
}
Object.assign(CoreConstants.CONFIG, config); Object.assign(CoreConstants.CONFIG, config);
CoreEvents.trigger(CoreConfigProvider.ENVIRONMENT_UPDATED, CoreConstants.CONFIG); CoreEvents.trigger(CoreConfigProvider.ENVIRONMENT_UPDATED, CoreConstants.CONFIG);
@ -169,8 +187,7 @@ export class CoreConfigProvider {
return; return;
} }
Object.keys(CoreConstants.CONFIG).forEach(key => delete CoreConstants.CONFIG[key]); this.resetEnvironmentSilently();
Object.assign(CoreConstants.CONFIG, this.defaultEnvironment);
CoreEvents.trigger(CoreConfigProvider.ENVIRONMENT_UPDATED, CoreConstants.CONFIG); CoreEvents.trigger(CoreConfigProvider.ENVIRONMENT_UPDATED, CoreConstants.CONFIG);
} }
@ -185,6 +202,20 @@ export class CoreConfigProvider {
this.patchEnvironment(JSON.parse(CoreUtils.getCookie('MoodleAppConfig') ?? '{}')); this.patchEnvironment(JSON.parse(CoreUtils.getCookie('MoodleAppConfig') ?? '{}'));
} }
/**
* Reset config values to its original state without emitting any events.
*/
protected resetEnvironmentSilently(): void {
if (!this.defaultEnvironment) {
// The environment config hasn't been modified; there's not need to reset.
return;
}
Object.keys(CoreConstants.CONFIG).forEach(key => delete CoreConstants.CONFIG[key]);
Object.assign(CoreConstants.CONFIG, this.defaultEnvironment);
}
} }
export const CoreConfig = makeSingleton(CoreConfigProvider); export const CoreConfig = makeSingleton(CoreConfigProvider);

View File

@ -66,6 +66,7 @@ import { asyncInstance, AsyncInstance } from '../utils/async-instance';
import { CoreConfig } from './config'; import { CoreConfig } from './config';
export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS'); export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS');
export const CORE_SITE_CURRENT_SITE_ID_CONFIG = 'current_site_id';
/* /*
* Service to manage and interact with sites. * Service to manage and interact with sites.
@ -1032,13 +1033,31 @@ export class CoreSitesProvider {
try { try {
const data = await this.sitesTable.getOneByPrimaryKey({ id: siteId }); const data = await this.sitesTable.getOneByPrimaryKey({ id: siteId });
return this.makeSiteFromSiteListEntry(data); return this.addSiteFromSiteListEntry(data);
} catch { } catch {
throw new CoreError('SiteId not found'); throw new CoreError('SiteId not found');
} }
} }
} }
/**
* Get a site directly from the database, without using any optimizations.
*
* @param siteId Site id.
* @return Site.
*/
async getSiteFromDB(siteId: string): Promise<CoreSite> {
const db = CoreApp.getDB();
try {
const record = await db.getRecord<SiteDBEntry>(SITES_TABLE_NAME, { id: siteId });
return this.makeSiteFromSiteListEntry(record);
} catch {
throw new CoreError('SiteId not found');
}
}
/** /**
* Finds a site with a certain URL. It will return the first site found. * Finds a site with a certain URL. It will return the first site found.
* *
@ -1052,7 +1071,7 @@ export class CoreSitesProvider {
return this.sites[data.id]; return this.sites[data.id];
} }
return this.makeSiteFromSiteListEntry(data); return this.addSiteFromSiteListEntry(data);
} }
/** /**
@ -1061,8 +1080,25 @@ export class CoreSitesProvider {
* @param entry Site list entry. * @param entry Site list entry.
* @return Promised resolved with the created site. * @return Promised resolved with the created site.
*/ */
makeSiteFromSiteListEntry(entry: SiteDBEntry): Promise<CoreSite> { addSiteFromSiteListEntry(entry: SiteDBEntry): Promise<CoreSite> {
// Parse info and config. // Parse info and config.
const site = this.makeSiteFromSiteListEntry(entry);
return this.migrateSiteSchemas(site).then(() => {
// Set site after migrating schemas, or a call to getSite could get the site while tables are being created.
this.sites[entry.id] = site;
return site;
});
}
/**
* Make a site instance from a database entry.
*
* @param entry Site database entry.
* @return Site.
*/
makeSiteFromSiteListEntry(entry: SiteDBEntry): CoreSite {
const info = entry.info ? <CoreSiteInfo> CoreTextUtils.parseJSON(entry.info) : undefined; const info = entry.info ? <CoreSiteInfo> CoreTextUtils.parseJSON(entry.info) : undefined;
const config = entry.config ? <CoreSiteConfig> CoreTextUtils.parseJSON(entry.config) : undefined; const config = entry.config ? <CoreSiteConfig> CoreTextUtils.parseJSON(entry.config) : undefined;
@ -1077,12 +1113,7 @@ export class CoreSitesProvider {
); );
site.setOAuthId(entry.oauthId || undefined); site.setOAuthId(entry.oauthId || undefined);
return this.migrateSiteSchemas(site).then(() => {
// Set site after migrating schemas, or a call to getSite could get the site while tables are being created.
this.sites[entry.id] = site;
return site; return site;
});
} }
/** /**
@ -1231,7 +1262,7 @@ export class CoreSitesProvider {
* @return Promise resolved when current site is stored. * @return Promise resolved when current site is stored.
*/ */
async login(siteId: string): Promise<void> { async login(siteId: string): Promise<void> {
await CoreConfig.set('current_site_id', siteId); await CoreConfig.set(CORE_SITE_CURRENT_SITE_ID_CONFIG, siteId);
CoreEvents.trigger(CoreEvents.LOGIN, {}, siteId); CoreEvents.trigger(CoreEvents.LOGIN, {}, siteId);
} }
@ -1477,7 +1508,7 @@ export class CoreSitesProvider {
siteEntries.forEach((site) => { siteEntries.forEach((site) => {
if (!this.sites[site.id]) { if (!this.sites[site.id]) {
promises.push(this.makeSiteFromSiteListEntry(site)); promises.push(this.addSiteFromSiteListEntry(site));
} }
if (this.sites[site.id].containsUrl(url)) { if (this.sites[site.id].containsUrl(url)) {
@ -1504,7 +1535,7 @@ export class CoreSitesProvider {
async getStoredCurrentSiteId(): Promise<string> { async getStoredCurrentSiteId(): Promise<string> {
await this.migrateCurrentSiteLegacyTable(); await this.migrateCurrentSiteLegacyTable();
return CoreConfig.get('current_site_id'); return CoreConfig.get(CORE_SITE_CURRENT_SITE_ID_CONFIG);
} }
/** /**
@ -1513,7 +1544,7 @@ export class CoreSitesProvider {
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
async removeStoredCurrentSite(): Promise<void> { async removeStoredCurrentSite(): Promise<void> {
await CoreConfig.delete('current_site_id'); await CoreConfig.delete(CORE_SITE_CURRENT_SITE_ID_CONFIG);
} }
/** /**
@ -1789,7 +1820,7 @@ export class CoreSitesProvider {
const { siteId } = await db.getRecord<{ siteId: string }>('current_site'); const { siteId } = await db.getRecord<{ siteId: string }>('current_site');
await CoreConfig.set('current_site_id', siteId); await CoreConfig.set(CORE_SITE_CURRENT_SITE_ID_CONFIG, siteId);
await CoreApp.deleteTableSchema('current_site'); await CoreApp.deleteTableSchema('current_site');
await db.dropTable('current_site'); await db.dropTable('current_site');
} finally { } finally {

View File

@ -56,6 +56,7 @@ export interface CoreEventsData {
[CoreEvents.ACTIVITY_DATA_SENT]: CoreEventActivityDataSentData; [CoreEvents.ACTIVITY_DATA_SENT]: CoreEventActivityDataSentData;
[CoreEvents.IAB_LOAD_START]: InAppBrowserEvent; [CoreEvents.IAB_LOAD_START]: InAppBrowserEvent;
[CoreEvents.LOGIN_SITE_CHECKED]: CoreEventLoginSiteCheckedData; [CoreEvents.LOGIN_SITE_CHECKED]: CoreEventLoginSiteCheckedData;
[CoreEvents.LOGIN_SITE_UNCHECKED]: CoreEventLoginSiteUncheckedData;
[CoreEvents.SEND_ON_ENTER_CHANGED]: CoreEventSendOnEnterChangedData; [CoreEvents.SEND_ON_ENTER_CHANGED]: CoreEventSendOnEnterChangedData;
[CoreEvents.COMPONENT_FILE_ACTION]: CoreFilepoolComponentFileEventData; [CoreEvents.COMPONENT_FILE_ACTION]: CoreFilepoolComponentFileEventData;
[CoreEvents.FILE_SHARED]: CoreEventFileSharedData; [CoreEvents.FILE_SHARED]: CoreEventFileSharedData;
@ -400,6 +401,14 @@ export type CoreEventLoginSiteCheckedData = {
config: CoreSitePublicConfigResponse; config: CoreSitePublicConfigResponse;
}; };
/**
* Data passed to LOGIN_SITE_UNCHECKED event.
*/
export type CoreEventLoginSiteUncheckedData = {
config?: CoreSitePublicConfigResponse;
loginSuccessful: boolean;
};
/** /**
* Data passed to SEND_ON_ENTER_CHANGED event. * Data passed to SEND_ON_ENTER_CHANGED event.
*/ */