Merge pull request #3110 from NoelDeMartin/MOBILE-3981
MOBILE-3981: Optimize tables used during startupmain
commit
c91f24245b
|
@ -17,7 +17,13 @@ import { asyncInstance } from '@/core/utils/async-instance';
|
||||||
import { SQLiteDB, SQLiteDBRecordValues } from '@classes/sqlitedb';
|
import { SQLiteDB, SQLiteDBRecordValues } from '@classes/sqlitedb';
|
||||||
import { CoreConfig, CoreConfigProvider } from '@services/config';
|
import { CoreConfig, CoreConfigProvider } from '@services/config';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreDatabaseReducer, CoreDatabaseTable, CoreDatabaseConditions, GetDBRecordPrimaryKey } from './database-table';
|
import {
|
||||||
|
CoreDatabaseReducer,
|
||||||
|
CoreDatabaseTable,
|
||||||
|
CoreDatabaseConditions,
|
||||||
|
GetDBRecordPrimaryKey,
|
||||||
|
CoreDatabaseQueryOptions,
|
||||||
|
} 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';
|
||||||
import { CoreLazyDatabaseTable } from './lazy-database-table';
|
import { CoreLazyDatabaseTable } from './lazy-database-table';
|
||||||
|
@ -67,15 +73,25 @@ export class CoreDatabaseTableProxy<
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async getMany(conditions?: Partial<DBRecord>): Promise<DBRecord[]> {
|
async getMany(conditions?: Partial<DBRecord>, options?: Partial<CoreDatabaseQueryOptions<DBRecord>>): Promise<DBRecord[]> {
|
||||||
return this.target.getMany(conditions);
|
return this.target.getMany(conditions, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async getOne(conditions: Partial<DBRecord>): Promise<DBRecord> {
|
getManyWhere(conditions: CoreDatabaseConditions<DBRecord>): Promise<DBRecord[]> {
|
||||||
return this.target.getOne(conditions);
|
return this.target.getManyWhere(conditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async getOne(
|
||||||
|
conditions?: Partial<DBRecord>,
|
||||||
|
options?: Partial<Omit<CoreDatabaseQueryOptions<DBRecord>, 'offset' | 'limit'>>,
|
||||||
|
): Promise<DBRecord> {
|
||||||
|
return this.target.getOne(conditions, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,6 +108,20 @@ export class CoreDatabaseTableProxy<
|
||||||
return this.target.reduce<T>(reducer, conditions);
|
return this.target.reduce<T>(reducer, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
hasAny(conditions?: Partial<DBRecord>): Promise<boolean> {
|
||||||
|
return this.target.hasAny(conditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
count(conditions?: Partial<DBRecord>): Promise<number> {
|
||||||
|
return this.target.count(conditions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreError } from '@classes/errors/error';
|
||||||
import { SQLiteDB, SQLiteDBRecordValue, SQLiteDBRecordValues } from '@classes/sqlitedb';
|
import { SQLiteDB, SQLiteDBRecordValue, SQLiteDBRecordValues } from '@classes/sqlitedb';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,24 +79,59 @@ export class CoreDatabaseTable<
|
||||||
* Get records matching the given conditions.
|
* Get records matching the given conditions.
|
||||||
*
|
*
|
||||||
* @param conditions Matching conditions. If this argument is missing, all records in the table will be returned.
|
* @param conditions Matching conditions. If this argument is missing, all records in the table will be returned.
|
||||||
|
* @param options Query options.
|
||||||
* @returns Database records.
|
* @returns Database records.
|
||||||
*/
|
*/
|
||||||
getMany(conditions?: Partial<DBRecord>): Promise<DBRecord[]> {
|
getMany(conditions?: Partial<DBRecord>, options?: Partial<CoreDatabaseQueryOptions<DBRecord>>): Promise<DBRecord[]> {
|
||||||
return conditions
|
if (!conditions && !options) {
|
||||||
? this.database.getRecords(this.tableName, conditions)
|
return this.database.getAllRecords(this.tableName);
|
||||||
: this.database.getAllRecords(this.tableName);
|
}
|
||||||
|
|
||||||
|
const sorting = options?.sorting
|
||||||
|
&& this.normalizedSorting(options.sorting).map(([column, direction]) => `${column} ${direction}`).join(', ');
|
||||||
|
|
||||||
|
return this.database.getRecords(this.tableName, conditions, sorting, '*', options?.offset, options?.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get records matching the given conditions.
|
||||||
|
*
|
||||||
|
* This method should be used when it's necessary to apply complex conditions; the simple `getMany`
|
||||||
|
* method should be favored otherwise for better performance.
|
||||||
|
*
|
||||||
|
* @param conditions Matching conditions in SQL and JavaScript.
|
||||||
|
*/
|
||||||
|
getManyWhere(conditions: CoreDatabaseConditions<DBRecord>): Promise<DBRecord[]> {
|
||||||
|
return this.database.getRecordsSelect(this.tableName, conditions.sql, conditions.sqlParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find one record matching the given conditions.
|
* Find one record matching the given conditions.
|
||||||
*
|
*
|
||||||
* @param conditions Matching conditions.
|
* @param conditions Matching conditions.
|
||||||
|
* @param options Result options.
|
||||||
* @returns Database record.
|
* @returns Database record.
|
||||||
*/
|
*/
|
||||||
getOne(conditions: Partial<DBRecord>): Promise<DBRecord> {
|
async getOne(
|
||||||
|
conditions?: Partial<DBRecord>,
|
||||||
|
options?: Partial<Omit<CoreDatabaseQueryOptions<DBRecord>, 'offset' | 'limit'>>,
|
||||||
|
): Promise<DBRecord> {
|
||||||
|
if (!options) {
|
||||||
return this.database.getRecord<DBRecord>(this.tableName, conditions);
|
return this.database.getRecord<DBRecord>(this.tableName, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const records = await this.getMany(conditions, {
|
||||||
|
...options,
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (records.length === 0) {
|
||||||
|
throw new CoreError('No records found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return records[0];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find one record by its primary key.
|
* Find one record by its primary key.
|
||||||
*
|
*
|
||||||
|
@ -121,6 +157,43 @@ export class CoreDatabaseTable<
|
||||||
) as unknown as Promise<T>;
|
) as unknown as Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the table is empty or not.
|
||||||
|
*
|
||||||
|
* @returns Whether the table is empty or not.
|
||||||
|
*/
|
||||||
|
isEmpty(): Promise<boolean> {
|
||||||
|
return this.hasAny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the table has any record matching the given conditions.
|
||||||
|
*
|
||||||
|
* @param conditions Matching conditions. If this argument is missing, this method will return whether the table
|
||||||
|
* is empty or not.
|
||||||
|
* @returns Whether the table contains any records matching the given conditions.
|
||||||
|
*/
|
||||||
|
async hasAny(conditions?: Partial<DBRecord>): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await this.getOne(conditions);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
// Couldn't get a single record.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count records in table.
|
||||||
|
*
|
||||||
|
* @param conditions Matching conditions.
|
||||||
|
* @returns Number of records matching the given conditions.
|
||||||
|
*/
|
||||||
|
count(conditions?: Partial<DBRecord>): Promise<number> {
|
||||||
|
return this.database.countRecords(this.tableName, conditions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert a new record.
|
* Insert a new record.
|
||||||
*
|
*
|
||||||
|
@ -208,6 +281,59 @@ export class CoreDatabaseTable<
|
||||||
return !Object.entries(conditions).some(([column, value]) => record[column] !== value);
|
return !Object.entries(conditions).some(([column, value]) => record[column] !== value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort a list of records with the given order. This method mutates the input array.
|
||||||
|
*
|
||||||
|
* @param records Array of records to sort.
|
||||||
|
* @param sorting Sorting conditions.
|
||||||
|
* @returns Sorted array. This will be the same reference that was given as an argument.
|
||||||
|
*/
|
||||||
|
protected sortRecords(records: DBRecord[], sorting: CoreDatabaseSorting<DBRecord>): DBRecord[] {
|
||||||
|
const columnsSorting = this.normalizedSorting(sorting);
|
||||||
|
|
||||||
|
records.sort((a, b) => {
|
||||||
|
for (const [column, direction] of columnsSorting) {
|
||||||
|
const aValue = a[column];
|
||||||
|
const bValue = b[column];
|
||||||
|
|
||||||
|
if (aValue > bValue) {
|
||||||
|
return direction === 'desc' ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aValue < bValue) {
|
||||||
|
return direction === 'desc' ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a normalized array of sorting conditions.
|
||||||
|
*
|
||||||
|
* @param sorting Sorting conditions.
|
||||||
|
* @returns Normalized sorting conditions.
|
||||||
|
*/
|
||||||
|
protected normalizedSorting(sorting: CoreDatabaseSorting<DBRecord>): [keyof DBRecord, 'asc' | 'desc'][] {
|
||||||
|
const sortingArray = Array.isArray(sorting) ? sorting : [sorting];
|
||||||
|
|
||||||
|
return sortingArray.reduce((normalizedSorting, columnSorting) => {
|
||||||
|
normalizedSorting.push(
|
||||||
|
typeof columnSorting === 'object'
|
||||||
|
? [
|
||||||
|
Object.keys(columnSorting)[0] as keyof DBRecord,
|
||||||
|
Object.values(columnSorting)[0] as 'asc' | 'desc',
|
||||||
|
]
|
||||||
|
: [columnSorting, 'asc'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return normalizedSorting;
|
||||||
|
}, [] as [keyof DBRecord, 'asc' | 'desc'][]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -238,3 +364,37 @@ export type CoreDatabaseConditions<DBRecord> = {
|
||||||
sqlParams?: SQLiteDBRecordValue[];
|
sqlParams?: SQLiteDBRecordValue[];
|
||||||
js: (record: DBRecord) => boolean;
|
js: (record: DBRecord) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorting conditions for a single column.
|
||||||
|
*
|
||||||
|
* This type will accept an object that defines sorting conditions for a single column, but not more.
|
||||||
|
* For example, `{id: 'desc'}` and `{name: 'asc'}` would be acceptend values, but `{id: 'desc', name: 'asc'}` wouldn't.
|
||||||
|
*
|
||||||
|
* @see https://stackoverflow.com/questions/57571664/typescript-type-for-an-object-with-only-one-key-no-union-type-allowed-as-a-key
|
||||||
|
*/
|
||||||
|
export type CoreDatabaseColumnSorting<DBRecordColumn extends string | symbol | number> = {
|
||||||
|
[Column in DBRecordColumn]:
|
||||||
|
(Record<Column, 'asc' | 'desc'> & Partial<Record<Exclude<DBRecordColumn, Column>, never>>) extends infer ColumnSorting
|
||||||
|
? { [Column in keyof ColumnSorting]: ColumnSorting[Column] }
|
||||||
|
: never;
|
||||||
|
}[DBRecordColumn];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorting conditions to apply to query results.
|
||||||
|
*
|
||||||
|
* Columns will be sorted in ascending order by default.
|
||||||
|
*/
|
||||||
|
export type CoreDatabaseSorting<DBRecord> =
|
||||||
|
keyof DBRecord |
|
||||||
|
CoreDatabaseColumnSorting<keyof DBRecord> |
|
||||||
|
Array<keyof DBRecord | CoreDatabaseColumnSorting<keyof DBRecord>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options to configure query results.
|
||||||
|
*/
|
||||||
|
export type CoreDatabaseQueryOptions<DBRecord> = {
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
sorting: CoreDatabaseSorting<DBRecord>;
|
||||||
|
};
|
||||||
|
|
|
@ -14,7 +14,13 @@
|
||||||
|
|
||||||
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
|
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreDatabaseTable, CoreDatabaseConditions, GetDBRecordPrimaryKey, CoreDatabaseReducer } from './database-table';
|
import {
|
||||||
|
CoreDatabaseTable,
|
||||||
|
CoreDatabaseConditions,
|
||||||
|
GetDBRecordPrimaryKey,
|
||||||
|
CoreDatabaseReducer,
|
||||||
|
CoreDatabaseQueryOptions,
|
||||||
|
} from './database-table';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database table proxy used to debug runtime operations.
|
* Database table proxy used to debug runtime operations.
|
||||||
|
@ -41,7 +47,7 @@ export class CoreDebugDatabaseTable<
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
initialize(): Promise<void> {
|
initialize(): Promise<void> {
|
||||||
this.logger.log('initialize');
|
this.logger.log('initialize', this.target);
|
||||||
|
|
||||||
return this.target.initialize();
|
return this.target.initialize();
|
||||||
}
|
}
|
||||||
|
@ -58,19 +64,31 @@ export class CoreDebugDatabaseTable<
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
getMany(conditions?: Partial<DBRecord>): Promise<DBRecord[]> {
|
getMany(conditions?: Partial<DBRecord>, options?: Partial<CoreDatabaseQueryOptions<DBRecord>>): Promise<DBRecord[]> {
|
||||||
this.logger.log('getMany', conditions);
|
this.logger.log('getMany', conditions, options);
|
||||||
|
|
||||||
return this.target.getMany(conditions);
|
return this.target.getMany(conditions, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
getOne(conditions: Partial<DBRecord>): Promise<DBRecord> {
|
getManyWhere(conditions: CoreDatabaseConditions<DBRecord>): Promise<DBRecord[]> {
|
||||||
this.logger.log('getOne', conditions);
|
this.logger.log('getManyWhere', conditions);
|
||||||
|
|
||||||
return this.target.getOne(conditions);
|
return this.target.getManyWhere(conditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getOne(
|
||||||
|
conditions?: Partial<DBRecord>,
|
||||||
|
options?: Partial<Omit<CoreDatabaseQueryOptions<DBRecord>, 'offset' | 'limit'>>,
|
||||||
|
): Promise<DBRecord> {
|
||||||
|
this.logger.log('getOne', conditions, options);
|
||||||
|
|
||||||
|
return this.target.getOne(conditions, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,6 +109,24 @@ export class CoreDebugDatabaseTable<
|
||||||
return this.target.reduce<T>(reducer, conditions);
|
return this.target.reduce<T>(reducer, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
hasAny(conditions?: Partial<DBRecord>): Promise<boolean> {
|
||||||
|
this.logger.log('hasAny', conditions);
|
||||||
|
|
||||||
|
return this.target.hasAny(conditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
count(conditions?: Partial<DBRecord>): Promise<number> {
|
||||||
|
this.logger.log('count', conditions);
|
||||||
|
|
||||||
|
return this.target.count(conditions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,7 +14,13 @@
|
||||||
|
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
|
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
|
||||||
import { CoreDatabaseTable, CoreDatabaseConditions, GetDBRecordPrimaryKey, CoreDatabaseReducer } from './database-table';
|
import {
|
||||||
|
CoreDatabaseTable,
|
||||||
|
CoreDatabaseConditions,
|
||||||
|
GetDBRecordPrimaryKey,
|
||||||
|
CoreDatabaseReducer,
|
||||||
|
CoreDatabaseQueryOptions,
|
||||||
|
} from './database-table';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper used to improve performance by caching all the records for faster read operations.
|
* Wrapper used to improve performance by caching all the records for faster read operations.
|
||||||
|
@ -48,21 +54,44 @@ export class CoreEagerDatabaseTable<
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async getMany(conditions?: Partial<DBRecord>): Promise<DBRecord[]> {
|
async getMany(conditions?: Partial<DBRecord>, options?: Partial<CoreDatabaseQueryOptions<DBRecord>>): Promise<DBRecord[]> {
|
||||||
const records = Object.values(this.records);
|
const records = Object.values(this.records);
|
||||||
|
const filteredRecords = conditions
|
||||||
return conditions
|
|
||||||
? records.filter(record => this.recordMatches(record, conditions))
|
? records.filter(record => this.recordMatches(record, conditions))
|
||||||
: records;
|
: records;
|
||||||
|
|
||||||
|
if (options?.sorting) {
|
||||||
|
this.sortRecords(filteredRecords, options.sorting);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredRecords.slice(options?.offset ?? 0, options?.limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async getOne(conditions: Partial<DBRecord>): Promise<DBRecord> {
|
async getManyWhere(conditions: CoreDatabaseConditions<DBRecord>): Promise<DBRecord[]> {
|
||||||
const record = Object.values(this.records).find(record => this.recordMatches(record, conditions)) ?? null;
|
return Object.values(this.records).filter(record => conditions.js(record));
|
||||||
|
}
|
||||||
|
|
||||||
if (record === null) {
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async getOne(
|
||||||
|
conditions?: Partial<DBRecord>,
|
||||||
|
options?: Partial<Omit<CoreDatabaseQueryOptions<DBRecord>, 'offset' | 'limit'>>,
|
||||||
|
): Promise<DBRecord> {
|
||||||
|
let record: DBRecord | undefined;
|
||||||
|
|
||||||
|
if (options?.sorting) {
|
||||||
|
record = this.getMany(conditions, { ...options, limit: 1 })[0];
|
||||||
|
} else if (conditions) {
|
||||||
|
record = Object.values(this.records).find(record => this.recordMatches(record, conditions));
|
||||||
|
} else {
|
||||||
|
record = Object.values(this.records)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
throw new CoreError('No records found.');
|
throw new CoreError('No records found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +123,24 @@ export class CoreEagerDatabaseTable<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async hasAny(conditions?: Partial<DBRecord>): Promise<boolean> {
|
||||||
|
return conditions
|
||||||
|
? Object.values(this.records).some(record => this.recordMatches(record, conditions))
|
||||||
|
: Object.values(this.records).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async count(conditions?: Partial<DBRecord>): Promise<number> {
|
||||||
|
return conditions
|
||||||
|
? Object.values(this.records).filter(record => this.recordMatches(record, conditions)).length
|
||||||
|
: Object.values(this.records).length;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
|
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
|
||||||
import { CoreDatabaseTable, CoreDatabaseConditions, GetDBRecordPrimaryKey } from './database-table';
|
import { CoreDatabaseTable, CoreDatabaseConditions, GetDBRecordPrimaryKey, CoreDatabaseQueryOptions } from './database-table';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper used to improve performance by caching records that are used often for faster read operations.
|
* Wrapper used to improve performance by caching records that are used often for faster read operations.
|
||||||
|
@ -33,15 +33,13 @@ export class CoreLazyDatabaseTable<
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async getOne(conditions: Partial<DBRecord>): Promise<DBRecord> {
|
async getOne(
|
||||||
let record: DBRecord | null =
|
conditions?: Partial<DBRecord>,
|
||||||
Object.values(this.records).find(record => record && this.recordMatches(record, conditions)) ?? null;
|
options?: Partial<Omit<CoreDatabaseQueryOptions<DBRecord>, 'offset' | 'limit'>>,
|
||||||
|
): Promise<DBRecord> {
|
||||||
if (!record) {
|
const record = await super.getOne(conditions, options);
|
||||||
record = await super.getOne(conditions);
|
|
||||||
|
|
||||||
this.records[this.serializePrimaryKey(this.getPrimaryKeyFromRecord(record))] = record;
|
this.records[this.serializePrimaryKey(this.getPrimaryKeyFromRecord(record))] = record;
|
||||||
}
|
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
@ -75,6 +73,21 @@ export class CoreLazyDatabaseTable<
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async hasAny(conditions?: Partial<DBRecord>): Promise<boolean> {
|
||||||
|
const hasAnyMatching = Object
|
||||||
|
.values(this.records)
|
||||||
|
.some(record => record !== null && (!conditions || this.recordMatches(record, conditions)));
|
||||||
|
|
||||||
|
if (hasAnyMatching) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.hasAny(conditions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -108,6 +108,7 @@ export class CoreSite {
|
||||||
protected logger: CoreLogger;
|
protected logger: CoreLogger;
|
||||||
protected db?: SQLiteDB;
|
protected db?: SQLiteDB;
|
||||||
protected cacheTable: AsyncInstance<CoreDatabaseTable<CoreSiteWSCacheRecord>>;
|
protected cacheTable: AsyncInstance<CoreDatabaseTable<CoreSiteWSCacheRecord>>;
|
||||||
|
protected configTable: AsyncInstance<CoreDatabaseTable<CoreSiteConfigDBRecord, 'name'>>;
|
||||||
protected cleanUnicode = false;
|
protected cleanUnicode = false;
|
||||||
protected lastAutoLogin = 0;
|
protected lastAutoLogin = 0;
|
||||||
protected offlineDisabled = false;
|
protected offlineDisabled = false;
|
||||||
|
@ -146,6 +147,12 @@ export class CoreSite {
|
||||||
database: this.getDb(),
|
database: this.getDb(),
|
||||||
config: { cachingStrategy: CoreDatabaseCachingStrategy.None },
|
config: { cachingStrategy: CoreDatabaseCachingStrategy.None },
|
||||||
}));
|
}));
|
||||||
|
this.configTable = asyncInstance(() => CoreSites.getSiteTable(CoreSite.CONFIG_TABLE, {
|
||||||
|
siteId: this.getId(),
|
||||||
|
database: this.getDb(),
|
||||||
|
config: { cachingStrategy: CoreDatabaseCachingStrategy.Eager },
|
||||||
|
primaryKeyColumns: ['name'],
|
||||||
|
}));
|
||||||
this.setInfo(infos);
|
this.setInfo(infos);
|
||||||
this.calculateOfflineDisabled();
|
this.calculateOfflineDisabled();
|
||||||
|
|
||||||
|
@ -1825,7 +1832,7 @@ export class CoreSite {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async deleteSiteConfig(name: string): Promise<void> {
|
async deleteSiteConfig(name: string): Promise<void> {
|
||||||
await this.getDb().deleteRecords(CoreSite.CONFIG_TABLE, { name });
|
await this.configTable.deleteByPrimaryKey({ name });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1837,7 +1844,7 @@ export class CoreSite {
|
||||||
*/
|
*/
|
||||||
async getLocalSiteConfig<T extends number | string>(name: string, defaultValue?: T): Promise<T> {
|
async getLocalSiteConfig<T extends number | string>(name: string, defaultValue?: T): Promise<T> {
|
||||||
try {
|
try {
|
||||||
const entry = await this.getDb().getRecord<CoreSiteConfigDBRecord>(CoreSite.CONFIG_TABLE, { name });
|
const entry = await this.configTable.getOneByPrimaryKey({ name });
|
||||||
|
|
||||||
return <T> entry.value;
|
return <T> entry.value;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -1857,7 +1864,7 @@ export class CoreSite {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async setLocalSiteConfig(name: string, value: number | string): Promise<void> {
|
async setLocalSiteConfig(name: string, value: number | string): Promise<void> {
|
||||||
await this.getDb().insertRecord(CoreSite.CONFIG_TABLE, { name, value });
|
await this.configTable.insert({ name, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -136,6 +136,50 @@ export interface SQLiteDBForeignKeySchema {
|
||||||
*/
|
*/
|
||||||
export class SQLiteDB {
|
export class SQLiteDB {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs 'IN()' or '=' sql fragment
|
||||||
|
*
|
||||||
|
* @param items A single value or array of values for the expression. It doesn't accept objects.
|
||||||
|
* @param equal True means we want to equate to the constructed expression.
|
||||||
|
* @param onEmptyItems This defines the behavior when the array of items provided is empty. Defaults to false,
|
||||||
|
* meaning return empty. Other values will become part of the returned SQL fragment.
|
||||||
|
* @return A list containing the constructed sql fragment and an array of parameters.
|
||||||
|
*/
|
||||||
|
static getInOrEqual(
|
||||||
|
items: SQLiteDBRecordValue | SQLiteDBRecordValue[],
|
||||||
|
equal: boolean = true,
|
||||||
|
onEmptyItems?: SQLiteDBRecordValue | null,
|
||||||
|
): SQLiteDBQueryParams {
|
||||||
|
let sql = '';
|
||||||
|
let params: SQLiteDBRecordValue[];
|
||||||
|
|
||||||
|
// Default behavior, return empty data on empty array.
|
||||||
|
if (Array.isArray(items) && !items.length && onEmptyItems === undefined) {
|
||||||
|
return { sql: '', params: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle onEmptyItems on empty array of items.
|
||||||
|
if (Array.isArray(items) && !items.length) {
|
||||||
|
if (onEmptyItems === null) { // Special case, NULL value.
|
||||||
|
sql = equal ? ' IS NULL' : ' IS NOT NULL';
|
||||||
|
|
||||||
|
return { sql, params: [] };
|
||||||
|
} else {
|
||||||
|
items = [onEmptyItems as SQLiteDBRecordValue]; // Rest of cases, prepare items for processing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(items) || items.length == 1) {
|
||||||
|
sql = equal ? '= ?' : '<> ?';
|
||||||
|
params = Array.isArray(items) ? items : [items];
|
||||||
|
} else {
|
||||||
|
sql = (equal ? '' : 'NOT ') + 'IN (' + ',?'.repeat(items.length).substring(1) + ')';
|
||||||
|
params = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sql, params };
|
||||||
|
}
|
||||||
|
|
||||||
db?: SQLiteObject;
|
db?: SQLiteObject;
|
||||||
promise!: Promise<void>;
|
promise!: Promise<void>;
|
||||||
|
|
||||||
|
@ -564,50 +608,6 @@ export class SQLiteDB {
|
||||||
return record[Object.keys(record)[0]];
|
return record[Object.keys(record)[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs 'IN()' or '=' sql fragment
|
|
||||||
*
|
|
||||||
* @param items A single value or array of values for the expression. It doesn't accept objects.
|
|
||||||
* @param equal True means we want to equate to the constructed expression.
|
|
||||||
* @param onEmptyItems This defines the behavior when the array of items provided is empty. Defaults to false,
|
|
||||||
* meaning return empty. Other values will become part of the returned SQL fragment.
|
|
||||||
* @return A list containing the constructed sql fragment and an array of parameters.
|
|
||||||
*/
|
|
||||||
getInOrEqual(
|
|
||||||
items: SQLiteDBRecordValue | SQLiteDBRecordValue[],
|
|
||||||
equal: boolean = true,
|
|
||||||
onEmptyItems?: SQLiteDBRecordValue | null,
|
|
||||||
): SQLiteDBQueryParams {
|
|
||||||
let sql = '';
|
|
||||||
let params: SQLiteDBRecordValue[];
|
|
||||||
|
|
||||||
// Default behavior, return empty data on empty array.
|
|
||||||
if (Array.isArray(items) && !items.length && onEmptyItems === undefined) {
|
|
||||||
return { sql: '', params: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle onEmptyItems on empty array of items.
|
|
||||||
if (Array.isArray(items) && !items.length) {
|
|
||||||
if (onEmptyItems === null || onEmptyItems === undefined) { // Special case, NULL value.
|
|
||||||
sql = equal ? ' IS NULL' : ' IS NOT NULL';
|
|
||||||
|
|
||||||
return { sql, params: [] };
|
|
||||||
} else {
|
|
||||||
items = [onEmptyItems]; // Rest of cases, prepare items for processing.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(items) || items.length == 1) {
|
|
||||||
sql = equal ? '= ?' : '<> ?';
|
|
||||||
params = Array.isArray(items) ? items : [items];
|
|
||||||
} else {
|
|
||||||
sql = (equal ? '' : 'NOT ') + 'IN (' + ',?'.repeat(items.length).substring(1) + ')';
|
|
||||||
params = items;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { sql, params };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the database name.
|
* Get the database name.
|
||||||
*
|
*
|
||||||
|
|
|
@ -13,20 +13,20 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { mock, mockSingleton } from '@/testing/utils';
|
import { mock, mockSingleton } from '@/testing/utils';
|
||||||
import { CoreDatabaseTable } from '@classes/database/database-table';
|
import { CoreDatabaseSorting, CoreDatabaseTable } from '@classes/database/database-table';
|
||||||
import {
|
import {
|
||||||
CoreDatabaseCachingStrategy,
|
CoreDatabaseCachingStrategy,
|
||||||
CoreDatabaseConfiguration,
|
CoreDatabaseConfiguration,
|
||||||
CoreDatabaseTableProxy,
|
CoreDatabaseTableProxy,
|
||||||
} from '@classes/database/database-table-proxy';
|
} from '@classes/database/database-table-proxy';
|
||||||
import { SQLiteDB, SQLiteDBRecordValues } from '@classes/sqlitedb';
|
import { SQLiteDB } from '@classes/sqlitedb';
|
||||||
import { CoreConfig } from '@services/config';
|
import { CoreConfig } from '@services/config';
|
||||||
|
|
||||||
interface User extends SQLiteDBRecordValues {
|
type User = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
surname: string;
|
surname: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
function userMatches(user: User, conditions: Partial<User>) {
|
function userMatches(user: User, conditions: Partial<User>) {
|
||||||
return !Object.entries(conditions).some(([column, value]) => user[column] !== value);
|
return !Object.entries(conditions).some(([column, value]) => user[column] !== value);
|
||||||
|
@ -45,7 +45,7 @@ function prepareStubs(config: Partial<CoreDatabaseConfiguration> = {}): [User[],
|
||||||
return record as unknown as T;
|
return record as unknown as T;
|
||||||
},
|
},
|
||||||
getRecords: async <T>(_, conditions) => records.filter(record => userMatches(record, conditions)) as unknown as T[],
|
getRecords: async <T>(_, conditions) => records.filter(record => userMatches(record, conditions)) as unknown as T[],
|
||||||
getAllRecords: async <T>() => records as unknown as T[],
|
getAllRecords: async <T>() => records.slice(0) as unknown as T[],
|
||||||
deleteRecords: async (_, conditions) => {
|
deleteRecords: async (_, conditions) => {
|
||||||
const usersToDelete: User[] = [];
|
const usersToDelete: User[] = [];
|
||||||
|
|
||||||
|
@ -81,10 +81,10 @@ async function testFindItems(records: User[], table: CoreDatabaseTable<User>) {
|
||||||
|
|
||||||
await table.initialize();
|
await table.initialize();
|
||||||
|
|
||||||
await expect(table.getOneByPrimaryKey({ id: 1 })).resolves.toEqual(john);
|
|
||||||
await expect(table.getOneByPrimaryKey({ id: 2 })).resolves.toEqual(amy);
|
|
||||||
await expect(table.getOne({ surname: 'Doe', name: 'John' })).resolves.toEqual(john);
|
await expect(table.getOne({ surname: 'Doe', name: 'John' })).resolves.toEqual(john);
|
||||||
await expect(table.getOne({ surname: 'Doe', name: 'Amy' })).resolves.toEqual(amy);
|
await expect(table.getOne({ surname: 'Doe', name: 'Amy' })).resolves.toEqual(amy);
|
||||||
|
await expect(table.getOneByPrimaryKey({ id: 1 })).resolves.toEqual(john);
|
||||||
|
await expect(table.getOneByPrimaryKey({ id: 2 })).resolves.toEqual(amy);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testInsertItems(records: User[], database: SQLiteDB, table: CoreDatabaseTable<User>) {
|
async function testInsertItems(records: User[], database: SQLiteDB, table: CoreDatabaseTable<User>) {
|
||||||
|
@ -165,6 +165,32 @@ describe('CoreDatabaseTable with eager caching', () => {
|
||||||
expect(database.getRecord).not.toHaveBeenCalled();
|
expect(database.getRecord).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sorts items', async () => {
|
||||||
|
// Arrange.
|
||||||
|
const john = { id: 1, name: 'John', surname: 'Doe' };
|
||||||
|
const amy = { id: 2, name: 'Amy', surname: 'Doe' };
|
||||||
|
const jane = { id: 3, name: 'Jane', surname: 'Smith' };
|
||||||
|
const expectSorting = async (sorting: CoreDatabaseSorting<User>, expectedResults: User[]) => {
|
||||||
|
const results = await table.getMany({}, { sorting });
|
||||||
|
|
||||||
|
expect(results).toEqual(expectedResults);
|
||||||
|
};
|
||||||
|
|
||||||
|
records.push(john);
|
||||||
|
records.push(amy);
|
||||||
|
records.push(jane);
|
||||||
|
|
||||||
|
await table.initialize();
|
||||||
|
|
||||||
|
// Act & Assert.
|
||||||
|
await expectSorting('name', [amy, jane, john]);
|
||||||
|
await expectSorting('surname', [john, amy, jane]);
|
||||||
|
await expectSorting({ name: 'desc' }, [john, jane, amy]);
|
||||||
|
await expectSorting({ surname: 'desc' }, [jane, john, amy]);
|
||||||
|
await expectSorting(['name', { surname: 'desc' }], [amy, jane, john]);
|
||||||
|
await expectSorting([{ surname: 'desc' }, 'name'], [jane, amy, john]);
|
||||||
|
});
|
||||||
|
|
||||||
it('inserts items', () => testInsertItems(records, database, table));
|
it('inserts items', () => testInsertItems(records, database, table));
|
||||||
it('deletes items', () => testDeleteItems(records, database, table));
|
it('deletes items', () => testDeleteItems(records, database, table));
|
||||||
it('deletes items by primary key', () => testDeleteItemsByPrimaryKey(records, database, table));
|
it('deletes items by primary key', () => testDeleteItemsByPrimaryKey(records, database, table));
|
||||||
|
|
|
@ -45,6 +45,10 @@ import { CoreCourseAutoSyncData, CoreCourseSyncProvider } from './sync';
|
||||||
import { CoreTagItem } from '@features/tag/services/tag';
|
import { CoreTagItem } from '@features/tag/services/tag';
|
||||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||||
import { CoreCourseModuleDelegate } from './module-delegate';
|
import { CoreCourseModuleDelegate } from './module-delegate';
|
||||||
|
import { lazyMap, LazyMap } from '@/core/utils/lazy-map';
|
||||||
|
import { asyncInstance, AsyncInstance } from '@/core/utils/async-instance';
|
||||||
|
import { CoreDatabaseTable } from '@classes/database/database-table';
|
||||||
|
import { CoreDatabaseCachingStrategy } from '@classes/database/database-table-proxy';
|
||||||
|
|
||||||
const ROOT_CACHE_KEY = 'mmCourse:';
|
const ROOT_CACHE_KEY = 'mmCourse:';
|
||||||
|
|
||||||
|
@ -140,9 +144,18 @@ export class CoreCourseProvider {
|
||||||
];
|
];
|
||||||
|
|
||||||
protected logger: CoreLogger;
|
protected logger: CoreLogger;
|
||||||
|
protected statusTables: LazyMap<AsyncInstance<CoreDatabaseTable<CoreCourseStatusDBRecord>>>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.logger = CoreLogger.getInstance('CoreCourseProvider');
|
this.logger = CoreLogger.getInstance('CoreCourseProvider');
|
||||||
|
this.statusTables = lazyMap(
|
||||||
|
siteId => asyncInstance(
|
||||||
|
() => CoreSites.getSiteTable(COURSE_STATUS_TABLE, {
|
||||||
|
siteId,
|
||||||
|
config: { cachingStrategy: CoreDatabaseCachingStrategy.Eager },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -221,7 +234,7 @@ export class CoreCourseProvider {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
this.logger.debug('Clear all course status for site ' + site.id);
|
this.logger.debug('Clear all course status for site ' + site.id);
|
||||||
|
|
||||||
await site.getDb().deleteRecords(COURSE_STATUS_TABLE);
|
await this.statusTables[site.getId()].delete();
|
||||||
this.triggerCourseStatusChanged(CoreCourseProvider.ALL_COURSES_CLEARED, CoreConstants.NOT_DOWNLOADED, site.id);
|
this.triggerCourseStatusChanged(CoreCourseProvider.ALL_COURSES_CLEARED, CoreConstants.NOT_DOWNLOADED, site.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,7 +386,7 @@ export class CoreCourseProvider {
|
||||||
*/
|
*/
|
||||||
async getCourseStatusData(courseId: number, siteId?: string): Promise<CoreCourseStatusDBRecord> {
|
async getCourseStatusData(courseId: number, siteId?: string): Promise<CoreCourseStatusDBRecord> {
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
const entry: CoreCourseStatusDBRecord = await site.getDb().getRecord(COURSE_STATUS_TABLE, { id: courseId });
|
const entry = await this.statusTables[site.getId()].getOneByPrimaryKey({ id: courseId });
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
throw Error('No entry found on course status table');
|
throw Error('No entry found on course status table');
|
||||||
}
|
}
|
||||||
|
@ -405,16 +418,13 @@ export class CoreCourseProvider {
|
||||||
* @return Resolves with an array containing downloaded course ids.
|
* @return Resolves with an array containing downloaded course ids.
|
||||||
*/
|
*/
|
||||||
async getDownloadedCourseIds(siteId?: string): Promise<number[]> {
|
async getDownloadedCourseIds(siteId?: string): Promise<number[]> {
|
||||||
|
const downloadedStatuses = [CoreConstants.DOWNLOADED, CoreConstants.DOWNLOADING, CoreConstants.OUTDATED];
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
const entries: CoreCourseStatusDBRecord[] = await site.getDb().getRecordsList(
|
const entries = await this.statusTables[site.getId()].getManyWhere({
|
||||||
COURSE_STATUS_TABLE,
|
sql: 'status IN (?,?,?)',
|
||||||
'status',
|
sqlParams: downloadedStatuses,
|
||||||
[
|
js: ({ status }) => downloadedStatuses.includes(status),
|
||||||
CoreConstants.DOWNLOADED,
|
});
|
||||||
CoreConstants.DOWNLOADING,
|
|
||||||
CoreConstants.OUTDATED,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return entries.map((entry) => entry.id);
|
return entries.map((entry) => entry.id);
|
||||||
}
|
}
|
||||||
|
@ -1269,7 +1279,6 @@ export class CoreCourseProvider {
|
||||||
this.logger.debug(`Set previous status for course ${courseId} in site ${siteId}`);
|
this.logger.debug(`Set previous status for course ${courseId} in site ${siteId}`);
|
||||||
|
|
||||||
const site = await CoreSites.getSite(siteId);
|
const site = await CoreSites.getSite(siteId);
|
||||||
const db = site.getDb();
|
|
||||||
const entry = await this.getCourseStatusData(courseId, siteId);
|
const entry = await this.getCourseStatusData(courseId, siteId);
|
||||||
|
|
||||||
this.logger.debug(`Set previous status '${entry.status}' for course ${courseId}`);
|
this.logger.debug(`Set previous status '${entry.status}' for course ${courseId}`);
|
||||||
|
@ -1282,7 +1291,7 @@ export class CoreCourseProvider {
|
||||||
downloadTime: entry.status == CoreConstants.DOWNLOADING ? entry.previousDownloadTime : entry.downloadTime,
|
downloadTime: entry.status == CoreConstants.DOWNLOADING ? entry.previousDownloadTime : entry.downloadTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.updateRecords(COURSE_STATUS_TABLE, newData, { id: courseId });
|
await this.statusTables[site.getId()].update(newData, { id: courseId });
|
||||||
// Success updating, trigger event.
|
// Success updating, trigger event.
|
||||||
this.triggerCourseStatusChanged(courseId, newData.status, siteId);
|
this.triggerCourseStatusChanged(courseId, newData.status, siteId);
|
||||||
|
|
||||||
|
@ -1329,16 +1338,14 @@ export class CoreCourseProvider {
|
||||||
|
|
||||||
if (previousStatus != status) {
|
if (previousStatus != status) {
|
||||||
// Status has changed, update it.
|
// Status has changed, update it.
|
||||||
const data: CoreCourseStatusDBRecord = {
|
await this.statusTables[site.getId()].insert({
|
||||||
id: courseId,
|
id: courseId,
|
||||||
status: status,
|
status: status,
|
||||||
previous: previousStatus,
|
previous: previousStatus,
|
||||||
updated: new Date().getTime(),
|
updated: new Date().getTime(),
|
||||||
downloadTime: downloadTime,
|
downloadTime: downloadTime,
|
||||||
previousDownloadTime: previousDownloadTime,
|
previousDownloadTime: previousDownloadTime,
|
||||||
};
|
});
|
||||||
|
|
||||||
await site.getDb().insertRecord(COURSE_STATUS_TABLE, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success inserting, trigger event.
|
// Success inserting, trigger event.
|
||||||
|
|
|
@ -43,6 +43,7 @@ import { CoreH5PContentBeingSaved, CoreH5PLibraryBeingSaved } from './storage';
|
||||||
import { CoreH5PLibraryAddTo, CoreH5PLibraryMetadataSettings } from './validator';
|
import { CoreH5PLibraryAddTo, CoreH5PLibraryMetadataSettings } from './validator';
|
||||||
import { CoreH5PMetadata } from './metadata';
|
import { CoreH5PMetadata } from './metadata';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
|
import { SQLiteDB } from '@classes/sqlitedb';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Equivalent to Moodle's implementation of H5PFrameworkInterface.
|
* Equivalent to Moodle's implementation of H5PFrameworkInterface.
|
||||||
|
@ -64,7 +65,7 @@ export class CoreH5PFramework {
|
||||||
|
|
||||||
const db = await CoreSites.getSiteDb(siteId);
|
const db = await CoreSites.getSiteDb(siteId);
|
||||||
|
|
||||||
const whereAndParams = db.getInOrEqual(libraryIds);
|
const whereAndParams = SQLiteDB.getInOrEqual(libraryIds);
|
||||||
whereAndParams.sql = 'mainlibraryid ' + whereAndParams.sql;
|
whereAndParams.sql = 'mainlibraryid ' + whereAndParams.sql;
|
||||||
|
|
||||||
await db.updateRecordsWhere(CONTENT_TABLE_NAME, { filtered: null }, whereAndParams.sql, whereAndParams.params);
|
await db.updateRecordsWhere(CONTENT_TABLE_NAME, { filtered: null }, whereAndParams.sql, whereAndParams.params);
|
||||||
|
|
|
@ -24,7 +24,6 @@ import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreConfig } from '@services/config';
|
import { CoreConfig } from '@services/config';
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { SQLiteDB } from '@classes/sqlitedb';
|
|
||||||
import { CoreSite, CoreSiteInfo } from '@classes/site';
|
import { CoreSite, CoreSiteInfo } from '@classes/site';
|
||||||
import { makeSingleton, Badge, Push, Device, Translate, Platform, ApplicationInit, NgZone } from '@singletons';
|
import { makeSingleton, Badge, Push, Device, Translate, Platform, ApplicationInit, NgZone } from '@singletons';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
|
@ -42,6 +41,11 @@ import { CoreError } from '@classes/errors/error';
|
||||||
import { CoreWSExternalWarning } from '@services/ws';
|
import { CoreWSExternalWarning } from '@services/ws';
|
||||||
import { CoreSitesFactory } from '@services/sites-factory';
|
import { CoreSitesFactory } from '@services/sites-factory';
|
||||||
import { CoreMainMenuProvider } from '@features/mainmenu/services/mainmenu';
|
import { CoreMainMenuProvider } from '@features/mainmenu/services/mainmenu';
|
||||||
|
import { AsyncInstance, asyncInstance } from '@/core/utils/async-instance';
|
||||||
|
import { CoreDatabaseTable } from '@classes/database/database-table';
|
||||||
|
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
|
||||||
|
import { CoreObject } from '@singletons/object';
|
||||||
|
import { lazyMap, LazyMap } from '@/core/utils/lazy-map';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to handle push notifications.
|
* Service to handle push notifications.
|
||||||
|
@ -53,14 +57,27 @@ export class CorePushNotificationsProvider {
|
||||||
|
|
||||||
protected logger: CoreLogger;
|
protected logger: CoreLogger;
|
||||||
protected pushID?: string;
|
protected pushID?: string;
|
||||||
|
protected badgesTable = asyncInstance<CoreDatabaseTable<CorePushNotificationsBadgeDBRecord, 'siteid' | 'addon'>>();
|
||||||
|
protected pendingUnregistersTable =
|
||||||
|
asyncInstance<CoreDatabaseTable<CorePushNotificationsPendingUnregisterDBRecord, 'siteid'>>();
|
||||||
|
|
||||||
// Variables for DB.
|
protected registeredDevicesTables:
|
||||||
protected appDB: Promise<SQLiteDB>;
|
LazyMap<AsyncInstance<CoreDatabaseTable<CorePushNotificationsRegisteredDeviceDBRecord, 'appid' | 'uuid'>>>;
|
||||||
protected resolveAppDB!: (appDB: SQLiteDB) => void;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.appDB = new Promise(resolve => this.resolveAppDB = resolve);
|
|
||||||
this.logger = CoreLogger.getInstance('CorePushNotificationsProvider');
|
this.logger = CoreLogger.getInstance('CorePushNotificationsProvider');
|
||||||
|
this.registeredDevicesTables = lazyMap(
|
||||||
|
siteId => asyncInstance(
|
||||||
|
() => CoreSites.getSiteTable<CorePushNotificationsRegisteredDeviceDBRecord, 'appid' | 'uuid'>(
|
||||||
|
REGISTERED_DEVICES_TABLE_NAME,
|
||||||
|
{
|
||||||
|
siteId,
|
||||||
|
config: { cachingStrategy: CoreDatabaseCachingStrategy.None },
|
||||||
|
primaryKeyColumns: ['appid', 'uuid'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -157,7 +174,27 @@ export class CorePushNotificationsProvider {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resolveAppDB(CoreApp.getDB());
|
const database = CoreApp.getDB();
|
||||||
|
const badgesTable = new CoreDatabaseTableProxy<CorePushNotificationsBadgeDBRecord, 'siteid' | 'addon'>(
|
||||||
|
{ cachingStrategy: CoreDatabaseCachingStrategy.Eager },
|
||||||
|
database,
|
||||||
|
BADGE_TABLE_NAME,
|
||||||
|
['siteid', 'addon'],
|
||||||
|
);
|
||||||
|
const pendingUnregistersTable = new CoreDatabaseTableProxy<CorePushNotificationsPendingUnregisterDBRecord, 'siteid'>(
|
||||||
|
{ cachingStrategy: CoreDatabaseCachingStrategy.Eager },
|
||||||
|
database,
|
||||||
|
PENDING_UNREGISTER_TABLE_NAME,
|
||||||
|
['siteid'],
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
badgesTable.initialize(),
|
||||||
|
pendingUnregistersTable.initialize(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.badgesTable.setInstance(badgesTable);
|
||||||
|
this.pendingUnregistersTable.setInstance(pendingUnregistersTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -177,8 +214,7 @@ export class CorePushNotificationsProvider {
|
||||||
*/
|
*/
|
||||||
async cleanSiteCounters(siteId: string): Promise<void> {
|
async cleanSiteCounters(siteId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const db = await this.appDB;
|
await this.badgesTable.delete({ siteid: siteId });
|
||||||
await db.deleteRecords(BADGE_TABLE_NAME, { siteid: siteId } );
|
|
||||||
} finally {
|
} finally {
|
||||||
this.updateAppCounter();
|
this.updateAppCounter();
|
||||||
}
|
}
|
||||||
|
@ -514,7 +550,6 @@ export class CorePushNotificationsProvider {
|
||||||
|
|
||||||
this.logger.debug(`Unregister device on Moodle: '${site.getId()}'`);
|
this.logger.debug(`Unregister device on Moodle: '${site.getId()}'`);
|
||||||
|
|
||||||
const db = await this.appDB;
|
|
||||||
const data: CoreUserRemoveUserDeviceWSParams = {
|
const data: CoreUserRemoveUserDeviceWSParams = {
|
||||||
appid: CoreConstants.CONFIG.app_id,
|
appid: CoreConstants.CONFIG.app_id,
|
||||||
uuid: Device.uuid,
|
uuid: Device.uuid,
|
||||||
|
@ -526,7 +561,7 @@ export class CorePushNotificationsProvider {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (CoreUtils.isWebServiceError(error) || CoreUtils.isExpiredTokenError(error)) {
|
if (CoreUtils.isWebServiceError(error) || CoreUtils.isExpiredTokenError(error)) {
|
||||||
// Cannot unregister. Don't try again.
|
// Cannot unregister. Don't try again.
|
||||||
await CoreUtils.ignoreErrors(db.deleteRecords(PENDING_UNREGISTER_TABLE_NAME, {
|
await CoreUtils.ignoreErrors(this.pendingUnregistersTable.delete({
|
||||||
token: site.getToken(),
|
token: site.getToken(),
|
||||||
siteid: site.getId(),
|
siteid: site.getId(),
|
||||||
}));
|
}));
|
||||||
|
@ -535,13 +570,12 @@ export class CorePushNotificationsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the pending unregister so it's retried again later.
|
// Store the pending unregister so it's retried again later.
|
||||||
const entry: CorePushNotificationsPendingUnregisterDBRecord = {
|
await this.pendingUnregistersTable.insert({
|
||||||
siteid: site.getId(),
|
siteid: site.getId(),
|
||||||
siteurl: site.getURL(),
|
siteurl: site.getURL(),
|
||||||
token: site.getToken(),
|
token: site.getToken(),
|
||||||
info: JSON.stringify(site.getInfo()),
|
info: JSON.stringify(site.getInfo()),
|
||||||
};
|
});
|
||||||
await db.insertRecord(PENDING_UNREGISTER_TABLE_NAME, entry);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -552,9 +586,9 @@ export class CorePushNotificationsProvider {
|
||||||
|
|
||||||
await CoreUtils.ignoreErrors(Promise.all([
|
await CoreUtils.ignoreErrors(Promise.all([
|
||||||
// Remove the device from the local DB.
|
// Remove the device from the local DB.
|
||||||
site.getDb().deleteRecords(REGISTERED_DEVICES_TABLE_NAME, this.getRegisterData()),
|
this.registeredDevicesTables[site.getId()].delete(this.getRegisterData()),
|
||||||
// Remove pending unregisters for this site.
|
// Remove pending unregisters for this site.
|
||||||
db.deleteRecords(PENDING_UNREGISTER_TABLE_NAME, { siteid: site.getId() }),
|
this.pendingUnregistersTable.deleteByPrimaryKey({ siteid: site.getId() }),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -714,15 +748,14 @@ export class CorePushNotificationsProvider {
|
||||||
|
|
||||||
// Insert the device in the local DB.
|
// Insert the device in the local DB.
|
||||||
try {
|
try {
|
||||||
await site.getDb().insertRecord(REGISTERED_DEVICES_TABLE_NAME, data);
|
await this.registeredDevicesTables[site.getId()].insert(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// Remove pending unregisters for this site.
|
// Remove pending unregisters for this site.
|
||||||
const db = await this.appDB;
|
await CoreUtils.ignoreErrors(this.pendingUnregistersTable.deleteByPrimaryKey({ siteid: site.getId() }));
|
||||||
await CoreUtils.ignoreErrors(db.deleteRecords(PENDING_UNREGISTER_TABLE_NAME, { siteid: site.getId() }));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -735,8 +768,7 @@ export class CorePushNotificationsProvider {
|
||||||
*/
|
*/
|
||||||
protected async getAddonBadge(siteId?: string, addon: string = 'site'): Promise<number> {
|
protected async getAddonBadge(siteId?: string, addon: string = 'site'): Promise<number> {
|
||||||
try {
|
try {
|
||||||
const db = await this.appDB;
|
const entry = await this.badgesTable.getOne({ siteid: siteId, addon });
|
||||||
const entry = await db.getRecord<CorePushNotificationsBadgeDBRecord>(BADGE_TABLE_NAME, { siteid: siteId, addon });
|
|
||||||
|
|
||||||
return entry?.number || 0;
|
return entry?.number || 0;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -751,19 +783,7 @@ export class CorePushNotificationsProvider {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async retryUnregisters(siteId?: string): Promise<void> {
|
async retryUnregisters(siteId?: string): Promise<void> {
|
||||||
|
const results = await this.pendingUnregistersTable.getMany(CoreObject.withoutEmpty({ siteid: siteId }));
|
||||||
const db = await this.appDB;
|
|
||||||
let results: CorePushNotificationsPendingUnregisterDBRecord[];
|
|
||||||
|
|
||||||
if (siteId) {
|
|
||||||
// Check if the site has a pending unregister.
|
|
||||||
results = await db.getRecords<CorePushNotificationsPendingUnregisterDBRecord>(PENDING_UNREGISTER_TABLE_NAME, {
|
|
||||||
siteid: siteId,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Get all pending unregisters.
|
|
||||||
results = await db.getAllRecords<CorePushNotificationsPendingUnregisterDBRecord>(PENDING_UNREGISTER_TABLE_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(results.map(async (result) => {
|
await Promise.all(results.map(async (result) => {
|
||||||
// Create a temporary site to unregister.
|
// Create a temporary site to unregister.
|
||||||
|
@ -789,14 +809,11 @@ export class CorePushNotificationsProvider {
|
||||||
protected async saveAddonBadge(value: number, siteId?: string, addon: string = 'site'): Promise<number> {
|
protected async saveAddonBadge(value: number, siteId?: string, addon: string = 'site'): Promise<number> {
|
||||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||||
|
|
||||||
const entry: CorePushNotificationsBadgeDBRecord = {
|
await this.badgesTable.insert({
|
||||||
siteid: siteId,
|
siteid: siteId,
|
||||||
addon,
|
addon,
|
||||||
number: value, // eslint-disable-line id-blacklist
|
number: value, // eslint-disable-line id-blacklist
|
||||||
};
|
});
|
||||||
|
|
||||||
const db = await this.appDB;
|
|
||||||
await db.insertRecord(BADGE_TABLE_NAME, entry);
|
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -815,7 +832,7 @@ export class CorePushNotificationsProvider {
|
||||||
|
|
||||||
// Check if the device is already registered.
|
// Check if the device is already registered.
|
||||||
const records = await CoreUtils.ignoreErrors(
|
const records = await CoreUtils.ignoreErrors(
|
||||||
site.getDb().getRecords<CorePushNotificationsRegisteredDeviceDBRecord>(REGISTERED_DEVICES_TABLE_NAME, {
|
this.registeredDevicesTables[site.getId()].getMany({
|
||||||
appid: data.appid,
|
appid: data.appid,
|
||||||
uuid: data.uuid,
|
uuid: data.uuid,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
|
|
@ -25,14 +25,9 @@ import { CoreColors } from '@singletons/colors';
|
||||||
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app';
|
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app';
|
||||||
import { CoreObject } from '@singletons/object';
|
import { CoreObject } from '@singletons/object';
|
||||||
import { CoreRedirectPayload } from './navigator';
|
import { CoreRedirectPayload } from './navigator';
|
||||||
|
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
|
||||||
/**
|
import { asyncInstance } from '../utils/async-instance';
|
||||||
* Object responsible of managing schema versions.
|
import { CoreDatabaseTable } from '@classes/database/database-table';
|
||||||
*/
|
|
||||||
type SchemaVersionsManager = {
|
|
||||||
get(schemaName: string): Promise<number>;
|
|
||||||
set(schemaName: string, version: number): Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -58,13 +53,9 @@ export class CoreAppProvider {
|
||||||
protected keyboardClosing = false;
|
protected keyboardClosing = false;
|
||||||
protected forceOffline = false;
|
protected forceOffline = false;
|
||||||
protected redirect?: CoreRedirectData;
|
protected redirect?: CoreRedirectData;
|
||||||
|
protected schemaVersionsTable = asyncInstance<CoreDatabaseTable<SchemaVersionsDBEntry, 'name'>>();
|
||||||
// Variables for DB.
|
|
||||||
protected schemaVersionsManager: Promise<SchemaVersionsManager>;
|
|
||||||
protected resolveSchemaVersionsManager!: (schemaVersionsManager: SchemaVersionsManager) => void;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.schemaVersionsManager = new Promise(resolve => this.resolveSchemaVersionsManager = resolve);
|
|
||||||
this.logger = CoreLogger.getInstance('CoreAppProvider');
|
this.logger = CoreLogger.getInstance('CoreAppProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,24 +72,20 @@ export class CoreAppProvider {
|
||||||
* Initialize database.
|
* Initialize database.
|
||||||
*/
|
*/
|
||||||
async initializeDatabase(): Promise<void> {
|
async initializeDatabase(): Promise<void> {
|
||||||
await this.getDB().createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA);
|
const database = this.getDB();
|
||||||
|
|
||||||
this.resolveSchemaVersionsManager({
|
await database.createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA);
|
||||||
get: async name => {
|
|
||||||
try {
|
|
||||||
// Fetch installed version of the schema.
|
|
||||||
const entry = await this.getDB().getRecord<SchemaVersionsDBEntry>(SCHEMA_VERSIONS_TABLE_NAME, { name });
|
|
||||||
|
|
||||||
return entry.version;
|
const schemaVersionsTable = new CoreDatabaseTableProxy<SchemaVersionsDBEntry, 'name'>(
|
||||||
} catch (error) {
|
{ cachingStrategy: CoreDatabaseCachingStrategy.Eager },
|
||||||
// No installed version yet.
|
database,
|
||||||
return 0;
|
SCHEMA_VERSIONS_TABLE_NAME,
|
||||||
}
|
['name'],
|
||||||
},
|
);
|
||||||
set: async (name, version) => {
|
|
||||||
await this.getDB().insertRecord(SCHEMA_VERSIONS_TABLE_NAME, { name, version });
|
await schemaVersionsTable.initialize();
|
||||||
},
|
|
||||||
});
|
this.schemaVersionsTable.setInstance(schemaVersionsTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,8 +124,7 @@ export class CoreAppProvider {
|
||||||
async createTablesFromSchema(schema: CoreAppSchema): Promise<void> {
|
async createTablesFromSchema(schema: CoreAppSchema): Promise<void> {
|
||||||
this.logger.debug(`Apply schema to app DB: ${schema.name}`);
|
this.logger.debug(`Apply schema to app DB: ${schema.name}`);
|
||||||
|
|
||||||
const schemaVersionsManager = await this.schemaVersionsManager;
|
const oldVersion = await this.getInstalledSchemaVersion(schema);
|
||||||
const oldVersion = await schemaVersionsManager.get(schema.name);
|
|
||||||
|
|
||||||
if (oldVersion >= schema.version) {
|
if (oldVersion >= schema.version) {
|
||||||
// Version already installed, nothing else to do.
|
// Version already installed, nothing else to do.
|
||||||
|
@ -155,7 +141,16 @@ export class CoreAppProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set installed version.
|
// Set installed version.
|
||||||
schemaVersionsManager.set(schema.name, schema.version);
|
await this.schemaVersionsTable.insert({ name: schema.name, version: schema.version });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete table schema.
|
||||||
|
*
|
||||||
|
* @param name Schema name.
|
||||||
|
*/
|
||||||
|
async deleteTableSchema(name: string): Promise<void> {
|
||||||
|
await this.schemaVersionsTable.deleteByPrimaryKey({ name });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -683,6 +678,24 @@ export class CoreAppProvider {
|
||||||
this.forceOffline = !!value;
|
this.forceOffline = !!value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the installed version for the given schema.
|
||||||
|
*
|
||||||
|
* @param schema App schema.
|
||||||
|
* @returns Installed version number, or 0 if the schema is not installed.
|
||||||
|
*/
|
||||||
|
protected async getInstalledSchemaVersion(schema: CoreAppSchema): Promise<number> {
|
||||||
|
try {
|
||||||
|
// Fetch installed version of the schema.
|
||||||
|
const entry = await this.schemaVersionsTable.getOneByPrimaryKey({ name: schema.name });
|
||||||
|
|
||||||
|
return entry.version;
|
||||||
|
} catch (error) {
|
||||||
|
// No installed version yet.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreApp = makeSingleton(CoreAppProvider);
|
export const CoreApp = makeSingleton(CoreAppProvider);
|
||||||
|
|
|
@ -120,6 +120,22 @@ export class CoreConfigProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given app setting exists.
|
||||||
|
*
|
||||||
|
* @param name The config name.
|
||||||
|
* @returns Whether the app setting exists.
|
||||||
|
*/
|
||||||
|
async has(name: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await this.table.getOneByPrimaryKey({ name });
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set an app setting.
|
* Set an app setting.
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,12 +18,14 @@ import { CoreApp } 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';
|
||||||
import { SQLiteDB } from '@classes/sqlitedb';
|
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { APP_SCHEMA, CRON_TABLE_NAME, CronDBEntry } from '@services/database/cron';
|
import { APP_SCHEMA, CRON_TABLE_NAME, CronDBEntry } from '@services/database/cron';
|
||||||
|
import { asyncInstance } from '../utils/async-instance';
|
||||||
|
import { CoreDatabaseTable } from '@classes/database/database-table';
|
||||||
|
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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.
|
||||||
|
@ -39,13 +41,9 @@ export class CoreCronDelegateService {
|
||||||
protected logger: CoreLogger;
|
protected logger: CoreLogger;
|
||||||
protected handlers: { [s: string]: CoreCronHandler } = {};
|
protected handlers: { [s: string]: CoreCronHandler } = {};
|
||||||
protected queuePromise: Promise<void> = Promise.resolve();
|
protected queuePromise: Promise<void> = Promise.resolve();
|
||||||
|
protected table = asyncInstance<CoreDatabaseTable<CronDBEntry>>();
|
||||||
// Variables for DB.
|
|
||||||
protected appDB: Promise<SQLiteDB>;
|
|
||||||
protected resolveAppDB!: (appDB: SQLiteDB) => void;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.appDB = new Promise(resolve => this.resolveAppDB = resolve);
|
|
||||||
this.logger = CoreLogger.getInstance('CoreCronDelegate');
|
this.logger = CoreLogger.getInstance('CoreCronDelegate');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +57,15 @@ export class CoreCronDelegateService {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resolveAppDB(CoreApp.getDB());
|
const table = new CoreDatabaseTableProxy<CronDBEntry>(
|
||||||
|
{ cachingStrategy: CoreDatabaseCachingStrategy.Eager },
|
||||||
|
CoreApp.getDB(),
|
||||||
|
CRON_TABLE_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
await table.initialize();
|
||||||
|
|
||||||
|
this.table.setInstance(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -238,11 +244,10 @@ export class CoreCronDelegateService {
|
||||||
* @return Promise resolved with the handler's last execution time.
|
* @return Promise resolved with the handler's last execution time.
|
||||||
*/
|
*/
|
||||||
protected async getHandlerLastExecutionTime(name: string): Promise<number> {
|
protected async getHandlerLastExecutionTime(name: string): Promise<number> {
|
||||||
const db = await this.appDB;
|
|
||||||
const id = this.getHandlerLastExecutionId(name);
|
const id = this.getHandlerLastExecutionId(name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const entry = await db.getRecord<CronDBEntry>(CRON_TABLE_NAME, { id });
|
const entry = await this.table.getOneByPrimaryKey({ id });
|
||||||
|
|
||||||
const time = Number(entry.value);
|
const time = Number(entry.value);
|
||||||
|
|
||||||
|
@ -397,14 +402,13 @@ export class CoreCronDelegateService {
|
||||||
* @return Promise resolved when the execution time is saved.
|
* @return Promise resolved when the execution time is saved.
|
||||||
*/
|
*/
|
||||||
protected async setHandlerLastExecutionTime(name: string, time: number): Promise<void> {
|
protected async setHandlerLastExecutionTime(name: string, time: number): Promise<void> {
|
||||||
const db = await this.appDB;
|
|
||||||
const id = this.getHandlerLastExecutionId(name);
|
const id = this.getHandlerLastExecutionId(name);
|
||||||
const entry = {
|
const entry = {
|
||||||
id,
|
id,
|
||||||
value: time,
|
value: time,
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.insertRecord(CRON_TABLE_NAME, entry);
|
await this.table.insert(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,7 +21,6 @@ import { CoreSite } from '@classes/site';
|
||||||
* Database variables for CoreSites service.
|
* Database variables for CoreSites service.
|
||||||
*/
|
*/
|
||||||
export const SITES_TABLE_NAME = 'sites_2';
|
export const SITES_TABLE_NAME = 'sites_2';
|
||||||
export const CURRENT_SITE_TABLE_NAME = 'current_site';
|
|
||||||
export const SCHEMA_VERSIONS_TABLE_NAME = 'schema_versions';
|
export const SCHEMA_VERSIONS_TABLE_NAME = 'schema_versions';
|
||||||
|
|
||||||
// Schema to register in App DB.
|
// Schema to register in App DB.
|
||||||
|
@ -68,22 +67,6 @@ export const APP_SCHEMA: CoreAppSchema = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
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> {
|
async migrate(db: SQLiteDB, oldVersion: number): Promise<void> {
|
||||||
if (oldVersion < 2) {
|
if (oldVersion < 2) {
|
||||||
|
@ -184,11 +167,6 @@ export type SiteDBEntry = {
|
||||||
oauthId?: number | null;
|
oauthId?: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CurrentSiteDBEntry = {
|
|
||||||
id: number;
|
|
||||||
siteId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SchemaVersionsDBEntry = {
|
export type SchemaVersionsDBEntry = {
|
||||||
name: string;
|
name: string;
|
||||||
version: number;
|
version: number;
|
||||||
|
|
|
@ -49,7 +49,7 @@ import {
|
||||||
import { CoreFileHelper } from './file-helper';
|
import { CoreFileHelper } from './file-helper';
|
||||||
import { CoreUrl } from '@singletons/url';
|
import { CoreUrl } from '@singletons/url';
|
||||||
import { CoreDatabaseTable } from '@classes/database/database-table';
|
import { CoreDatabaseTable } from '@classes/database/database-table';
|
||||||
import { CoreDatabaseCachingStrategy } from '@classes/database/database-table-proxy';
|
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
|
||||||
import { lazyMap, LazyMap } from '../utils/lazy-map';
|
import { lazyMap, LazyMap } from '../utils/lazy-map';
|
||||||
import { asyncInstance, AsyncInstance } from '../utils/async-instance';
|
import { asyncInstance, AsyncInstance } from '../utils/async-instance';
|
||||||
|
|
||||||
|
@ -98,14 +98,14 @@ export class CoreFilepoolProvider {
|
||||||
// Variables to prevent downloading packages/files twice at the same time.
|
// Variables to prevent downloading packages/files twice at the same time.
|
||||||
protected packagesPromises: { [s: string]: { [s: string]: Promise<void> } } = {};
|
protected packagesPromises: { [s: string]: { [s: string]: Promise<void> } } = {};
|
||||||
protected filePromises: { [s: string]: { [s: string]: Promise<string> } } = {};
|
protected filePromises: { [s: string]: { [s: string]: Promise<string> } } = {};
|
||||||
|
|
||||||
// Variables for DB.
|
|
||||||
protected appDB: Promise<SQLiteDB>;
|
|
||||||
protected resolveAppDB!: (appDB: SQLiteDB) => void;
|
|
||||||
protected filesTables: LazyMap<AsyncInstance<CoreDatabaseTable<CoreFilepoolFileEntry, 'fileId'>>>;
|
protected filesTables: LazyMap<AsyncInstance<CoreDatabaseTable<CoreFilepoolFileEntry, 'fileId'>>>;
|
||||||
|
protected linksTables:
|
||||||
|
LazyMap<AsyncInstance<CoreDatabaseTable<CoreFilepoolLinksRecord, 'fileId' | 'component' | 'componentId'>>>;
|
||||||
|
|
||||||
|
protected packagesTables: LazyMap<AsyncInstance<CoreDatabaseTable<CoreFilepoolPackageEntry>>>;
|
||||||
|
protected queueTable = asyncInstance<CoreDatabaseTable<CoreFilepoolQueueDBEntry, 'siteId' | 'fileId'>>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.appDB = new Promise(resolve => this.resolveAppDB = resolve);
|
|
||||||
this.logger = CoreLogger.getInstance('CoreFilepoolProvider');
|
this.logger = CoreLogger.getInstance('CoreFilepoolProvider');
|
||||||
this.filesTables = lazyMap(
|
this.filesTables = lazyMap(
|
||||||
siteId => asyncInstance(
|
siteId => asyncInstance(
|
||||||
|
@ -116,6 +116,23 @@ export class CoreFilepoolProvider {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
this.linksTables = lazyMap(
|
||||||
|
siteId => asyncInstance(
|
||||||
|
() => CoreSites.getSiteTable<CoreFilepoolLinksRecord, 'fileId' | 'component' | 'componentId'>(LINKS_TABLE_NAME, {
|
||||||
|
siteId,
|
||||||
|
config: { cachingStrategy: CoreDatabaseCachingStrategy.Lazy },
|
||||||
|
primaryKeyColumns: ['fileId', 'component', 'componentId'],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.packagesTables = lazyMap(
|
||||||
|
siteId => asyncInstance(
|
||||||
|
() => CoreSites.getSiteTable<CoreFilepoolPackageEntry, 'id'>(PACKAGES_TABLE_NAME, {
|
||||||
|
siteId,
|
||||||
|
config: { cachingStrategy: CoreDatabaseCachingStrategy.Lazy },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,7 +171,16 @@ export class CoreFilepoolProvider {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resolveAppDB(CoreApp.getDB());
|
const queueTable = new CoreDatabaseTableProxy<CoreFilepoolQueueDBEntry, 'siteId' | 'fileId'>(
|
||||||
|
{ cachingStrategy: CoreDatabaseCachingStrategy.Lazy },
|
||||||
|
CoreApp.getDB(),
|
||||||
|
QUEUE_TABLE_NAME,
|
||||||
|
['siteId','fileId'],
|
||||||
|
);
|
||||||
|
|
||||||
|
await queueTable.initialize();
|
||||||
|
|
||||||
|
this.queueTable.setInstance(queueTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,16 +197,11 @@ export class CoreFilepoolProvider {
|
||||||
throw new CoreError('Cannot add link because component is invalid.');
|
throw new CoreError('Cannot add link because component is invalid.');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentId = this.fixComponentId(componentId);
|
await this.linksTables[siteId].insert({
|
||||||
|
|
||||||
const db = await CoreSites.getSiteDb(siteId);
|
|
||||||
const newEntry: CoreFilepoolLinksRecord = {
|
|
||||||
fileId,
|
fileId,
|
||||||
component,
|
component,
|
||||||
componentId: componentId || '',
|
componentId: this.fixComponentId(componentId) || '',
|
||||||
};
|
});
|
||||||
|
|
||||||
await db.insertRecord(LINKS_TABLE_NAME, newEntry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -301,9 +322,7 @@ export class CoreFilepoolProvider {
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.logger.debug(`Adding ${fileId} to the queue`);
|
this.logger.debug(`Adding ${fileId} to the queue`);
|
||||||
|
|
||||||
const db = await this.appDB;
|
await this.queueTable.insert({
|
||||||
|
|
||||||
await db.insertRecord(QUEUE_TABLE_NAME, {
|
|
||||||
siteId,
|
siteId,
|
||||||
fileId,
|
fileId,
|
||||||
url,
|
url,
|
||||||
|
@ -431,10 +450,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`);
|
||||||
|
|
||||||
const db = await this.appDB;
|
return this.queueTable.update(newData, primaryKey).then(() => this.getQueuePromise(siteId, fileId, true, onProgress));
|
||||||
|
|
||||||
return db.updateRecords(QUEUE_TABLE_NAME, newData, primaryKey).then(() =>
|
|
||||||
this.getQueuePromise(siteId, fileId, true, onProgress));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.debug(`File ${fileId} already in queue and does not require update`);
|
this.logger.debug(`File ${fileId} already in queue and does not require update`);
|
||||||
|
@ -560,11 +576,10 @@ export class CoreFilepoolProvider {
|
||||||
async clearAllPackagesStatus(siteId: string): Promise<void> {
|
async clearAllPackagesStatus(siteId: string): Promise<void> {
|
||||||
this.logger.debug('Clear all packages status for site ' + siteId);
|
this.logger.debug('Clear all packages status for site ' + siteId);
|
||||||
|
|
||||||
const site = await CoreSites.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(PACKAGES_TABLE_NAME);
|
const entries = await this.packagesTables[siteId].getMany();
|
||||||
// Delete all the entries.
|
// Delete all the entries.
|
||||||
await site.getDb().deleteRecords(PACKAGES_TABLE_NAME);
|
await this.packagesTables[siteId].delete();
|
||||||
|
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
if (!entry.component) {
|
if (!entry.component) {
|
||||||
|
@ -583,15 +598,13 @@ export class CoreFilepoolProvider {
|
||||||
* @return Promise resolved when the filepool is cleared.
|
* @return Promise resolved when the filepool is cleared.
|
||||||
*/
|
*/
|
||||||
async clearFilepool(siteId: string): Promise<void> {
|
async clearFilepool(siteId: string): Promise<void> {
|
||||||
const db = await CoreSites.getSiteDb(siteId);
|
|
||||||
|
|
||||||
// Read the data first to be able to notify the deletions.
|
// Read the data first to be able to notify the deletions.
|
||||||
const filesEntries = await this.filesTables[siteId].getMany();
|
const filesEntries = await this.filesTables[siteId].getMany();
|
||||||
const filesLinks = await db.getAllRecords<CoreFilepoolLinksRecord>(LINKS_TABLE_NAME);
|
const filesLinks = await this.linksTables[siteId].getMany();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.filesTables[siteId].delete(),
|
this.filesTables[siteId].delete(),
|
||||||
db.deleteRecords(LINKS_TABLE_NAME),
|
this.linksTables[siteId].delete(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Notify now.
|
// Notify now.
|
||||||
|
@ -609,14 +622,14 @@ export class CoreFilepoolProvider {
|
||||||
* @return Resolved means yes, rejected means no.
|
* @return Resolved means yes, rejected means no.
|
||||||
*/
|
*/
|
||||||
async componentHasFiles(siteId: string, component: string, componentId?: string | number): Promise<void> {
|
async componentHasFiles(siteId: string, component: string, componentId?: string | number): Promise<void> {
|
||||||
const db = await CoreSites.getSiteDb(siteId);
|
|
||||||
const conditions = {
|
const conditions = {
|
||||||
component,
|
component,
|
||||||
componentId: this.fixComponentId(componentId),
|
componentId: this.fixComponentId(componentId),
|
||||||
};
|
};
|
||||||
|
|
||||||
const count = await db.countRecords(LINKS_TABLE_NAME, conditions);
|
const hasAnyLinks = await this.linksTables[siteId].hasAny(conditions);
|
||||||
if (count <= 0) {
|
|
||||||
|
if (!hasAnyLinks) {
|
||||||
throw new CoreError('Component doesn\'t have files');
|
throw new CoreError('Component doesn\'t have files');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1144,7 +1157,6 @@ export class CoreFilepoolProvider {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = await CoreSites.getSiteDb(siteId);
|
|
||||||
const extension = CoreMimetypeUtils.getFileExtension(entry.path);
|
const extension = CoreMimetypeUtils.getFileExtension(entry.path);
|
||||||
if (!extension) {
|
if (!extension) {
|
||||||
// Files does not have extension. Invalidate file (stale = true).
|
// Files does not have extension. Invalidate file (stale = true).
|
||||||
|
@ -1170,7 +1182,7 @@ export class CoreFilepoolProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now update the links.
|
// Now update the links.
|
||||||
await db.updateRecords(LINKS_TABLE_NAME, { fileId: entry.fileId }, { fileId });
|
await this.linksTables[siteId].update({ fileId: entry.fileId }, { fileId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1228,16 +1240,18 @@ export class CoreFilepoolProvider {
|
||||||
* @return Promise resolved with the files.
|
* @return Promise resolved with the files.
|
||||||
*/
|
*/
|
||||||
protected async getComponentFiles(
|
protected async getComponentFiles(
|
||||||
db: SQLiteDB,
|
siteId: string | undefined,
|
||||||
component: string,
|
component: string,
|
||||||
componentId?: string | number,
|
componentId?: string | number,
|
||||||
): Promise<CoreFilepoolLinksRecord[]> {
|
): Promise<CoreFilepoolLinksRecord[]> {
|
||||||
|
siteId = siteId ?? CoreSites.getCurrentSiteId();
|
||||||
const conditions = {
|
const conditions = {
|
||||||
component,
|
component,
|
||||||
componentId: this.fixComponentId(componentId),
|
componentId: this.fixComponentId(componentId),
|
||||||
};
|
};
|
||||||
|
|
||||||
const items = await db.getRecords<CoreFilepoolLinksRecord>(LINKS_TABLE_NAME, conditions);
|
const items = await this.linksTables[siteId].getMany(conditions);
|
||||||
|
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
item.componentId = this.fixComponentId(item.componentId);
|
item.componentId = this.fixComponentId(item.componentId);
|
||||||
});
|
});
|
||||||
|
@ -1349,8 +1363,7 @@ export class CoreFilepoolProvider {
|
||||||
* @return Promise resolved with the links.
|
* @return Promise resolved with the links.
|
||||||
*/
|
*/
|
||||||
protected async getFileLinks(siteId: string, fileId: string): Promise<CoreFilepoolLinksRecord[]> {
|
protected async getFileLinks(siteId: string, fileId: string): Promise<CoreFilepoolLinksRecord[]> {
|
||||||
const db = await CoreSites.getSiteDb(siteId);
|
const items = await this.linksTables[siteId].getMany({ 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);
|
||||||
|
@ -1421,8 +1434,7 @@ export class CoreFilepoolProvider {
|
||||||
* @return Promise resolved with the files on success.
|
* @return Promise resolved with the files on success.
|
||||||
*/
|
*/
|
||||||
async getFilesByComponent(siteId: string, component: string, componentId?: string | number): Promise<CoreFilepoolFileEntry[]> {
|
async getFilesByComponent(siteId: string, component: string, componentId?: string | number): Promise<CoreFilepoolFileEntry[]> {
|
||||||
const db = await CoreSites.getSiteDb(siteId);
|
const items = await this.getComponentFiles(siteId, component, componentId);
|
||||||
const items = await this.getComponentFiles(db, component, componentId);
|
|
||||||
const files: CoreFilepoolFileEntry[] = [];
|
const files: CoreFilepoolFileEntry[] = [];
|
||||||
|
|
||||||
await Promise.all(items.map(async (item) => {
|
await Promise.all(items.map(async (item) => {
|
||||||
|
@ -1706,10 +1718,9 @@ export class CoreFilepoolProvider {
|
||||||
async getPackageData(siteId: string, component: string, componentId?: string | number): Promise<CoreFilepoolPackageEntry> {
|
async getPackageData(siteId: string, component: string, componentId?: string | number): Promise<CoreFilepoolPackageEntry> {
|
||||||
componentId = this.fixComponentId(componentId);
|
componentId = this.fixComponentId(componentId);
|
||||||
|
|
||||||
const site = await CoreSites.getSite(siteId);
|
|
||||||
const packageId = this.getPackageId(component, componentId);
|
const packageId = this.getPackageId(component, componentId);
|
||||||
|
|
||||||
return site.getDb().getRecord(PACKAGES_TABLE_NAME, { id: packageId });
|
return this.packagesTables[siteId].getOneByPrimaryKey({ id: packageId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2171,16 +2182,16 @@ export class CoreFilepoolProvider {
|
||||||
* @return Resolved with file object from DB on success, rejected otherwise.
|
* @return Resolved with file object from DB on success, rejected otherwise.
|
||||||
*/
|
*/
|
||||||
protected async hasFileInQueue(siteId: string, fileId: string): Promise<CoreFilepoolQueueEntry> {
|
protected async hasFileInQueue(siteId: string, fileId: string): Promise<CoreFilepoolQueueEntry> {
|
||||||
const db = await this.appDB;
|
const entry = await this.queueTable.getOneByPrimaryKey({ siteId, fileId });
|
||||||
const entry = await db.getRecord<CoreFilepoolQueueEntry>(QUEUE_TABLE_NAME, { siteId, fileId });
|
|
||||||
|
|
||||||
if (entry === undefined) {
|
if (entry === undefined) {
|
||||||
throw new CoreError('File not found in queue.');
|
throw new CoreError('File not found in queue.');
|
||||||
}
|
}
|
||||||
// Convert the links to an object.
|
|
||||||
entry.linksUnserialized = <CoreFilepoolComponentLink[]> CoreTextUtils.parseJSON(entry.links || '[]', []);
|
|
||||||
|
|
||||||
return entry;
|
return {
|
||||||
|
...entry,
|
||||||
|
linksUnserialized: CoreTextUtils.parseJSON(entry.links, []),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2238,8 +2249,7 @@ export class CoreFilepoolProvider {
|
||||||
componentId?: string | number,
|
componentId?: string | number,
|
||||||
onlyUnknown: boolean = true,
|
onlyUnknown: boolean = true,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const db = await CoreSites.getSiteDb(siteId);
|
const items = await this.getComponentFiles(siteId, component, componentId);
|
||||||
const items = await this.getComponentFiles(db, component, componentId);
|
|
||||||
|
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
// Nothing to invalidate.
|
// Nothing to invalidate.
|
||||||
|
@ -2250,7 +2260,7 @@ export class CoreFilepoolProvider {
|
||||||
|
|
||||||
const fileIds = items.map((item) => item.fileId);
|
const fileIds = items.map((item) => item.fileId);
|
||||||
|
|
||||||
const whereAndParams = db.getInOrEqual(fileIds);
|
const whereAndParams = SQLiteDB.getInOrEqual(fileIds);
|
||||||
|
|
||||||
whereAndParams.sql = 'fileId ' + whereAndParams.sql;
|
whereAndParams.sql = 'fileId ' + whereAndParams.sql;
|
||||||
|
|
||||||
|
@ -2523,30 +2533,25 @@ export class CoreFilepoolProvider {
|
||||||
* @return Resolved on success. Rejected on failure.
|
* @return Resolved on success. Rejected on failure.
|
||||||
*/
|
*/
|
||||||
protected async processImportantQueueItem(): Promise<void> {
|
protected async processImportantQueueItem(): Promise<void> {
|
||||||
let items: CoreFilepoolQueueEntry[];
|
|
||||||
const db = await this.appDB;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
items = await db.getRecords<CoreFilepoolQueueEntry>(
|
const item = await this.queueTable.getOne({}, {
|
||||||
QUEUE_TABLE_NAME,
|
sorting: [
|
||||||
undefined,
|
{ priority: 'desc' },
|
||||||
'priority DESC, added ASC',
|
{ added: 'asc' },
|
||||||
undefined,
|
],
|
||||||
0,
|
});
|
||||||
1,
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
throw CoreFilepoolProvider.ERR_QUEUE_IS_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = items.pop();
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
throw CoreFilepoolProvider.ERR_QUEUE_IS_EMPTY;
|
throw CoreFilepoolProvider.ERR_QUEUE_IS_EMPTY;
|
||||||
}
|
}
|
||||||
// Convert the links to an object.
|
|
||||||
item.linksUnserialized = <CoreFilepoolComponentLink[]> CoreTextUtils.parseJSON(item.links, []);
|
|
||||||
|
|
||||||
return this.processQueueItem(item);
|
return this.processQueueItem({
|
||||||
|
...item,
|
||||||
|
linksUnserialized: CoreTextUtils.parseJSON(item.links, []),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw CoreFilepoolProvider.ERR_QUEUE_IS_EMPTY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2671,9 +2676,7 @@ export class CoreFilepoolProvider {
|
||||||
* @return Resolved on success. Rejected on failure. It is advised to silently ignore failures.
|
* @return Resolved on success. Rejected on failure. It is advised to silently ignore failures.
|
||||||
*/
|
*/
|
||||||
protected async removeFromQueue(siteId: string, fileId: string): Promise<void> {
|
protected async removeFromQueue(siteId: string, fileId: string): Promise<void> {
|
||||||
const db = await this.appDB;
|
await this.queueTable.deleteByPrimaryKey({ siteId, fileId });
|
||||||
|
|
||||||
await db.deleteRecords(QUEUE_TABLE_NAME, { siteId, fileId });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2684,8 +2687,6 @@ export class CoreFilepoolProvider {
|
||||||
* @return Resolved on success.
|
* @return Resolved on success.
|
||||||
*/
|
*/
|
||||||
protected async removeFileById(siteId: string, fileId: string): Promise<void> {
|
protected async removeFileById(siteId: string, fileId: string): Promise<void> {
|
||||||
const db = await CoreSites.getSiteDb(siteId);
|
|
||||||
|
|
||||||
// Get the path to the file first since it relies on the file object stored in the pool.
|
// 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.
|
// Don't use getFilePath to prevent performing 2 DB requests.
|
||||||
let path = this.getFilepoolFolderPath(siteId) + '/' + fileId;
|
let path = this.getFilepoolFolderPath(siteId) + '/' + fileId;
|
||||||
|
@ -2714,7 +2715,7 @@ export class CoreFilepoolProvider {
|
||||||
promises.push(this.filesTables[siteId].delete(conditions));
|
promises.push(this.filesTables[siteId].delete(conditions));
|
||||||
|
|
||||||
// Remove links.
|
// Remove links.
|
||||||
promises.push(db.deleteRecords(LINKS_TABLE_NAME, conditions));
|
promises.push(this.linksTables[siteId].delete(conditions));
|
||||||
|
|
||||||
// Remove the file.
|
// Remove the file.
|
||||||
if (CoreFile.isAvailable()) {
|
if (CoreFile.isAvailable()) {
|
||||||
|
@ -2745,8 +2746,7 @@ export class CoreFilepoolProvider {
|
||||||
* @return Resolved on success.
|
* @return Resolved on success.
|
||||||
*/
|
*/
|
||||||
async removeFilesByComponent(siteId: string, component: string, componentId?: string | number): Promise<void> {
|
async removeFilesByComponent(siteId: string, component: string, componentId?: string | number): Promise<void> {
|
||||||
const db = await CoreSites.getSiteDb(siteId);
|
const items = await this.getComponentFiles(siteId, component, componentId);
|
||||||
const items = await this.getComponentFiles(db, component, componentId);
|
|
||||||
|
|
||||||
await Promise.all(items.map((item) => this.removeFileById(siteId, item.fileId)));
|
await Promise.all(items.map((item) => this.removeFileById(siteId, item.fileId)));
|
||||||
}
|
}
|
||||||
|
@ -2795,11 +2795,10 @@ export class CoreFilepoolProvider {
|
||||||
componentId = this.fixComponentId(componentId);
|
componentId = this.fixComponentId(componentId);
|
||||||
this.logger.debug(`Set previous status for package ${component} ${componentId}`);
|
this.logger.debug(`Set previous status for package ${component} ${componentId}`);
|
||||||
|
|
||||||
const site = await CoreSites.getSite(siteId);
|
|
||||||
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(PACKAGES_TABLE_NAME, { id: packageId });
|
const entry = await this.packagesTables[siteId].getOneByPrimaryKey({ 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.
|
||||||
|
@ -2809,9 +2808,9 @@ 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(PACKAGES_TABLE_NAME, newData, { id: packageId });
|
await this.packagesTables[siteId].update(newData, { id: packageId });
|
||||||
// Success updating, trigger event.
|
// Success updating, trigger event.
|
||||||
this.triggerPackageStatusChanged(site.getId(), newData.status, component, componentId);
|
this.triggerPackageStatusChanged(siteId, newData.status, component, componentId);
|
||||||
|
|
||||||
return newData.status;
|
return newData.status;
|
||||||
}
|
}
|
||||||
|
@ -2900,7 +2899,6 @@ export class CoreFilepoolProvider {
|
||||||
this.logger.debug(`Set status '${status}' for package ${component} ${componentId}`);
|
this.logger.debug(`Set status '${status}' for package ${component} ${componentId}`);
|
||||||
componentId = this.fixComponentId(componentId);
|
componentId = this.fixComponentId(componentId);
|
||||||
|
|
||||||
const site = await CoreSites.getSite(siteId);
|
|
||||||
const packageId = this.getPackageId(component, componentId);
|
const packageId = this.getPackageId(component, componentId);
|
||||||
let downloadTime: number | undefined;
|
let downloadTime: number | undefined;
|
||||||
let previousDownloadTime: number | undefined;
|
let previousDownloadTime: number | undefined;
|
||||||
|
@ -2913,7 +2911,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 = await site.getDb().getRecord<CoreFilepoolPackageEntry>(PACKAGES_TABLE_NAME, { id: packageId });
|
const entry = await this.packagesTables[siteId].getOneByPrimaryKey({ id: packageId });
|
||||||
|
|
||||||
extra = extra ?? entry.extra;
|
extra = extra ?? entry.extra;
|
||||||
if (downloadTime === undefined) {
|
if (downloadTime === undefined) {
|
||||||
|
@ -2930,7 +2928,12 @@ export class CoreFilepoolProvider {
|
||||||
// No previous status.
|
// No previous status.
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageEntry: CoreFilepoolPackageEntry = {
|
if (previousStatus === status) {
|
||||||
|
// The package already has this status, no need to change it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.packagesTables[siteId].insert({
|
||||||
id: packageId,
|
id: packageId,
|
||||||
component,
|
component,
|
||||||
componentId,
|
componentId,
|
||||||
|
@ -2940,14 +2943,7 @@ export class CoreFilepoolProvider {
|
||||||
downloadTime,
|
downloadTime,
|
||||||
previousDownloadTime,
|
previousDownloadTime,
|
||||||
extra,
|
extra,
|
||||||
};
|
});
|
||||||
|
|
||||||
if (previousStatus === status) {
|
|
||||||
// The package already has this status, no need to change it.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
@ -3067,11 +3063,9 @@ export class CoreFilepoolProvider {
|
||||||
async updatePackageDownloadTime(siteId: string, component: string, componentId?: string | number): Promise<void> {
|
async updatePackageDownloadTime(siteId: string, component: string, componentId?: string | number): Promise<void> {
|
||||||
componentId = this.fixComponentId(componentId);
|
componentId = this.fixComponentId(componentId);
|
||||||
|
|
||||||
const site = await CoreSites.getSite(siteId);
|
|
||||||
const packageId = this.getPackageId(component, componentId);
|
const packageId = this.getPackageId(component, componentId);
|
||||||
|
|
||||||
await site.getDb().updateRecords(
|
await this.packagesTables[siteId].update(
|
||||||
PACKAGES_TABLE_NAME,
|
|
||||||
{ downloadTime: CoreTimeUtils.timestamp() },
|
{ downloadTime: CoreTimeUtils.timestamp() },
|
||||||
{ id: packageId },
|
{ id: packageId },
|
||||||
);
|
);
|
||||||
|
|
|
@ -41,10 +41,8 @@ import {
|
||||||
APP_SCHEMA,
|
APP_SCHEMA,
|
||||||
SCHEMA_VERSIONS_TABLE_SCHEMA,
|
SCHEMA_VERSIONS_TABLE_SCHEMA,
|
||||||
SITES_TABLE_NAME,
|
SITES_TABLE_NAME,
|
||||||
CURRENT_SITE_TABLE_NAME,
|
|
||||||
SCHEMA_VERSIONS_TABLE_NAME,
|
SCHEMA_VERSIONS_TABLE_NAME,
|
||||||
SiteDBEntry,
|
SiteDBEntry,
|
||||||
CurrentSiteDBEntry,
|
|
||||||
SchemaVersionsDBEntry,
|
SchemaVersionsDBEntry,
|
||||||
} from '@services/database/sites';
|
} from '@services/database/sites';
|
||||||
import { CoreArray } from '../singletons/array';
|
import { CoreArray } from '../singletons/array';
|
||||||
|
@ -59,7 +57,13 @@ import { CoreAjaxWSError } from '@classes/errors/ajaxwserror';
|
||||||
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
||||||
import { CorePromisedValue } from '@classes/promised-value';
|
import { CorePromisedValue } from '@classes/promised-value';
|
||||||
import { CoreDatabaseTable } from '@classes/database/database-table';
|
import { CoreDatabaseTable } from '@classes/database/database-table';
|
||||||
import { CoreDatabaseConfiguration, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
|
import {
|
||||||
|
CoreDatabaseCachingStrategy,
|
||||||
|
CoreDatabaseConfiguration,
|
||||||
|
CoreDatabaseTableProxy,
|
||||||
|
} from '@classes/database/database-table-proxy';
|
||||||
|
import { asyncInstance, AsyncInstance } from '../utils/async-instance';
|
||||||
|
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');
|
||||||
|
|
||||||
|
@ -84,14 +88,11 @@ export class CoreSitesProvider {
|
||||||
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 } = {};
|
||||||
|
|
||||||
// Variables for DB.
|
|
||||||
protected appDB: Promise<SQLiteDB>;
|
|
||||||
protected resolveAppDB!: (appDB: SQLiteDB) => void;
|
|
||||||
protected siteTables: Record<string, Record<string, CorePromisedValue<CoreDatabaseTable>>> = {};
|
protected siteTables: Record<string, Record<string, CorePromisedValue<CoreDatabaseTable>>> = {};
|
||||||
|
protected schemasTables: Record<string, AsyncInstance<CoreDatabaseTable<SchemaVersionsDBEntry, 'name'>>> = {};
|
||||||
|
protected sitesTable = asyncInstance<CoreDatabaseTable<SiteDBEntry>>();
|
||||||
|
|
||||||
constructor(@Optional() @Inject(CORE_SITE_SCHEMAS) siteSchemas: CoreSiteSchema[][] = []) {
|
constructor(@Optional() @Inject(CORE_SITE_SCHEMAS) siteSchemas: CoreSiteSchema[][] = []) {
|
||||||
this.appDB = new Promise(resolve => this.resolveAppDB = resolve);
|
|
||||||
this.logger = CoreLogger.getInstance('CoreSitesProvider');
|
this.logger = CoreLogger.getInstance('CoreSitesProvider');
|
||||||
this.siteSchemas = CoreArray.flatten(siteSchemas).reduce(
|
this.siteSchemas = CoreArray.flatten(siteSchemas).reduce(
|
||||||
(siteSchemas, schema) => {
|
(siteSchemas, schema) => {
|
||||||
|
@ -132,7 +133,15 @@ export class CoreSitesProvider {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resolveAppDB(CoreApp.getDB());
|
const sitesTable = new CoreDatabaseTableProxy<SiteDBEntry>(
|
||||||
|
{ cachingStrategy: CoreDatabaseCachingStrategy.Eager },
|
||||||
|
CoreApp.getDB(),
|
||||||
|
SITES_TABLE_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
await sitesTable.initialize();
|
||||||
|
|
||||||
|
this.sitesTable.setInstance(sitesTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -743,8 +752,7 @@ export class CoreSitesProvider {
|
||||||
config?: CoreSiteConfig,
|
config?: CoreSiteConfig,
|
||||||
oauthId?: number,
|
oauthId?: number,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const db = await this.appDB;
|
await this.sitesTable.insert({
|
||||||
const entry: SiteDBEntry = {
|
|
||||||
id,
|
id,
|
||||||
siteUrl,
|
siteUrl,
|
||||||
token,
|
token,
|
||||||
|
@ -753,9 +761,7 @@ export class CoreSitesProvider {
|
||||||
config: config ? JSON.stringify(config) : undefined,
|
config: config ? JSON.stringify(config) : undefined,
|
||||||
loggedOut: 0,
|
loggedOut: 0,
|
||||||
oauthId,
|
oauthId,
|
||||||
};
|
});
|
||||||
|
|
||||||
await db.insertRecord(SITES_TABLE_NAME, entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -982,9 +988,7 @@ export class CoreSitesProvider {
|
||||||
delete this.sites[siteId];
|
delete this.sites[siteId];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = await this.appDB;
|
await this.sitesTable.deleteByPrimaryKey({ id: siteId });
|
||||||
|
|
||||||
await db.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.
|
||||||
}
|
}
|
||||||
|
@ -1001,10 +1005,9 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved with true if there are sites and false if there aren't.
|
* @return Promise resolved with true if there are sites and false if there aren't.
|
||||||
*/
|
*/
|
||||||
async hasSites(): Promise<boolean> {
|
async hasSites(): Promise<boolean> {
|
||||||
const db = await this.appDB;
|
const isEmpty = await this.sitesTable.isEmpty();
|
||||||
const count = await db.countRecords(SITES_TABLE_NAME);
|
|
||||||
|
|
||||||
return count > 0;
|
return !isEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1026,9 +1029,8 @@ export class CoreSitesProvider {
|
||||||
return this.sites[siteId];
|
return this.sites[siteId];
|
||||||
} else {
|
} else {
|
||||||
// Retrieve and create the site.
|
// Retrieve and create the site.
|
||||||
const db = await this.appDB;
|
|
||||||
try {
|
try {
|
||||||
const data = await db.getRecord<SiteDBEntry>(SITES_TABLE_NAME, { id: siteId });
|
const data = await this.sitesTable.getOneByPrimaryKey({ id: siteId });
|
||||||
|
|
||||||
return this.makeSiteFromSiteListEntry(data);
|
return this.makeSiteFromSiteListEntry(data);
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -1044,8 +1046,7 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved with the site.
|
* @return Promise resolved with the site.
|
||||||
*/
|
*/
|
||||||
async getSiteByUrl(siteUrl: string): Promise<CoreSite> {
|
async getSiteByUrl(siteUrl: string): Promise<CoreSite> {
|
||||||
const db = await this.appDB;
|
const data = await this.sitesTable.getOne({ siteUrl });
|
||||||
const data = await db.getRecord<SiteDBEntry>(SITES_TABLE_NAME, { siteUrl });
|
|
||||||
|
|
||||||
if (this.sites[data.id] !== undefined) {
|
if (this.sites[data.id] !== undefined) {
|
||||||
return this.sites[data.id];
|
return this.sites[data.id];
|
||||||
|
@ -1131,8 +1132,7 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved when the sites are retrieved.
|
* @return Promise resolved when the sites are retrieved.
|
||||||
*/
|
*/
|
||||||
async getSites(ids?: string[]): Promise<CoreSiteBasicInfo[]> {
|
async getSites(ids?: string[]): Promise<CoreSiteBasicInfo[]> {
|
||||||
const db = await this.appDB;
|
const sites = await this.sitesTable.getMany();
|
||||||
const sites = await db.getAllRecords<SiteDBEntry>(SITES_TABLE_NAME);
|
|
||||||
|
|
||||||
const formattedSites: CoreSiteBasicInfo[] = [];
|
const formattedSites: CoreSiteBasicInfo[] = [];
|
||||||
sites.forEach((site) => {
|
sites.forEach((site) => {
|
||||||
|
@ -1197,8 +1197,7 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved when the sites IDs are retrieved.
|
* @return Promise resolved when the sites IDs are retrieved.
|
||||||
*/
|
*/
|
||||||
async getLoggedInSitesIds(): Promise<string[]> {
|
async getLoggedInSitesIds(): Promise<string[]> {
|
||||||
const db = await this.appDB;
|
const sites = await this.sitesTable.getMany({ loggedOut : 0 });
|
||||||
const sites = await db.getRecords<SiteDBEntry>(SITES_TABLE_NAME, { loggedOut : 0 });
|
|
||||||
|
|
||||||
return sites.map((site) => site.id);
|
return sites.map((site) => site.id);
|
||||||
}
|
}
|
||||||
|
@ -1209,8 +1208,7 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved when the sites IDs are retrieved.
|
* @return Promise resolved when the sites IDs are retrieved.
|
||||||
*/
|
*/
|
||||||
async getSitesIds(): Promise<string[]> {
|
async getSitesIds(): Promise<string[]> {
|
||||||
const db = await this.appDB;
|
const sites = await this.sitesTable.getMany();
|
||||||
const sites = await db.getAllRecords<SiteDBEntry>(SITES_TABLE_NAME);
|
|
||||||
|
|
||||||
return sites.map((site) => site.id);
|
return sites.map((site) => site.id);
|
||||||
}
|
}
|
||||||
|
@ -1233,13 +1231,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> {
|
||||||
const db = await this.appDB;
|
await CoreConfig.set('current_site_id', siteId);
|
||||||
const entry = {
|
|
||||||
id: 1,
|
|
||||||
siteId,
|
|
||||||
};
|
|
||||||
|
|
||||||
await db.insertRecord(CURRENT_SITE_TABLE_NAME, entry);
|
|
||||||
|
|
||||||
CoreEvents.trigger(CoreEvents.LOGIN, {}, siteId);
|
CoreEvents.trigger(CoreEvents.LOGIN, {}, siteId);
|
||||||
}
|
}
|
||||||
|
@ -1308,13 +1300,10 @@ export class CoreSitesProvider {
|
||||||
return Promise.reject(new CoreError('Session already restored.'));
|
return Promise.reject(new CoreError('Session already restored.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = await this.appDB;
|
|
||||||
|
|
||||||
this.sessionRestored = true;
|
this.sessionRestored = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const currentSite = await db.getRecord<CurrentSiteDBEntry>(CURRENT_SITE_TABLE_NAME, { id: 1 });
|
const siteId = await this.getStoredCurrentSiteId();
|
||||||
const siteId = currentSite.siteId;
|
|
||||||
this.logger.debug(`Restore session in site ${siteId}`);
|
this.logger.debug(`Restore session in site ${siteId}`);
|
||||||
|
|
||||||
await this.loadSite(siteId);
|
await this.loadSite(siteId);
|
||||||
|
@ -1330,12 +1319,11 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async setSiteLoggedOut(siteId: string): Promise<void> {
|
protected async setSiteLoggedOut(siteId: string): Promise<void> {
|
||||||
const db = await this.appDB;
|
|
||||||
const site = await this.getSite(siteId);
|
const site = await this.getSite(siteId);
|
||||||
|
|
||||||
site.setLoggedOut(true);
|
site.setLoggedOut(true);
|
||||||
|
|
||||||
await db.updateRecords(SITES_TABLE_NAME, { loggedOut: 1 }, { id: siteId });
|
await this.sitesTable.update({ loggedOut: 1 }, { id: siteId });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1371,19 +1359,20 @@ export class CoreSitesProvider {
|
||||||
* @return A promise resolved when the site is updated.
|
* @return A promise resolved when the site is updated.
|
||||||
*/
|
*/
|
||||||
async updateSiteTokenBySiteId(siteId: string, token: string, privateToken: string = ''): Promise<void> {
|
async updateSiteTokenBySiteId(siteId: string, token: string, privateToken: string = ''): Promise<void> {
|
||||||
const db = await this.appDB;
|
|
||||||
const site = await this.getSite(siteId);
|
const site = await this.getSite(siteId);
|
||||||
const newValues: Partial<SiteDBEntry> = {
|
|
||||||
token,
|
|
||||||
privateToken,
|
|
||||||
loggedOut: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
site.token = token;
|
site.token = token;
|
||||||
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 db.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId });
|
await this.sitesTable.update(
|
||||||
|
{
|
||||||
|
token,
|
||||||
|
privateToken,
|
||||||
|
loggedOut: 0,
|
||||||
|
},
|
||||||
|
{ id: siteId },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1425,9 +1414,7 @@ export class CoreSitesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = await this.appDB;
|
await this.sitesTable.update(newValues, { id: siteId });
|
||||||
|
|
||||||
await db.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId });
|
|
||||||
} finally {
|
} finally {
|
||||||
CoreEvents.trigger(CoreEvents.SITE_UPDATED, info, siteId);
|
CoreEvents.trigger(CoreEvents.SITE_UPDATED, info, siteId);
|
||||||
}
|
}
|
||||||
|
@ -1484,8 +1471,7 @@ export class CoreSitesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = await this.appDB;
|
const siteEntries = await this.sitesTable.getMany();
|
||||||
const siteEntries = await db.getAllRecords<SiteDBEntry>(SITES_TABLE_NAME);
|
|
||||||
const ids: string[] = [];
|
const ids: string[] = [];
|
||||||
const promises: Promise<unknown>[] = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
|
@ -1516,10 +1502,9 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved with the site ID.
|
* @return Promise resolved with the site ID.
|
||||||
*/
|
*/
|
||||||
async getStoredCurrentSiteId(): Promise<string> {
|
async getStoredCurrentSiteId(): Promise<string> {
|
||||||
const db = await this.appDB;
|
await this.migrateCurrentSiteLegacyTable();
|
||||||
const currentSite = await db.getRecord<CurrentSiteDBEntry>(CURRENT_SITE_TABLE_NAME, { id: 1 });
|
|
||||||
|
|
||||||
return currentSite.siteId;
|
return CoreConfig.get('current_site_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1528,9 +1513,7 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async removeStoredCurrentSite(): Promise<void> {
|
async removeStoredCurrentSite(): Promise<void> {
|
||||||
const db = await this.appDB;
|
await CoreConfig.delete('current_site_id');
|
||||||
|
|
||||||
await db.deleteRecords(CURRENT_SITE_TABLE_NAME, { id: 1 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1645,10 +1628,8 @@ export class CoreSitesProvider {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async applySiteSchemas(site: CoreSite, schemas: {[name: string]: CoreRegisteredSiteSchema}): Promise<void> {
|
protected async applySiteSchemas(site: CoreSite, schemas: {[name: string]: CoreRegisteredSiteSchema}): Promise<void> {
|
||||||
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_NAME);
|
const records = await this.getSiteSchemasTable(site).getMany();
|
||||||
|
|
||||||
const versions: {[name: string]: number} = {};
|
const versions: {[name: string]: number} = {};
|
||||||
records.forEach((record) => {
|
records.forEach((record) => {
|
||||||
|
@ -1695,7 +1676,7 @@ export class CoreSitesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set installed version.
|
// Set installed version.
|
||||||
await db.insertRecord(SCHEMA_VERSIONS_TABLE_NAME, { name: schema.name, version: schema.version });
|
await this.getSiteSchemasTable(site).insert({ name: schema.name, version: schema.version });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1794,6 +1775,47 @@ export class CoreSitesProvider {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate the legacy current_site table.
|
||||||
|
*/
|
||||||
|
protected async migrateCurrentSiteLegacyTable(): Promise<void> {
|
||||||
|
if (await CoreConfig.has('current_site_migrated')) {
|
||||||
|
// Already migrated.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const db = CoreApp.getDB();
|
||||||
|
|
||||||
|
const { siteId } = await db.getRecord<{ siteId: string }>('current_site');
|
||||||
|
|
||||||
|
await CoreConfig.set('current_site_id', siteId);
|
||||||
|
await CoreApp.deleteTableSchema('current_site');
|
||||||
|
await db.dropTable('current_site');
|
||||||
|
} finally {
|
||||||
|
await CoreConfig.set('current_site_migrated', 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get schemas table for the given site.
|
||||||
|
*
|
||||||
|
* @param site Site.
|
||||||
|
* @returns Scehmas Table.
|
||||||
|
*/
|
||||||
|
protected getSiteSchemasTable(site: CoreSite): AsyncInstance<CoreDatabaseTable<SchemaVersionsDBEntry, 'name'>> {
|
||||||
|
this.schemasTables[site.getId()] = this.schemasTables[site.getId()] ?? asyncInstance(
|
||||||
|
() => this.getSiteTable(SCHEMA_VERSIONS_TABLE_NAME, {
|
||||||
|
siteId: site.getId(),
|
||||||
|
database: site.getDb(),
|
||||||
|
config: { cachingStrategy: CoreDatabaseCachingStrategy.Eager },
|
||||||
|
primaryKeyColumns: ['name'],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.schemasTables[site.getId()];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreSites = makeSingleton(CoreSitesProvider);
|
export const CoreSites = makeSingleton(CoreSitesProvider);
|
||||||
|
|
Loading…
Reference in New Issue