mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-11-04 15:11:31 +08:00 
			
		
		
		
	erasing rows of deleted entities
This commit is contained in:
		
							parent
							
								
									20c7c657da
								
							
						
					
					
						commit
						248fa780e8
					
				@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					UPDATE options SET name = 'eraseNotesAfterTimeInSeconds' WHERE name = 'eraseNotesAfterTimeInSeconds';
 | 
				
			||||||
@ -42,14 +42,14 @@ const TPL = `
 | 
				
			|||||||
<div>
 | 
					<div>
 | 
				
			||||||
    <h4>Note erasure timeout</h4>
 | 
					    <h4>Note erasure timeout</h4>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <p>Deleted notes are at first only marked as deleted and it is possible to recover them 
 | 
					    <p>Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them 
 | 
				
			||||||
    from Recent Notes dialog. After a period of time, deleted notes are "erased" which means 
 | 
					    from Recent Notes dialog. After a period of time, deleted notes are "erased" which means 
 | 
				
			||||||
    their content is not recoverable anymore. This setting allows you to configure the length 
 | 
					    their content is not recoverable anymore. This setting allows you to configure the length 
 | 
				
			||||||
    of the period between deleting and erasing the note.</p>
 | 
					    of the period between deleting and erasing the note.</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="form-group">
 | 
					    <div class="form-group">
 | 
				
			||||||
        <label for="erase-notes-after-time-in-seconds">Erase notes after X seconds</label>
 | 
					        <label for="erase-entities-after-time-in-seconds">Erase notes after X seconds</label>
 | 
				
			||||||
        <input class="form-control" id="erase-notes-after-time-in-seconds" type="number" min="0">
 | 
					        <input class="form-control" id="erase-entities-after-time-in-seconds" type="number" min="0">
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    <p>You can also trigger erasing manually:</p>
 | 
					    <p>You can also trigger erasing manually:</p>
 | 
				
			||||||
@ -111,12 +111,12 @@ export default class ProtectedSessionOptions {
 | 
				
			|||||||
            this.$availableLanguageCodes.text(webContents.session.availableSpellCheckerLanguages.join(', '));
 | 
					            this.$availableLanguageCodes.text(webContents.session.availableSpellCheckerLanguages.join(', '));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$eraseNotesAfterTimeInSeconds = $("#erase-notes-after-time-in-seconds");
 | 
					        this.$eraseEntitiesAfterTimeInSeconds = $("#erase-entities-after-time-in-seconds");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$eraseNotesAfterTimeInSeconds.on('change', () => {
 | 
					        this.$eraseEntitiesAfterTimeInSeconds.on('change', () => {
 | 
				
			||||||
            const eraseNotesAfterTimeInSeconds = this.$eraseNotesAfterTimeInSeconds.val();
 | 
					            const eraseEntitiesAfterTimeInSeconds = this.$eraseEntitiesAfterTimeInSeconds.val();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            server.put('options', { 'eraseNotesAfterTimeInSeconds': eraseNotesAfterTimeInSeconds }).then(() => {
 | 
					            server.put('options', { 'eraseEntitiesAfterTimeInSeconds': eraseEntitiesAfterTimeInSeconds }).then(() => {
 | 
				
			||||||
                toastService.showMessage("Options change have been saved.");
 | 
					                toastService.showMessage("Options change have been saved.");
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -173,7 +173,7 @@ export default class ProtectedSessionOptions {
 | 
				
			|||||||
        this.$spellCheckEnabled.prop("checked", options['spellCheckEnabled'] === 'true');
 | 
					        this.$spellCheckEnabled.prop("checked", options['spellCheckEnabled'] === 'true');
 | 
				
			||||||
        this.$spellCheckLanguageCode.val(options['spellCheckLanguageCode']);
 | 
					        this.$spellCheckLanguageCode.val(options['spellCheckLanguageCode']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$eraseNotesAfterTimeInSeconds.val(options['eraseNotesAfterTimeInSeconds']);
 | 
					        this.$eraseEntitiesAfterTimeInSeconds.val(options['eraseEntitiesAfterTimeInSeconds']);
 | 
				
			||||||
        this.$protectedSessionTimeout.val(options['protectedSessionTimeout']);
 | 
					        this.$protectedSessionTimeout.val(options['protectedSessionTimeout']);
 | 
				
			||||||
        this.$noteRevisionsTimeInterval.val(options['noteRevisionSnapshotTimeInterval']);
 | 
					        this.$noteRevisionsTimeInterval.val(options['noteRevisionSnapshotTimeInterval']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@ const attributes = require('../../services/attributes');
 | 
				
			|||||||
// options allowed to be updated directly in options dialog
 | 
					// options allowed to be updated directly in options dialog
 | 
				
			||||||
const ALLOWED_OPTIONS = new Set([
 | 
					const ALLOWED_OPTIONS = new Set([
 | 
				
			||||||
    'username', // not exposed for update (not harmful anyway), needed for reading
 | 
					    'username', // not exposed for update (not harmful anyway), needed for reading
 | 
				
			||||||
    'eraseNotesAfterTimeInSeconds',
 | 
					    'eraseEntitiesAfterTimeInSeconds',
 | 
				
			||||||
    'protectedSessionTimeout',
 | 
					    'protectedSessionTimeout',
 | 
				
			||||||
    'noteRevisionSnapshotTimeInterval',
 | 
					    'noteRevisionSnapshotTimeInterval',
 | 
				
			||||||
    'zoomFactor',
 | 
					    'zoomFactor',
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@ const build = require('./build');
 | 
				
			|||||||
const packageJson = require('../../package');
 | 
					const packageJson = require('../../package');
 | 
				
			||||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
 | 
					const {TRILIUM_DATA_DIR} = require('./data_dir');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const APP_DB_VERSION = 173;
 | 
					const APP_DB_VERSION = 175;
 | 
				
			||||||
const SYNC_VERSION = 17;
 | 
					const SYNC_VERSION = 17;
 | 
				
			||||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
 | 
					const CLIPPER_PROTOCOL_VERSION = "1.0";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -673,61 +673,90 @@ function scanForLinks(note) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function eraseDeletedNotes(eraseNotesAfterTimeInSeconds = null) {
 | 
					function eraseNotes(noteIdsToErase) {
 | 
				
			||||||
    if (eraseNotesAfterTimeInSeconds === null) {
 | 
					 | 
				
			||||||
        eraseNotesAfterTimeInSeconds = optionService.getOptionInt('eraseNotesAfterTimeInSeconds');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const cutoffDate = new Date(Date.now() - eraseNotesAfterTimeInSeconds * 1000);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const noteIdsToErase = sql.getColumn("SELECT noteId FROM notes WHERE isDeleted = 1 AND isErased = 0 AND notes.utcDateModified <= ?", [dateUtils.utcDateStr(cutoffDate)]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (noteIdsToErase.length === 0) {
 | 
					    if (noteIdsToErase.length === 0) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // it's better to not use repository for this because:
 | 
					    sql.executeMany(`DELETE FROM notes WHERE noteId IN (???)`, noteIdsToErase);
 | 
				
			||||||
    // - it would complain about saving protected notes out of protected session
 | 
					    sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'notes' AND entityId IN (???)`, noteIdsToErase);
 | 
				
			||||||
    // - we don't want these changes to be synced (since they are done on all instances anyway)
 | 
					 | 
				
			||||||
    // - we don't want change the hash since this erasing happens on each instance separately
 | 
					 | 
				
			||||||
    //   and changing the hash would fire up the sync errors temporarily
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sql.executeMany(`
 | 
					    sql.executeMany(`DELETE FROM note_contents WHERE noteId IN (???)`, noteIdsToErase);
 | 
				
			||||||
        UPDATE notes 
 | 
					    sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'note_contents' AND entityId IN (???)`, noteIdsToErase);
 | 
				
			||||||
        SET title = '[erased]',
 | 
					 | 
				
			||||||
            isProtected = 0,
 | 
					 | 
				
			||||||
            isErased = 1
 | 
					 | 
				
			||||||
        WHERE noteId IN (???)`, noteIdsToErase);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sql.executeMany(`
 | 
					    // we also need to erase all "dependent" entities of the erased notes
 | 
				
			||||||
        UPDATE note_contents 
 | 
					    const branchIdsToErase = sql.getManyRows(`SELECT branchId FROM branches WHERE noteId IN (???)`, noteIdsToErase)
 | 
				
			||||||
        SET content = NULL 
 | 
					        .map(row => row.branchId);
 | 
				
			||||||
        WHERE noteId IN (???)`, noteIdsToErase);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // deleting first contents since the WHERE relies on isErased = 0
 | 
					    eraseBranches(branchIdsToErase);
 | 
				
			||||||
    sql.executeMany(`
 | 
					 | 
				
			||||||
        UPDATE note_revision_contents
 | 
					 | 
				
			||||||
        SET content = NULL
 | 
					 | 
				
			||||||
        WHERE noteRevisionId IN 
 | 
					 | 
				
			||||||
            (SELECT noteRevisionId FROM note_revisions WHERE isErased = 0 AND noteId IN (???))`, noteIdsToErase);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sql.executeMany(`
 | 
					    const attributeIdsToErase = sql.getManyRows(`SELECT attributeId FROM attributes WHERE noteId IN (???)`, noteIdsToErase)
 | 
				
			||||||
        UPDATE note_revisions 
 | 
					        .map(row => row.attributeId);
 | 
				
			||||||
        SET isErased = 1,
 | 
					 | 
				
			||||||
            title = NULL
 | 
					 | 
				
			||||||
        WHERE isErased = 0 AND noteId IN (???)`, noteIdsToErase);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sql.executeMany(`
 | 
					    eraseAttributes(attributeIdsToErase);
 | 
				
			||||||
        UPDATE attributes 
 | 
					
 | 
				
			||||||
        SET name = 'deleted',
 | 
					    const noteRevisionIdsToErase = sql.getManyRows(`SELECT noteRevisionId FROM note_revisions WHERE noteId IN (???)`, noteIdsToErase)
 | 
				
			||||||
            value = ''
 | 
					        .map(row => row.noteRevisionId);
 | 
				
			||||||
        WHERE noteId IN (???)`, noteIdsToErase);
 | 
					
 | 
				
			||||||
 | 
					    eraseNoteRevisions(noteRevisionIdsToErase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
 | 
					    log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function eraseBranches(branchIdsToErase) {
 | 
				
			||||||
 | 
					    if (branchIdsToErase.length === 0) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sql.executeMany(`DELETE FROM branches WHERE branchId IN (???)`, branchIdsToErase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'branches' AND entityId IN (???)`, branchIdsToErase);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function eraseAttributes(attributeIdsToErase) {
 | 
				
			||||||
 | 
					    if (attributeIdsToErase.length === 0) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sql.executeMany(`DELETE FROM attributes WHERE attributeId IN (???)`, attributeIdsToErase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'attributes' AND entityId IN (???)`, attributeIdsToErase);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function eraseNoteRevisions(noteRevisionIdsToErase) {
 | 
				
			||||||
 | 
					    if (noteRevisionIdsToErase.length === 0) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sql.executeMany(`DELETE FROM note_revisions WHERE noteRevisionId IN (???)`, noteRevisionIdsToErase);
 | 
				
			||||||
 | 
					    sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'note_revisions' AND entityId IN (???)`, noteRevisionIdsToErase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sql.executeMany(`DELETE FROM note_revision_contents WHERE noteRevisionId IN (???)`, noteRevisionIdsToErase);
 | 
				
			||||||
 | 
					    sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'note_revision_contents' AND entityId IN (???)`, noteRevisionIdsToErase);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function eraseDeletedEntities(eraseEntitiesAfterTimeInSeconds = null) {
 | 
				
			||||||
 | 
					    if (eraseEntitiesAfterTimeInSeconds === null) {
 | 
				
			||||||
 | 
					        eraseEntitiesAfterTimeInSeconds = optionService.getOptionInt('eraseEntitiesAfterTimeInSeconds');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const cutoffDate = new Date(Date.now() - eraseEntitiesAfterTimeInSeconds * 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const noteIdsToErase = sql.getColumn("SELECT noteId FROM notes WHERE isDeleted = 1 AND utcDateModified <= ?", [dateUtils.utcDateStr(cutoffDate)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eraseNotes(noteIdsToErase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const branchIdsToErase = sql.getColumn("SELECT branchId FROM branches WHERE isDeleted = 1 AND utcDateModified <= ?", [dateUtils.utcDateStr(cutoffDate)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eraseBranches(branchIdsToErase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const attributeIdsToErase = sql.getColumn("SELECT attributeId FROM attributes WHERE isDeleted = 1 AND utcDateModified <= ?", [dateUtils.utcDateStr(cutoffDate)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eraseAttributes(attributeIdsToErase);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function eraseDeletedNotesNow() {
 | 
					function eraseDeletedNotesNow() {
 | 
				
			||||||
    eraseDeletedNotes(0);
 | 
					    eraseDeletedEntities(0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// do a replace in str - all keys should be replaced by the corresponding values
 | 
					// do a replace in str - all keys should be replaced by the corresponding values
 | 
				
			||||||
@ -836,9 +865,9 @@ function getNoteIdMapping(origNote) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
sqlInit.dbReady.then(() => {
 | 
					sqlInit.dbReady.then(() => {
 | 
				
			||||||
    // first cleanup kickoff 5 minutes after startup
 | 
					    // first cleanup kickoff 5 minutes after startup
 | 
				
			||||||
    setTimeout(cls.wrap(() => eraseDeletedNotes()), 5 * 60 * 1000);
 | 
					    setTimeout(cls.wrap(() => eraseDeletedEntities()), 5 * 60 * 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setInterval(cls.wrap(() => eraseDeletedNotes()), 4 * 3600 * 1000);
 | 
					    setInterval(cls.wrap(() => eraseDeletedEntities()), 4 * 3600 * 1000);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
 | 
				
			|||||||
@ -82,7 +82,7 @@ const defaultOptions = [
 | 
				
			|||||||
    { name: 'rightPaneWidth', value: '25', isSynced: false },
 | 
					    { name: 'rightPaneWidth', value: '25', isSynced: false },
 | 
				
			||||||
    { name: 'rightPaneVisible', value: 'true', isSynced: false },
 | 
					    { name: 'rightPaneVisible', value: 'true', isSynced: false },
 | 
				
			||||||
    { name: 'nativeTitleBarVisible', value: 'false', isSynced: false },
 | 
					    { name: 'nativeTitleBarVisible', value: 'false', isSynced: false },
 | 
				
			||||||
    { name: 'eraseNotesAfterTimeInSeconds', value: '604800', isSynced: true }, // default is 7 days
 | 
					    { name: 'eraseEntitiesAfterTimeInSeconds', value: '604800', isSynced: true }, // default is 7 days
 | 
				
			||||||
    { name: 'hideArchivedNotes_main', value: 'false', isSynced: false },
 | 
					    { name: 'hideArchivedNotes_main', value: 'false', isSynced: false },
 | 
				
			||||||
    { name: 'hideIncludedImages_main', value: 'true', isSynced: false },
 | 
					    { name: 'hideIncludedImages_main', value: 'true', isSynced: false },
 | 
				
			||||||
    { name: 'attributeListExpanded', value: 'false', isSynced: false },
 | 
					    { name: 'attributeListExpanded', value: 'false', isSynced: false },
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,4 @@
 | 
				
			|||||||
const sql = require('./sql');
 | 
					const sql = require('./sql');
 | 
				
			||||||
const log = require('./log');
 | 
					 | 
				
			||||||
const entityChangesService = require('./entity_changes.js');
 | 
					const entityChangesService = require('./entity_changes.js');
 | 
				
			||||||
const eventService = require('./events');
 | 
					const eventService = require('./events');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -9,22 +8,15 @@ function updateEntity(entityChange, entity, sourceId) {
 | 
				
			|||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const {entityName, hash} = entityChange;
 | 
					    const updated = entityChange.entityName === 'note_reordering'
 | 
				
			||||||
    let updated;
 | 
					        ? updateNoteReordering(entityChange, entity, sourceId)
 | 
				
			||||||
 | 
					        : updateNormalEntity(entityChange, entity, sourceId);
 | 
				
			||||||
    if (entityName === 'note_reordering') {
 | 
					 | 
				
			||||||
        updated = updateNoteReordering(entityChange, entity, sourceId);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else {
 | 
					 | 
				
			||||||
        updated = updateNormalEntity(entityChange, entity, sourceId);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // currently making exception for protected notes and note revisions because here
 | 
					    // currently making exception for protected notes and note revisions because here
 | 
				
			||||||
    // the title and content are not available decrypted as listeners would expect
 | 
					    // the title and content are not available decrypted as listeners would expect
 | 
				
			||||||
    if (updated &&
 | 
					    if (updated && !entity.isProtected) {
 | 
				
			||||||
        (!['notes', 'note_contents', 'note_revisions', 'note_revision_contents'].includes(entityName) || !entity.isProtected)) {
 | 
					 | 
				
			||||||
        eventService.emit(eventService.ENTITY_SYNCED, {
 | 
					        eventService.emit(eventService.ENTITY_SYNCED, {
 | 
				
			||||||
            entityName,
 | 
					            entityName: entityChange.entityName,
 | 
				
			||||||
            entity
 | 
					            entity
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -42,14 +34,7 @@ function updateNormalEntity(entityChange, entity, sourceId) {
 | 
				
			|||||||
        || hash !== entityChange.hash // sync error, we should still update
 | 
					        || hash !== entityChange.hash // sync error, we should still update
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        if (['note_contents', 'note_revision_contents'].includes(entityChange.entityName)) {
 | 
					        if (['note_contents', 'note_revision_contents'].includes(entityChange.entityName)) {
 | 
				
			||||||
            // we always use Buffer object which is different from normal saving - there we use simple string type for "string notes"
 | 
					            entity.content = handleContent(entity.content);
 | 
				
			||||||
            // the problem is that in general it's not possible to whether a note_content is string note or note (syncs can arrive out of order)
 | 
					 | 
				
			||||||
            entity.content = entity.content === null ? null : Buffer.from(entity.content, 'base64');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (entity.content && entity.content.byteLength === 0) {
 | 
					 | 
				
			||||||
                // there seems to be a bug which causes empty buffer to be stored as NULL which is then picked up as inconsistency
 | 
					 | 
				
			||||||
                entity.content = "";
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sql.transactional(() => {
 | 
					        sql.transactional(() => {
 | 
				
			||||||
@ -76,6 +61,19 @@ function updateNoteReordering(entityChange, entity, sourceId) {
 | 
				
			|||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleContent(content) {
 | 
				
			||||||
 | 
					    // we always use Buffer object which is different from normal saving - there we use simple string type for "string notes"
 | 
				
			||||||
 | 
					    // the problem is that in general it's not possible to whether a note_content is string note or note (syncs can arrive out of order)
 | 
				
			||||||
 | 
					    content = content === null ? null : Buffer.from(content, 'base64');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (content && content.byteLength === 0) {
 | 
				
			||||||
 | 
					        // there seems to be a bug which causes empty buffer to be stored as NULL which is then picked up as inconsistency
 | 
				
			||||||
 | 
					        content = "";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return content;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    updateEntity
 | 
					    updateEntity
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user