MOBILE-3936 core: Add install function on database initialization

main
Pau Ferrer Ocaña 2022-09-08 09:27:11 +02:00
parent d0e3ad6ee8
commit 3d7b9dfbc5
2 changed files with 130 additions and 81 deletions

View File

@ -138,6 +138,9 @@ export class CoreAppProvider {
if (schema.tables) { if (schema.tables) {
await this.getDB().createTablesFromSchema(schema.tables); await this.getDB().createTablesFromSchema(schema.tables);
} }
if (schema.install && oldVersion === 0) {
await schema.install(this.getDB());
}
if (schema.migrate && oldVersion > 0) { if (schema.migrate && oldVersion > 0) {
await schema.migrate(this.getDB(), oldVersion); await schema.migrate(this.getDB(), oldVersion);
} }
@ -662,7 +665,7 @@ export class CoreAppProvider {
const entry = await this.schemaVersionsTable.getOneByPrimaryKey({ name: schema.name }); const entry = await this.schemaVersionsTable.getOneByPrimaryKey({ name: schema.name });
return entry.version; return entry.version;
} catch (error) { } catch {
// No installed version yet. // No installed version yet.
return 0; return 0;
} }
@ -727,11 +730,21 @@ export type CoreAppSchema = {
/** /**
* Migrates the schema to the latest version. * Migrates the schema to the latest version.
* *
* Called when installing and upgrading the schema, after creating the defined tables. * Called when upgrading the schema, after creating the defined tables.
* *
* @param db The affected DB. * @param db The affected DB.
* @param oldVersion Old version of the schema or 0 if not installed. * @param oldVersion Old version of the schema or 0 if not installed.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
migrate?(db: SQLiteDB, oldVersion: number): Promise<void>; migrate?(db: SQLiteDB, oldVersion: number): Promise<void>;
/**
* Make changes to install the schema.
*
* Called when installing the schema, after creating the defined tables.
*
* @param db Site database.
* @return Promise resolved when done.
*/
install?(db: SQLiteDB): Promise<void> | void;
}; };

View File

@ -84,6 +84,7 @@ export class CoreSitesProvider {
protected sessionRestored = false; protected sessionRestored = false;
protected currentSite?: CoreSite; protected currentSite?: CoreSite;
protected sites: { [s: string]: CoreSite } = {}; protected sites: { [s: string]: CoreSite } = {};
protected sitesMigrated: { [s: string]: boolean } = {};
protected siteSchemasMigration: { [siteId: string]: Promise<void> } = {}; protected siteSchemasMigration: { [siteId: string]: Promise<void> } = {};
protected siteSchemas: { [name: string]: CoreRegisteredSiteSchema } = {}; protected siteSchemas: { [name: string]: CoreRegisteredSiteSchema } = {};
protected pluginsSiteSchemas: { [name: string]: CoreRegisteredSiteSchema } = {}; protected pluginsSiteSchemas: { [name: string]: CoreRegisteredSiteSchema } = {};
@ -128,7 +129,7 @@ export class CoreSitesProvider {
async initializeDatabase(): Promise<void> { async initializeDatabase(): Promise<void> {
try { try {
await CoreApp.createTablesFromSchema(APP_SCHEMA); await CoreApp.createTablesFromSchema(APP_SCHEMA);
} catch (e) { } catch {
// Ignore errors. // Ignore errors.
} }
@ -218,7 +219,9 @@ export class CoreSitesProvider {
if (!CoreUrlUtils.isHttpURL(siteUrl)) { if (!CoreUrlUtils.isHttpURL(siteUrl)) {
throw new CoreError(Translate.instant('core.login.invalidsite')); throw new CoreError(Translate.instant('core.login.invalidsite'));
} else if (!CoreNetwork.isOnline()) { }
if (!CoreNetwork.isOnline()) {
throw new CoreNetworkError(); throw new CoreNetworkError();
} }
@ -303,14 +306,18 @@ export class CoreSitesProvider {
errorDetails: Translate.instant('core.login.webservicesnotenabled'), errorDetails: Translate.instant('core.login.webservicesnotenabled'),
critical: true, critical: true,
}); });
} else if (!config.enablemobilewebservice) { }
if (!config.enablemobilewebservice) {
throw this.createCannotConnectLoginError(config.httpswwwroot || config.wwwroot, { throw this.createCannotConnectLoginError(config.httpswwwroot || config.wwwroot, {
supportConfig: new CoreUserGuestSupportConfig(config), supportConfig: new CoreUserGuestSupportConfig(config),
errorcode: 'mobileservicesnotenabled', errorcode: 'mobileservicesnotenabled',
errorDetails: Translate.instant('core.login.mobileservicesnotenabled'), errorDetails: Translate.instant('core.login.mobileservicesnotenabled'),
critical: true, critical: true,
}); });
} else if (config.maintenanceenabled) { }
if (config.maintenanceenabled) {
let message = Translate.instant('core.sitemaintenance'); let message = Translate.instant('core.sitemaintenance');
if (config.maintenancemessage) { if (config.maintenancemessage) {
message += config.maintenancemessage; message += config.maintenancemessage;
@ -497,39 +504,41 @@ export class CoreSitesProvider {
? Translate.instant('core.siteunavailablehelp', { site: this.currentSite?.siteUrl }) ? Translate.instant('core.siteunavailablehelp', { site: this.currentSite?.siteUrl })
: Translate.instant('core.sitenotfoundhelp'), : Translate.instant('core.sitenotfoundhelp'),
); );
} else { }
if (data.token !== undefined) {
return { token: data.token, siteUrl, privateToken: data.privatetoken };
} else {
if (data.error !== undefined) {
// We only allow one retry (to avoid loops).
if (!retry && data.errorcode == 'requirecorrectaccess') {
siteUrl = CoreUrlUtils.addOrRemoveWWW(siteUrl);
return this.getUserToken(siteUrl, username, password, service, true); if (data.token !== undefined) {
} else if (data.errorcode == 'missingparam') { return { token: data.token, siteUrl, privateToken: data.privatetoken };
// It seems the server didn't receive all required params, it could be due to a redirect. }
const redirect = await CoreUtils.checkRedirect(loginUrl);
if (redirect) { if (data.error === undefined) {
throw this.createCannotConnectLoginError(siteUrl, { throw new CoreError(Translate.instant('core.login.invalidaccount'));
supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), }
errorcode: 'sitehasredirect',
errorDetails: Translate.instant('core.login.sitehasredirect'),
});
}
}
throw this.createCannotConnectLoginError(siteUrl, { // We only allow one retry (to avoid loops).
supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), if (!retry && data.errorcode == 'requirecorrectaccess') {
errorcode: data.errorcode, siteUrl = CoreUrlUtils.addOrRemoveWWW(siteUrl);
errorDetails: data.error,
});
}
throw new CoreError(Translate.instant('core.login.invalidaccount')); return this.getUserToken(siteUrl, username, password, service, true);
}
if (data.errorcode == 'missingparam') {
// It seems the server didn't receive all required params, it could be due to a redirect.
const redirect = await CoreUtils.checkRedirect(loginUrl);
if (redirect) {
throw this.createCannotConnectLoginError(siteUrl, {
supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl),
errorcode: 'sitehasredirect',
errorDetails: Translate.instant('core.login.sitehasredirect'),
});
} }
} }
throw this.createCannotConnectLoginError(siteUrl, {
supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl),
errorcode: data.errorcode,
errorDetails: data.error,
});
} }
/** /**
@ -549,19 +558,19 @@ export class CoreSitesProvider {
login: boolean = true, login: boolean = true,
oauthId?: number, oauthId?: number,
): Promise<string> { ): Promise<string> {
if (typeof login != 'boolean') { if (typeof login !== 'boolean') {
login = true; login = true;
} }
// Create a "candidate" site to fetch the site info. // Create a "candidate" site to fetch the site info.
let candidateSite = CoreSitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken, undefined, undefined); let candidateSite = CoreSitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken);
let isNewSite = true; let isNewSite = true;
try { try {
const info = await candidateSite.fetchSiteInfo(); const info = await candidateSite.fetchSiteInfo();
const result = this.isValidMoodleVersion(info); const result = this.isValidMoodleVersion(info);
if (result != CoreSitesProvider.VALID_VERSION) { if (result !== CoreSitesProvider.VALID_VERSION) {
return this.treatInvalidAppVersion(result); return this.treatInvalidAppVersion(result);
} }
@ -1080,19 +1089,28 @@ export class CoreSitesProvider {
} }
throw new CoreError('No current site found.'); throw new CoreError('No current site found.');
} else if (this.currentSite && this.currentSite.getId() == siteId) { }
return this.currentSite;
} else if (this.sites[siteId] !== undefined) {
return this.sites[siteId];
} else {
// Retrieve and create the site.
try {
const data = await this.sitesTable.getOneByPrimaryKey({ id: siteId });
return this.addSiteFromSiteListEntry(data); if (this.currentSite && this.currentSite.getId() === siteId) {
} catch { return this.currentSite;
throw new CoreError('SiteId not found'); }
}
if (this.sites[siteId] !== undefined) {
return this.sites[siteId];
}
// Retrieve and create the site.
let record: SiteDBEntry;
try {
record = await this.sitesTable.getOneByPrimaryKey({ id: siteId });
} catch {
throw new CoreError('SiteId not found.');
}
try {
return await this.addSiteFromSiteListEntry(record);
} catch {
throw new CoreError('Site database installation or update failed.');
} }
} }
@ -1123,10 +1141,6 @@ export class CoreSitesProvider {
async getSiteByUrl(siteUrl: string): Promise<CoreSite> { async getSiteByUrl(siteUrl: string): Promise<CoreSite> {
const data = await this.sitesTable.getOne({ siteUrl }); const data = await this.sitesTable.getOne({ siteUrl });
if (this.sites[data.id] !== undefined) {
return this.sites[data.id];
}
return this.addSiteFromSiteListEntry(data); return this.addSiteFromSiteListEntry(data);
} }
@ -1148,16 +1162,20 @@ 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.
*/ */
addSiteFromSiteListEntry(entry: SiteDBEntry): Promise<CoreSite> { async addSiteFromSiteListEntry(entry: SiteDBEntry): Promise<CoreSite> {
if (this.sites[entry.id] !== undefined) {
return this.sites[entry.id];
}
// Parse info and config. // Parse info and config.
const site = this.makeSiteFromSiteListEntry(entry); const site = this.makeSiteFromSiteListEntry(entry);
return this.migrateSiteSchemas(site).then(() => { await this.migrateSiteSchemas(site);
// 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; // 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;
} }
/** /**
@ -1167,8 +1185,8 @@ export class CoreSitesProvider {
* @return Site. * @return Site.
*/ */
makeSiteFromSiteListEntry(entry: SiteDBEntry): CoreSite { makeSiteFromSiteListEntry(entry: SiteDBEntry): CoreSite {
const info = entry.info ? <CoreSiteInfo> CoreTextUtils.parseJSON(entry.info) : undefined; const info = entry.info ? CoreTextUtils.parseJSON<CoreSiteInfo>(entry.info) : undefined;
const config = entry.config ? <CoreSiteConfig> CoreTextUtils.parseJSON(entry.config) : undefined; const config = entry.config ? CoreTextUtils.parseJSON<CoreSiteConfig>(entry.config) : undefined;
const site = CoreSitesFactory.makeSite( const site = CoreSitesFactory.makeSite(
entry.id, entry.id,
@ -1321,13 +1339,13 @@ export class CoreSitesProvider {
async getSitesInstances(): Promise<CoreSite[]> { async getSitesInstances(): Promise<CoreSite[]> {
const siteIds = await this.getSitesIds(); const siteIds = await this.getSitesIds();
return Promise.all(siteIds.map(async (siteId) => await this.getSite(siteId))); return Promise.all(siteIds.map((siteId) => this.getSite(siteId)));
} }
/** /**
* Login the user in a site. * Login the user in a site.
* *
* @param siteid ID of the site the user is accessing. * @param siteId ID of the site the user is accessing.
* @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> {
@ -1479,7 +1497,7 @@ export class CoreSitesProvider {
/** /**
* Updates a site's info. * Updates a site's info.
* *
* @param siteid Site's ID. * @param siteId Site's ID.
* @return A promise resolved when the site is updated. * @return A promise resolved when the site is updated.
*/ */
async updateSiteInfo(siteId?: string): Promise<void> { async updateSiteInfo(siteId?: string): Promise<void> {
@ -1500,7 +1518,7 @@ export class CoreSitesProvider {
try { try {
config = await this.getSiteConfig(site); config = await this.getSiteConfig(site);
} catch (error) { } catch {
// Error getting config, keep the current one. // Error getting config, keep the current one.
} }
@ -1561,14 +1579,14 @@ export class CoreSitesProvider {
if (CoreUrlUtils.isAbsoluteURL(url)) { if (CoreUrlUtils.isAbsoluteURL(url)) {
// It has some protocol. Return empty array. // It has some protocol. Return empty array.
return []; return [];
} else {
// No protocol, probably a relative URL. Return current site.
if (this.currentSite) {
return [this.currentSite.getId()];
} else {
return [];
}
} }
// No protocol, probably a relative URL. Return current site.
if (this.currentSite) {
return [this.currentSite.getId()];
}
return [];
} }
try { try {
@ -1576,9 +1594,7 @@ export class CoreSitesProvider {
const ids: string[] = []; const ids: string[] = [];
await Promise.all(siteEntries.map(async (site) => { await Promise.all(siteEntries.map(async (site) => {
if (!this.sites[site.id]) { await this.addSiteFromSiteListEntry(site);
await this.addSiteFromSiteListEntry(site);
}
if (this.sites[site.id].containsUrl(url)) { if (this.sites[site.id].containsUrl(url)) {
if (!username || this.sites[site.id].getInfo()?.username === username) { if (!username || this.sites[site.id].getInfo()?.username === username) {
@ -1694,24 +1710,30 @@ export class CoreSitesProvider {
* @param site Site. * @param site Site.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
migrateSiteSchemas(site: CoreSite): Promise<void> { async migrateSiteSchemas(site: CoreSite): Promise<void> {
if (!site.id) { if (!site.id) {
return Promise.resolve(); return;
} }
const siteId = site.id; const siteId = site.id;
if (this.siteSchemasMigration[site.id] !== undefined) { if (this.siteSchemasMigration[siteId] !== undefined) {
return this.siteSchemasMigration[site.id]; return this.siteSchemasMigration[siteId];
} }
this.logger.debug(`Migrating all schemas of ${site.id}`); if (this.sitesMigrated[siteId] !== undefined) {
throw new CoreError('Site already migrated.');
}
this.sitesMigrated[siteId] = true;
this.logger.debug(`Migrating all schemas of ${siteId}`);
// First create tables not registerd with name/version. // First create tables not registerd with name/version.
const promise = site.getDb().createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA) const promise = site.getDb().createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA)
.then(() => this.applySiteSchemas(site, this.siteSchemas)); .then(() => this.applySiteSchemas(site, this.siteSchemas));
this.siteSchemasMigration[site.id] = promise; this.siteSchemasMigration[siteId] = promise;
return promise.finally(() => { return promise.finally(() => {
delete this.siteSchemasMigration[siteId]; delete this.siteSchemasMigration[siteId];
@ -1769,6 +1791,9 @@ export class CoreSitesProvider {
if (schema.tables) { if (schema.tables) {
await db.createTablesFromSchema(schema.tables); await db.createTablesFromSchema(schema.tables);
} }
if (schema.install && oldVersion == 0) {
await schema.install(db, site.id);
}
if (schema.migrate && oldVersion > 0) { if (schema.migrate && oldVersion > 0) {
await schema.migrate(db, oldVersion, site.id); await schema.migrate(db, oldVersion, site.id);
} }
@ -2032,7 +2057,7 @@ export type CoreSiteSchema = {
/** /**
* Migrates the schema in a site to the latest version. * Migrates the schema in a site to the latest version.
* *
* Called when installing and upgrading the schema, after creating the defined tables. * Called when upgrading the schema, after creating the defined tables.
* *
* @param db Site database. * @param db Site database.
* @param oldVersion Old version of the schema or 0 if not installed. * @param oldVersion Old version of the schema or 0 if not installed.
@ -2040,6 +2065,17 @@ export type CoreSiteSchema = {
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
migrate?(db: SQLiteDB, oldVersion: number, siteId: string): Promise<void> | void; migrate?(db: SQLiteDB, oldVersion: number, siteId: string): Promise<void> | void;
/**
* Make changes to install the schema in a site.
*
* Called when installing the schema, after creating the defined tables.
*
* @param db Site database.
* @param siteId Site Id to migrate.
* @return Promise resolved when done.
*/
install?(db: SQLiteDB, siteId: string): Promise<void> | void;
}; };
/** /**