diff --git a/.eslintrc.js b/.eslintrc.js index 1b4ee6949..deda33df6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -271,6 +271,7 @@ testsConfig['rules']['padded-blocks'] = [ switches: 'never', }, ]; +testsConfig['rules']['jest/expect-expect'] = 'off'; testsConfig['plugins'].push('jest'); testsConfig['extends'].push('plugin:jest/recommended'); diff --git a/src/core/classes/database/database-table-proxy.ts b/src/core/classes/database/database-table-proxy.ts index 8338df188..393e07ac4 100644 --- a/src/core/classes/database/database-table-proxy.ts +++ b/src/core/classes/database/database-table-proxy.ts @@ -16,6 +16,7 @@ import { CorePromisedValue } from '@classes/promised-value'; import { SQLiteDB, SQLiteDBRecordValues } from '@classes/sqlitedb'; import { CoreDatabaseReducer, CoreDatabaseTable, CoreDatabaseConditions, GetDBRecordPrimaryKey } from './database-table'; import { CoreEagerDatabaseTable } from './eager-database-table'; +import { CoreLazyDatabaseTable } from './lazy-database-table'; /** * Database table proxy used to route database interactions through different implementations. @@ -164,6 +165,8 @@ export class CoreDatabaseTableProxy< switch (cachingStrategy) { case CoreDatabaseCachingStrategy.Eager: return new CoreEagerDatabaseTable(this.database, this.tableName, this.primaryKeyColumns); + case CoreDatabaseCachingStrategy.Lazy: + return new CoreLazyDatabaseTable(this.database, this.tableName, this.primaryKeyColumns); case CoreDatabaseCachingStrategy.None: return new CoreDatabaseTable(this.database, this.tableName, this.primaryKeyColumns); } @@ -183,5 +186,6 @@ export interface CoreDatabaseConfiguration { */ export enum CoreDatabaseCachingStrategy { Eager = 'eager', + Lazy = 'lazy', None = 'none', } diff --git a/src/core/classes/database/lazy-database-table.ts b/src/core/classes/database/lazy-database-table.ts new file mode 100644 index 000000000..cf64623a7 --- /dev/null +++ b/src/core/classes/database/lazy-database-table.ts @@ -0,0 +1,141 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreError } from '@classes/errors/error'; +import { SQLiteDBRecordValues } from '@classes/sqlitedb'; +import { CoreDatabaseTable, CoreDatabaseConditions, GetDBRecordPrimaryKey } from './database-table'; + +/** + * Wrapper used to improve performance by caching records that are used often for faster read operations. + * + * This implementation works best for tables that have a lot of records and are read often; for tables with a few records use + * CoreEagerDatabaseTable instead. + */ +export class CoreLazyDatabaseTable< + DBRecord extends SQLiteDBRecordValues = SQLiteDBRecordValues, + PrimaryKeyColumn extends keyof DBRecord = 'id', + PrimaryKey extends GetDBRecordPrimaryKey = GetDBRecordPrimaryKey +> extends CoreDatabaseTable { + + protected records: Record = {}; + + /** + * @inheritdoc + */ + async find(conditions: Partial): Promise { + let record: DBRecord | null = + Object.values(this.records).find(record => record && this.recordMatches(record, conditions)) ?? null; + + if (!record) { + record = await super.find(conditions); + + this.records[this.serializePrimaryKey(this.getPrimaryKeyFromRecord(record))] = record; + } + + return record; + } + + /** + * @inheritdoc + */ + async findByPrimaryKey(primaryKey: PrimaryKey): Promise { + const serializePrimaryKey = this.serializePrimaryKey(primaryKey); + + if (!(serializePrimaryKey in this.records)) { + try { + const record = await super.findByPrimaryKey(primaryKey); + + this.records[serializePrimaryKey] = record; + + return record; + } catch (error) { + this.records[serializePrimaryKey] = null; + + throw error; + } + } + + const record = this.records[serializePrimaryKey]; + + if (!record) { + throw new CoreError('No records found.'); + } + + return record; + } + + /** + * @inheritdoc + */ + async insert(record: DBRecord): Promise { + await super.insert(record); + + this.records[this.serializePrimaryKey(this.getPrimaryKeyFromRecord(record))] = record; + } + + /** + * @inheritdoc + */ + async update(updates: Partial, conditions?: Partial): Promise { + await super.update(updates, conditions); + + for (const record of Object.values(this.records)) { + if (!record || (conditions && !this.recordMatches(record, conditions))) { + continue; + } + + Object.assign(record, updates); + } + } + + /** + * @inheritdoc + */ + async updateWhere(updates: Partial, conditions: CoreDatabaseConditions): Promise { + await super.updateWhere(updates, conditions); + + for (const record of Object.values(this.records)) { + if (!record || !conditions.js(record)) { + continue; + } + + Object.assign(record, updates); + } + } + + /** + * @inheritdoc + */ + async delete(conditions?: Partial): Promise { + await super.delete(conditions); + + for (const [primaryKey, record] of Object.entries(this.records)) { + if (!record || (conditions && !this.recordMatches(record, conditions))) { + continue; + } + + this.records[primaryKey] = null; + } + } + + /** + * @inheritdoc + */ + async deleteByPrimaryKey(primaryKey: PrimaryKey): Promise { + await super.deleteByPrimaryKey(primaryKey); + + this.records[this.serializePrimaryKey(primaryKey)] = null; + } + +} diff --git a/src/core/classes/tests/database-table.test.ts b/src/core/classes/tests/database-table.test.ts index f87589fc7..4d02603d2 100644 --- a/src/core/classes/tests/database-table.test.ts +++ b/src/core/classes/tests/database-table.test.ts @@ -13,6 +13,7 @@ // limitations under the License. import { mock } from '@/testing/utils'; +import { CoreDatabaseTable } from '@classes/database/database-table'; import { CoreDatabaseCachingStrategy, CoreDatabaseConfiguration, @@ -30,7 +31,7 @@ function userMatches(user: User, conditions: Partial) { return !Object.entries(conditions).some(([column, value]) => user[column] !== value); } -function prepareStubs(config: Partial = {}): [User[], SQLiteDB, CoreDatabaseTableProxy] { +function prepareStubs(config: Partial = {}): [User[], SQLiteDB, CoreDatabaseTable] { const records: User[] = []; const database = mock({ getRecord: async (_, conditions) => { @@ -68,11 +69,84 @@ function prepareStubs(config: Partial = {}): [User[], return [records, database, table]; } +async function testFindItems(records: User[], table: CoreDatabaseTable) { + const john = { id: 1, name: 'John', surname: 'Doe' }; + const amy = { id: 2, name: 'Amy', surname: 'Doe' }; + + records.push(john); + records.push(amy); + + await table.initialize(); + + await expect(table.findByPrimaryKey({ id: 1 })).resolves.toEqual(john); + await expect(table.findByPrimaryKey({ id: 2 })).resolves.toEqual(amy); + await expect(table.find({ surname: 'Doe', name: 'John' })).resolves.toEqual(john); + await expect(table.find({ surname: 'Doe', name: 'Amy' })).resolves.toEqual(amy); +} + +async function testInsertItems(records: User[], database: SQLiteDB, table: CoreDatabaseTable) { + // Arrange. + const john = { id: 1, name: 'John', surname: 'Doe' }; + + await table.initialize(); + + // Act. + await table.insert(john); + + // Assert. + expect(database.insertRecord).toHaveBeenCalledWith('users', john); + + await expect(table.findByPrimaryKey({ id: 1 })).resolves.toEqual(john); +} + +async function testDeleteItems(records: User[], database: SQLiteDB, table: CoreDatabaseTable) { + // 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' }; + + records.push(john); + records.push(amy); + records.push(jane); + + await table.initialize(); + + // Act. + await table.delete({ surname: 'Doe' }); + + // Assert. + expect(database.deleteRecords).toHaveBeenCalledWith('users', { surname: 'Doe' }); + + await expect(table.findByPrimaryKey({ id: 1 })).rejects.toThrow(); + await expect(table.findByPrimaryKey({ id: 2 })).rejects.toThrow(); + await expect(table.findByPrimaryKey({ id: 3 })).resolves.toEqual(jane); +} + +async function testDeleteItemsByPrimaryKey(records: User[], database: SQLiteDB, table: CoreDatabaseTable) { + // Arrange. + const john = { id: 1, name: 'John', surname: 'Doe' }; + const amy = { id: 2, name: 'Amy', surname: 'Doe' }; + + records.push(john); + records.push(amy); + + await table.initialize(); + + // Act. + await table.deleteByPrimaryKey({ id: 1 }); + + // Assert. + expect(database.deleteRecords).toHaveBeenCalledWith('users', { id: 1 }); + + await expect(table.findByPrimaryKey({ id: 1 })).rejects.toThrow(); + await expect(table.findByPrimaryKey({ id: 2 })).resolves.toEqual(amy); +} + describe('CoreDatabaseTable with eager caching', () => { let records: User[]; let database: SQLiteDB; - let table: CoreDatabaseTableProxy; + let table: CoreDatabaseTable; beforeEach(() => [records, database, table] = prepareStubs({ cachingStrategy: CoreDatabaseCachingStrategy.Eager })); @@ -83,79 +157,41 @@ describe('CoreDatabaseTable with eager caching', () => { }); it('finds items', async () => { - const john = { id: 1, name: 'John', surname: 'Doe' }; - const amy = { id: 2, name: 'Amy', surname: 'Doe' }; - - records.push(john); - records.push(amy); - - await table.initialize(); - - await expect(table.findByPrimaryKey({ id: 1 })).resolves.toEqual(john); - await expect(table.findByPrimaryKey({ id: 2 })).resolves.toEqual(amy); - await expect(table.find({ surname: 'Doe', name: 'John' })).resolves.toEqual(john); - await expect(table.find({ surname: 'Doe', name: 'Amy' })).resolves.toEqual(amy); + await testFindItems(records, table); expect(database.getRecord).not.toHaveBeenCalled(); }); - it('inserts items', async () => { - // Arrange. - const john = { id: 1, name: 'John', surname: 'Doe' }; + it('inserts items', () => testInsertItems(records, database, table)); + it('deletes items', () => testDeleteItems(records, database, table)); + it('deletes items by primary key', () => testDeleteItemsByPrimaryKey(records, database, table)); +}); + +describe('CoreDatabaseTable with lazy caching', () => { + + let records: User[]; + let database: SQLiteDB; + let table: CoreDatabaseTable; + + beforeEach(() => [records, database, table] = prepareStubs({ cachingStrategy: CoreDatabaseCachingStrategy.Lazy })); + + it('reads no records on initialization', async () => { await table.initialize(); - // Act. - await table.insert(john); - - // Assert. - expect(database.insertRecord).toHaveBeenCalledWith('users', john); - - await expect(table.findByPrimaryKey({ id: 1 })).resolves.toEqual(john); + expect(database.getRecords).not.toHaveBeenCalled(); + expect(database.getAllRecords).not.toHaveBeenCalled(); }); - it('deletes 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' }; + it('finds items', async () => { + await testFindItems(records, table); - records.push(john); - records.push(amy); - records.push(jane); - - await table.initialize(); - - // Act. - await table.delete({ surname: 'Doe' }); - - // Assert. - expect(database.deleteRecords).toHaveBeenCalledWith('users', { surname: 'Doe' }); - - await expect(table.findByPrimaryKey({ id: 1 })).rejects.toThrow(); - await expect(table.findByPrimaryKey({ id: 2 })).rejects.toThrow(); - await expect(table.findByPrimaryKey({ id: 3 })).resolves.toEqual(jane); + expect(database.getRecord).toHaveBeenCalledTimes(2); }); - it('deletes items by primary key', async () => { - // Arrange. - const john = { id: 1, name: 'John', surname: 'Doe' }; - const amy = { id: 2, name: 'Amy', surname: 'Doe' }; - - records.push(john); - records.push(amy); - - await table.initialize(); - - // Act. - await table.deleteByPrimaryKey({ id: 1 }); - - // Assert. - expect(database.deleteRecords).toHaveBeenCalledWith('users', { id: 1 }); - - await expect(table.findByPrimaryKey({ id: 1 })).rejects.toThrow(); - await expect(table.findByPrimaryKey({ id: 2 })).resolves.toEqual(amy); - }); + it('inserts items', () => testInsertItems(records, database, table)); + it('deletes items', () => testDeleteItems(records, database, table)); + it('deletes items by primary key', () => testDeleteItemsByPrimaryKey(records, database, table)); }); @@ -163,7 +199,7 @@ describe('CoreDatabaseTable with no caching', () => { let records: User[]; let database: SQLiteDB; - let table: CoreDatabaseTableProxy; + let table: CoreDatabaseTable; beforeEach(() => [records, database, table] = prepareStubs({ cachingStrategy: CoreDatabaseCachingStrategy.None })); @@ -175,78 +211,13 @@ describe('CoreDatabaseTable with no caching', () => { }); it('finds items', async () => { - const john = { id: 1, name: 'John', surname: 'Doe' }; - const amy = { id: 2, name: 'Amy', surname: 'Doe' }; - - records.push(john); - records.push(amy); - - await table.initialize(); - - await expect(table.findByPrimaryKey({ id: 1 })).resolves.toEqual(john); - await expect(table.findByPrimaryKey({ id: 2 })).resolves.toEqual(amy); - await expect(table.find({ surname: 'Doe', name: 'John' })).resolves.toEqual(john); - await expect(table.find({ surname: 'Doe', name: 'Amy' })).resolves.toEqual(amy); + await testFindItems(records, table); expect(database.getRecord).toHaveBeenCalledTimes(4); }); - it('inserts items', async () => { - // Arrange. - const john = { id: 1, name: 'John', surname: 'Doe' }; - - await table.initialize(); - - // Act. - await table.insert(john); - - // Assert. - expect(database.insertRecord).toHaveBeenCalledWith('users', john); - - await expect(table.findByPrimaryKey({ id: 1 })).resolves.toEqual(john); - }); - - it('deletes 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' }; - - records.push(john); - records.push(amy); - records.push(jane); - - await table.initialize(); - - // Act. - await table.delete({ surname: 'Doe' }); - - // Assert. - expect(database.deleteRecords).toHaveBeenCalledWith('users', { surname: 'Doe' }); - - await expect(table.findByPrimaryKey({ id: 1 })).rejects.toThrow(); - await expect(table.findByPrimaryKey({ id: 2 })).rejects.toThrow(); - await expect(table.findByPrimaryKey({ id: 3 })).resolves.toEqual(jane); - }); - - it('deletes items by primary key', async () => { - // Arrange. - const john = { id: 1, name: 'John', surname: 'Doe' }; - const amy = { id: 2, name: 'Amy', surname: 'Doe' }; - - records.push(john); - records.push(amy); - - await table.initialize(); - - // Act. - await table.deleteByPrimaryKey({ id: 1 }); - - // Assert. - expect(database.deleteRecords).toHaveBeenCalledWith('users', { id: 1 }); - - await expect(table.findByPrimaryKey({ id: 1 })).rejects.toThrow(); - await expect(table.findByPrimaryKey({ id: 2 })).resolves.toEqual(amy); - }); + it('inserts items', () => testInsertItems(records, database, table)); + it('deletes items', () => testDeleteItems(records, database, table)); + it('deletes items by primary key', () => testDeleteItemsByPrimaryKey(records, database, table)); }); diff --git a/src/core/services/filepool.ts b/src/core/services/filepool.ts index 99d5ea855..e3f5bab52 100644 --- a/src/core/services/filepool.ts +++ b/src/core/services/filepool.ts @@ -48,6 +48,9 @@ import { } from '@services/database/filepool'; import { CoreFileHelper } from './file-helper'; import { CoreUrl } from '@singletons/url'; +import { CorePromisedValue } from '@classes/promised-value'; +import { CoreDatabaseTable } from '@classes/database/database-table'; +import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy'; /* * Factory for handling downloading files and retrieve downloaded files. @@ -72,9 +75,13 @@ export class CoreFilepoolProvider { protected static readonly ERR_FS_OR_NETWORK_UNAVAILABLE = 'CoreFilepoolError:ERR_FS_OR_NETWORK_UNAVAILABLE'; protected static readonly ERR_QUEUE_ON_PAUSE = 'CoreFilepoolError:ERR_QUEUE_ON_PAUSE'; - protected static readonly FILE_UPDATE_UNKNOWN_WHERE_CLAUSE = + protected static readonly FILE_IS_UNKNOWN_SQL = 'isexternalfile = 1 OR ((revision IS NULL OR revision = 0) AND (timemodified IS NULL OR timemodified = 0))'; + protected static readonly FILE_IS_UNKNOWN_JS = + ({ isexternalfile, revision, timemodified }: CoreFilepoolFileEntry): boolean => + isexternalfile === 1 || ((revision === null || revision === 0) && (timemodified === null || timemodified === 0)); + protected logger: CoreLogger; protected queueState = CoreFilepoolProvider.QUEUE_PAUSED; protected urlAttributes: RegExp[] = [ @@ -94,6 +101,7 @@ export class CoreFilepoolProvider { // Variables for DB. protected appDB: Promise; protected resolveAppDB!: (appDB: SQLiteDB) => void; + protected filesTables: Record>> = {}; constructor() { this.appDB = new Promise(resolve => this.resolveAppDB = resolve); @@ -114,6 +122,18 @@ export class CoreFilepoolProvider { NgZone.run(() => this.checkQueueProcessing()); }); }); + + CoreEvents.on(CoreEvents.SITE_DELETED, async ({ siteId }) => { + if (!siteId || !(siteId in this.filesTables)) { + return; + } + + const filesTable = await this.filesTables[siteId]; + + delete this.filesTables[siteId]; + + await filesTable.destroy(); + }); } /** @@ -129,6 +149,33 @@ export class CoreFilepoolProvider { this.resolveAppDB(CoreApp.getDB()); } + /** + * Get files table. + * + * @param siteId Site id. + * @returns Files table. + */ + async getFilesTable(siteId?: string): Promise> { + siteId = siteId ?? CoreSites.getCurrentSiteId(); + + if (!(siteId in this.filesTables)) { + const filesTable = this.filesTables[siteId] = new CorePromisedValue(); + const database = await CoreSites.getSiteDb(siteId); + const table = new CoreDatabaseTableProxy( + { cachingStrategy: CoreDatabaseCachingStrategy.Lazy }, + database, + FILES_TABLE_NAME, + ['fileId'], + ); + + await table.initialize(); + + filesTable.resolve(table); + } + + return this.filesTables[siteId]; + } + /** * Link a file with a component. * @@ -215,9 +262,9 @@ export class CoreFilepoolProvider { ...data, }; - const db = await CoreSites.getSiteDb(siteId); + const filesTable = await this.getFilesTable(siteId); - await db.insertRecord(FILES_TABLE_NAME, record); + await filesTable.insert(record); } /** @@ -558,13 +605,14 @@ export class CoreFilepoolProvider { */ async clearFilepool(siteId: string): Promise { const db = await CoreSites.getSiteDb(siteId); + const filesTable = await this.getFilesTable(siteId); // Read the data first to be able to notify the deletions. - const filesEntries = await db.getAllRecords(FILES_TABLE_NAME); + const filesEntries = await filesTable.all(); const filesLinks = await db.getAllRecords(LINKS_TABLE_NAME); await Promise.all([ - db.deleteRecords(FILES_TABLE_NAME), + filesTable.delete(), db.deleteRecords(LINKS_TABLE_NAME), ]); @@ -1119,13 +1167,14 @@ export class CoreFilepoolProvider { } const db = await CoreSites.getSiteDb(siteId); + const filesTable = await this.getFilesTable(siteId); const extension = CoreMimetypeUtils.getFileExtension(entry.path); if (!extension) { // Files does not have extension. Invalidate file (stale = true). // Minor problem: file will remain in the filesystem once downloaded again. this.logger.debug('Staled file with no extension ' + entry.fileId); - await db.updateRecords(FILES_TABLE_NAME, { stale: 1 }, { fileId: entry.fileId }); + await filesTable.update({ stale: 1 }, { fileId: entry.fileId }); return; } @@ -1135,7 +1184,7 @@ export class CoreFilepoolProvider { entry.fileId = CoreMimetypeUtils.removeExtension(fileId); entry.extension = extension; - await db.updateRecords(FILES_TABLE_NAME, entry, { fileId }); + await filesTable.update(entry, { fileId }); if (entry.fileId == fileId) { // File ID hasn't changed, we're done. this.logger.debug('Removed extesion ' + extension + ' from file ' + entry.fileId); @@ -1396,15 +1445,13 @@ export class CoreFilepoolProvider { */ async getFilesByComponent(siteId: string, component: string, componentId?: string | number): Promise { const db = await CoreSites.getSiteDb(siteId); + const filesTable = await this.getFilesTable(siteId); const items = await this.getComponentFiles(db, component, componentId); const files: CoreFilepoolFileEntry[] = []; await Promise.all(items.map(async (item) => { try { - const fileEntry = await db.getRecord( - FILES_TABLE_NAME, - { fileId: item.fileId }, - ); + const fileEntry = await filesTable.findByPrimaryKey({ fileId: item.fileId }); if (!fileEntry) { return; @@ -2137,14 +2184,9 @@ export class CoreFilepoolProvider { * @return Resolved with file object from DB on success, rejected otherwise. */ protected async hasFileInPool(siteId: string, fileId: string): Promise { - const db = await CoreSites.getSiteDb(siteId); - const entry = await db.getRecord(FILES_TABLE_NAME, { fileId }); + const filesTable = await this.getFilesTable(siteId); - if (entry === undefined) { - throw new CoreError('File not found in filepool.'); - } - - return entry; + return filesTable.findByPrimaryKey({ fileId }); } /** @@ -2176,11 +2218,17 @@ export class CoreFilepoolProvider { * @return Resolved on success. */ async invalidateAllFiles(siteId: string, onlyUnknown: boolean = true): Promise { - const db = await CoreSites.getSiteDb(siteId); + const filesTable = await this.getFilesTable(siteId); - const where = onlyUnknown ? CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE : undefined; - - await db.updateRecordsWhere(FILES_TABLE_NAME, { stale: 1 }, where); + onlyUnknown + ? await filesTable.updateWhere( + { stale: 1 }, + { + sql: CoreFilepoolProvider.FILE_IS_UNKNOWN_SQL, + js: CoreFilepoolProvider.FILE_IS_UNKNOWN_JS, + }, + ) + : await filesTable.update({ stale: 1 }); } /** @@ -2199,9 +2247,9 @@ export class CoreFilepoolProvider { const file = await this.fixPluginfileURL(siteId, fileUrl); const fileId = this.getFileIdByUrl(CoreFileHelper.getFileUrl(file)); - const db = await CoreSites.getSiteDb(siteId); + const filesTable = await this.getFilesTable(siteId); - await db.updateRecords(FILES_TABLE_NAME, { stale: 1 }, { fileId }); + await filesTable.update({ stale: 1 }, { fileId }); } /** @@ -2221,7 +2269,7 @@ export class CoreFilepoolProvider { onlyUnknown: boolean = true, ): Promise { const db = await CoreSites.getSiteDb(siteId); - + const filesTable = await this.getFilesTable(siteId); const items = await this.getComponentFiles(db, component, componentId); if (!items.length) { @@ -2236,10 +2284,19 @@ export class CoreFilepoolProvider { whereAndParams.sql = 'fileId ' + whereAndParams.sql; if (onlyUnknown) { - whereAndParams.sql += ' AND (' + CoreFilepoolProvider.FILE_UPDATE_UNKNOWN_WHERE_CLAUSE + ')'; + whereAndParams.sql += ' AND (' + CoreFilepoolProvider.FILE_IS_UNKNOWN_SQL + ')'; } - await db.updateRecordsWhere(FILES_TABLE_NAME, { stale: 1 }, whereAndParams.sql, whereAndParams.params); + await filesTable.updateWhere( + { stale: 1 }, + { + sql: whereAndParams.sql, + sqlParams: whereAndParams.params, + js: record => fileIds.includes(record.fileId) && ( + !onlyUnknown || CoreFilepoolProvider.FILE_IS_UNKNOWN_JS(record) + ), + }, + ); } /** @@ -2657,6 +2714,8 @@ export class CoreFilepoolProvider { */ protected async removeFileById(siteId: string, fileId: string): Promise { const db = await CoreSites.getSiteDb(siteId); + const filesTable = await this.getFilesTable(siteId); + // Get the path to the file first since it relies on the file object stored in the pool. // Don't use getFilePath to prevent performing 2 DB requests. let path = this.getFilepoolFolderPath(siteId) + '/' + fileId; @@ -2682,7 +2741,7 @@ export class CoreFilepoolProvider { const promises: Promise[] = []; // Remove entry from filepool store. - promises.push(db.deleteRecords(FILES_TABLE_NAME, conditions)); + promises.push(filesTable.delete(conditions)); // Remove links. promises.push(db.deleteRecords(LINKS_TABLE_NAME, conditions));