2018-03-13 01:40:43 +02:00
const ItemChange = require ( 'lib/models/ItemChange' ) ;
const NoteResource = require ( 'lib/models/NoteResource' ) ;
const Note = require ( 'lib/models/Note' ) ;
2018-03-15 20:08:46 +02:00
const Resource = require ( 'lib/models/Resource' ) ;
2018-03-13 01:40:43 +02:00
const BaseModel = require ( 'lib/BaseModel' ) ;
2018-03-15 20:08:46 +02:00
const BaseService = require ( 'lib/services/BaseService' ) ;
2019-04-21 14:49:40 +02:00
const SearchEngine = require ( 'lib/services/SearchEngine' ) ;
2018-12-10 02:39:31 +02:00
const Setting = require ( 'lib/models/Setting' ) ;
2018-03-16 16:32:47 +02:00
const { shim } = require ( 'lib/shim' ) ;
2019-01-14 21:11:54 +02:00
const ItemChangeUtils = require ( 'lib/services/ItemChangeUtils' ) ;
2019-04-21 14:49:40 +02:00
const { sprintf } = require ( 'sprintf-js' ) ;
2018-03-13 01:40:43 +02:00
2018-03-15 20:08:46 +02:00
class ResourceService extends BaseService {
2018-03-13 01:40:43 +02:00
async indexNoteResources ( ) {
2018-03-15 20:08:46 +02:00
this . logger ( ) . info ( 'ResourceService::indexNoteResources: Start' ) ;
await ItemChange . waitForAllSaved ( ) ;
2018-03-13 01:40:43 +02:00
2019-04-21 14:49:40 +02:00
let foundNoteWithEncryption = false ;
2018-03-13 01:40:43 +02:00
while ( true ) {
const changes = await ItemChange . modelSelectAll ( `
2018-03-15 20:08:46 +02:00
SELECT id , item _id , type
2018-03-13 01:40:43 +02:00
FROM item _changes
WHERE item _type = ?
AND id > ?
2018-03-15 20:08:46 +02:00
ORDER BY id ASC
2019-06-29 00:49:43 +02:00
LIMIT 10
2018-12-10 02:39:31 +02:00
` , [BaseModel.TYPE_NOTE, Setting.value('resourceService.lastProcessedChangeId')]);
2018-03-13 01:40:43 +02:00
if ( ! changes . length ) break ;
const noteIds = changes . map ( a => a . item _id ) ;
2019-04-21 14:49:40 +02:00
const notes = await Note . modelSelectAll ( 'SELECT id, title, body, encryption_applied FROM notes WHERE id IN ("' + noteIds . join ( '","' ) + '")' ) ;
2018-03-13 01:40:43 +02:00
2018-03-15 20:08:46 +02:00
const noteById = ( noteId ) => {
for ( let i = 0 ; i < notes . length ; i ++ ) {
if ( notes [ i ] . id === noteId ) return notes [ i ] ;
}
2018-11-21 21:50:50 +02:00
// The note may have been deleted since the change was recorded. For example in this case:
// - Note created (Some Change object is recorded)
// - Note is deleted
// - ResourceService indexer runs.
// In that case, there will be a change for the note, but the note will be gone.
return null ;
2018-03-15 20:08:46 +02:00
}
for ( let i = 0 ; i < changes . length ; i ++ ) {
const change = changes [ i ] ;
2018-03-13 01:40:43 +02:00
if ( change . type === ItemChange . TYPE _CREATE || change . type === ItemChange . TYPE _UPDATE ) {
2018-03-15 20:08:46 +02:00
const note = noteById ( change . item _id ) ;
2019-04-21 14:49:40 +02:00
if ( ! ! note . encryption _applied ) {
// If we hit an encrypted note, abort processing for now.
// Note will eventually get decrypted and processing can resume then.
// This is a limitation of the change tracking system - we cannot skip a change
// and keep processing the rest since we only keep track of "lastProcessedChangeId".
foundNoteWithEncryption = true ;
break ;
}
2018-11-21 21:50:50 +02:00
if ( note ) {
2019-04-21 14:49:40 +02:00
await this . setAssociatedResources _ ( note ) ;
2018-11-21 21:50:50 +02:00
} else {
this . logger ( ) . warn ( 'ResourceService::indexNoteResources: A change was recorded for a note that has been deleted: ' + change . item _id ) ;
}
2018-03-13 01:40:43 +02:00
} else if ( change . type === ItemChange . TYPE _DELETE ) {
2018-03-15 20:08:46 +02:00
await NoteResource . remove ( change . item _id ) ;
2018-03-13 01:40:43 +02:00
} else {
throw new Error ( 'Invalid change type: ' + change . type ) ;
}
2018-12-10 02:39:31 +02:00
Setting . setValue ( 'resourceService.lastProcessedChangeId' , change . id ) ;
2018-03-13 01:40:43 +02:00
}
2019-04-21 14:49:40 +02:00
if ( foundNoteWithEncryption ) break ;
2018-03-13 01:40:43 +02:00
}
2018-03-15 20:08:46 +02:00
2018-12-10 02:39:31 +02:00
await Setting . saveAll ( ) ;
2018-03-15 20:08:46 +02:00
2018-03-16 19:39:44 +02:00
await NoteResource . addOrphanedResources ( ) ;
2019-01-14 21:11:54 +02:00
await ItemChangeUtils . deleteProcessedChanges ( ) ;
2018-03-15 20:08:46 +02:00
this . logger ( ) . info ( 'ResourceService::indexNoteResources: Completed' ) ;
}
2019-04-21 14:49:40 +02:00
async setAssociatedResources _ ( note ) {
const resourceIds = await Note . linkedResourceIds ( note . body ) ;
await NoteResource . setAssociatedResources ( note . id , resourceIds ) ;
}
2018-03-15 20:08:46 +02:00
async deleteOrphanResources ( expiryDelay = null ) {
2019-05-06 22:35:29 +02:00
if ( expiryDelay === null ) expiryDelay = Setting . value ( 'revisionService.ttlDays' ) * 24 * 60 * 60 * 1000 ;
2018-03-15 20:08:46 +02:00
const resourceIds = await NoteResource . orphanResources ( expiryDelay ) ;
this . logger ( ) . info ( 'ResourceService::deleteOrphanResources:' , resourceIds ) ;
for ( let i = 0 ; i < resourceIds . length ; i ++ ) {
2019-04-21 14:49:40 +02:00
const resourceId = resourceIds [ i ] ;
const results = await SearchEngine . instance ( ) . search ( resourceId ) ;
if ( results . length ) {
const note = await Note . load ( results [ 0 ] . id ) ;
if ( note ) {
this . logger ( ) . info ( sprintf ( 'ResourceService::deleteOrphanResources: Skipping deletion of resource %s because it is still referenced in note %s. Re-indexing note content to fix the issue.' , resourceId , note . id ) ) ;
await this . setAssociatedResources _ ( note ) ;
}
} else {
await Resource . delete ( resourceId ) ;
}
2018-03-15 20:08:46 +02:00
}
}
2019-05-28 23:05:11 +02:00
static async autoSetFileSize ( resourceId , filePath , waitTillExists = true ) {
const itDoes = await shim . fsDriver ( ) . waitTillExists ( filePath , waitTillExists ? 10000 : 0 ) ;
2019-05-19 12:18:44 +02:00
if ( ! itDoes ) {
2019-05-19 13:04:09 +02:00
// this.logger().warn('Trying to set file size on non-existent resource:', resourceId, filePath);
2019-05-19 12:18:44 +02:00
return ;
}
2019-05-12 16:53:42 +02:00
const fileStat = await shim . fsDriver ( ) . stat ( filePath ) ;
await Resource . setFileSizeOnly ( resourceId , fileStat . size ) ;
}
static async autoSetFileSizes ( ) {
const resources = await Resource . needFileSizeSet ( ) ;
for ( const r of resources ) {
2019-05-28 23:05:11 +02:00
await this . autoSetFileSize ( r . id , Resource . fullPath ( r ) , false ) ;
2019-05-12 16:53:42 +02:00
}
}
2018-03-15 20:08:46 +02:00
async maintenance ( ) {
await this . indexNoteResources ( ) ;
await this . deleteOrphanResources ( ) ;
2018-03-13 01:40:43 +02:00
}
2018-03-16 16:32:47 +02:00
static runInBackground ( ) {
if ( this . isRunningInBackground _ ) return ;
this . isRunningInBackground _ = true ;
const service = new ResourceService ( ) ;
setTimeout ( ( ) => {
service . maintenance ( ) ;
} , 1000 * 30 ) ;
shim . setInterval ( ( ) => {
service . maintenance ( ) ;
} , 1000 * 60 * 60 * 4 ) ;
}
2018-03-13 01:40:43 +02:00
}
module . exports = ResourceService ;