fix loading of string content saved as binary

This commit is contained in:
zadam 2023-06-14 22:21:22 +02:00
parent 8d2958738f
commit 75c6afd1c3
20 changed files with 64 additions and 66 deletions

View File

@ -2,8 +2,7 @@
const sql = require("../services/sql"); const sql = require("../services/sql");
const NoteSet = require("../services/search/note_set"); const NoteSet = require("../services/search/note_set");
const BAttachment = require("./entities/battachment.js"); const NotFoundError = require("../errors/not_found_error");
const NotFoundError = require("../errors/not_found_error.js");
/** /**
* Becca is a backend cache of all notes, branches and attributes. There's a similar frontend cache Froca. * Becca is a backend cache of all notes, branches and attributes. There's a similar frontend cache Froca.
@ -148,7 +147,7 @@ class Becca {
getRevision(revisionId) { getRevision(revisionId) {
const row = sql.getRow("SELECT * FROM revisions WHERE revisionId = ?", [revisionId]); const row = sql.getRow("SELECT * FROM revisions WHERE revisionId = ?", [revisionId]);
const BRevision = require("./entities/brevision.js"); // avoiding circular dependency problems const BRevision = require("./entities/brevision"); // avoiding circular dependency problems
return row ? new BRevision(row) : null; return row ? new BRevision(row) : null;
} }
@ -186,8 +185,8 @@ class Becca {
} }
/** @returns {BBlob|null} */ /** @returns {BBlob|null} */
getBlob(blobId) { getBlob(entity) {
const row = sql.getRow("SELECT *, LENGTH(content) AS contentLength FROM blobs WHERE blobId = ?", [blobId]); const row = sql.getRow("SELECT *, LENGTH(content) AS contentLength FROM blobs WHERE blobId = ?", [entity.blobId]);
const BBlob = require("./entities/bblob"); // avoiding circular dependency problems const BBlob = require("./entities/bblob"); // avoiding circular dependency problems
return row ? new BBlob(row) : null; return row ? new BBlob(row) : null;
@ -217,8 +216,6 @@ class Becca {
return this.getRevision(entityId); return this.getRevision(entityId);
} else if (entityName === 'attachments') { } else if (entityName === 'attachments') {
return this.getAttachment(entityId); return this.getAttachment(entityId);
} else if (entityName === 'blobs') {
return this.getBlob(entityId);
} }
const camelCaseEntityName = entityName.toLowerCase().replace(/(_[a-z])/g, const camelCaseEntityName = entityName.toLowerCase().replace(/(_[a-z])/g,
@ -247,7 +244,7 @@ class Becca {
getRevisionsFromQuery(query, params = []) { getRevisionsFromQuery(query, params = []) {
const rows = sql.getRows(query, params); const rows = sql.getRows(query, params);
const BRevision = require("./entities/brevision.js"); // avoiding circular dependency problems const BRevision = require("./entities/brevision"); // avoiding circular dependency problems
return rows.map(row => new BRevision(row)); return rows.map(row => new BRevision(row));
} }

View File

@ -7,7 +7,8 @@ const eventService = require("../../services/events");
const dateUtils = require("../../services/date_utils"); const dateUtils = require("../../services/date_utils");
const cls = require("../../services/cls"); const cls = require("../../services/cls");
const log = require("../../services/log"); const log = require("../../services/log");
const protectedSessionService = require("../../services/protected_session.js"); const protectedSessionService = require("../../services/protected_session");
const blobService = require("../../services/blob");
let becca = null; let becca = null;
@ -246,36 +247,13 @@ class AbstractBeccaEntity {
throw new Error(`Cannot find content for ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}', blobId '${this.blobId}'`); throw new Error(`Cannot find content for ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}', blobId '${this.blobId}'`);
} }
let content = row.content; return blobService.processContent(row.content);
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = content === null ? null : protectedSessionService.decrypt(content);
} else {
content = "";
}
}
if (this.hasStringContent()) {
return content === null
? ""
: content.toString("utf-8");
} else {
// see https://github.com/zadam/trilium/issues/3523
// IIRC a zero-sized buffer can be returned as null from the database
if (content === null) {
// this will force de/encryption
content = Buffer.alloc(0);
}
return content;
}
} }
/** /**
* Mark the entity as (soft) deleted. It will be completely erased later. * Mark the entity as (soft) deleted. It will be completely erased later.
* *
* This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead. * This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
* *
* @param [deleteId=null] * @param [deleteId=null]
*/ */

View File

@ -4,8 +4,8 @@ const utils = require('../../services/utils');
const dateUtils = require('../../services/date_utils'); const dateUtils = require('../../services/date_utils');
const AbstractBeccaEntity = require("./abstract_becca_entity"); const AbstractBeccaEntity = require("./abstract_becca_entity");
const sql = require("../../services/sql"); const sql = require("../../services/sql");
const protectedSessionService = require("../../services/protected_session.js"); const protectedSessionService = require("../../services/protected_session");
const log = require("../../services/log.js"); const log = require("../../services/log");
const attachmentRoleToNoteTypeMapping = { const attachmentRoleToNoteTypeMapping = {
'image': 'image' 'image': 'image'

View File

@ -148,8 +148,6 @@ function register(router) {
// (e.g. branchIds are not seen in UI), that we export "note export" instead. // (e.g. branchIds are not seen in UI), that we export "note export" instead.
const branch = note.getParentBranches()[0]; const branch = note.getParentBranches()[0];
console.log(note.getParentBranches());
zipExportService.exportToZip(taskContext, branch, format, res); zipExportService.exportToZip(taskContext, branch, format, res);
}); });

View File

@ -367,7 +367,7 @@ class Froca {
// we don't want to keep large payloads forever in memory, so we clean that up quite quickly // we don't want to keep large payloads forever in memory, so we clean that up quite quickly
// this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components) // this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components)
// this is also a workaround for missing invalidation after change // if the blob is updated within the cache lifetime, it should be invalidated by froca_updater
this.blobPromises[key].then( this.blobPromises[key].then(
() => setTimeout(() => this.blobPromises[key] = null, 1000) () => setTimeout(() => this.blobPromises[key] = null, 1000)
); );

View File

@ -354,7 +354,7 @@ class NoteListRenderer {
$content.append($renderedContent); $content.append($renderedContent);
$content.addClass(`type-${type}`); $content.addClass(`type-${type}`);
} catch (e) { } catch (e) {
console.log(`Caught error while rendering note ${note.noteId} of type ${note.type}: ${e.message}, stack: ${e.stack}`); console.log(`Caught error while rendering note '${note.noteId}' of type '${note.type}': ${e.message}, stack: ${e.stack}`);
$content.append("rendering error"); $content.append("rendering error");
} }

View File

@ -295,13 +295,16 @@ async function openDialog($dialog, closeActDialog = true) {
function isHtmlEmpty(html) { function isHtmlEmpty(html) {
if (!html) { if (!html) {
return true; return true;
} else if (typeof html !== 'string') {
logError(`Got object of type '${typeof html}' where string was expected.`);
return false;
} }
html = html.toLowerCase(); html = html.toLowerCase();
return !html.includes('<img') return !html.includes('<img')
&& !html.includes('<section') && !html.includes('<section')
// line below will actually attempt to load images so better to check for images first // the line below will actually attempt to load images so better to check for images first
&& $("<div>").html(html).text().trim().length === 0; && $("<div>").html(html).text().trim().length === 0;
} }
@ -579,7 +582,6 @@ export default {
sleep, sleep,
escapeRegExp, escapeRegExp,
formatNoteSize, formatNoteSize,
escapeRegExp,
areObjectsEqual, areObjectsEqual,
copyHtmlToClipboard copyHtmlToClipboard
}; };

View File

@ -665,12 +665,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const branch = froca.getBranch(node.data.branchId); const branch = froca.getBranch(node.data.branchId);
if (!note) { if (!note) {
console.log(`Node update not possible because note ${node.data.noteId} was not found.`); console.log(`Node update not possible because note '${node.data.noteId}' was not found.`);
return; return;
} } else if (!branch) {
console.log(`Node update not possible because branch '${node.data.branchId}' was not found.`);
if (!branch) {
console.log(`Node update not possible because branch ${node.data.branchId} was not found.`);
return; return;
} }

View File

@ -1,5 +1,5 @@
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const blobService = require("../../services/blob.js"); const blobService = require("../../services/blob");
const ValidationError = require("../../errors/validation_error"); const ValidationError = require("../../errors/validation_error");
function getAttachmentBlob(req) { function getAttachmentBlob(req) {

View File

@ -10,7 +10,7 @@ const { Readable } = require('stream');
const chokidar = require('chokidar'); const chokidar = require('chokidar');
const ws = require('../../services/ws'); const ws = require('../../services/ws');
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const ValidationError = require("../../errors/validation_error.js"); const ValidationError = require("../../errors/validation_error");
function updateFile(req) { function updateFile(req) {
const note = becca.getNoteOrThrow(req.params.noteId); const note = becca.getNoteOrThrow(req.params.noteId);

View File

@ -1,5 +1,5 @@
const becca = require("../../becca/becca.js"); const becca = require("../../becca/becca");
const sql = require("../../services/sql.js"); const sql = require("../../services/sql");
function getRelationMap(req) { function getRelationMap(req) {
const {relationMapNoteId, noteIds} = req.body; const {relationMapNoteId, noteIds} = req.body;

View File

@ -7,7 +7,7 @@ const sql = require('../../services/sql');
const cls = require('../../services/cls'); const cls = require('../../services/cls');
const path = require('path'); const path = require('path');
const becca = require("../../becca/becca"); const becca = require("../../becca/becca");
const blobService = require("../../services/blob.js"); const blobService = require("../../services/blob");
function getRevisionBlob(req) { function getRevisionBlob(req) {
const preview = req.query.preview === 'true'; const preview = req.query.preview === 'true';

View File

@ -1,7 +1,7 @@
const assetPath = require("../services/asset_path.js"); const assetPath = require("../services/asset_path");
const path = require("path"); const path = require("path");
const express = require("express"); const express = require("express");
const env = require("../services/env.js"); const env = require("../services/env");
const persistentCacheStatic = (root, options) => { const persistentCacheStatic = (root, options) => {
if (!env.isDev()) { if (!env.isDev()) {

View File

@ -1,6 +1,6 @@
const session = require("express-session"); const session = require("express-session");
const sessionSecret = require("../services/session_secret.js"); const sessionSecret = require("../services/session_secret");
const dataDir = require("../services/data_dir.js"); const dataDir = require("../services/data_dir");
const FileStore = require('session-file-store')(session); const FileStore = require('session-file-store')(session);
const sessionParser = session({ const sessionParser = session({

View File

@ -1,5 +1,6 @@
const becca = require('../becca/becca'); const becca = require('../becca/becca');
const NotFoundError = require("../errors/not_found_error"); const NotFoundError = require("../errors/not_found_error");
const protectedSessionService = require("./protected_session");
function getBlobPojo(entityName, entityId, opts = {}) { function getBlobPojo(entityName, entityId, opts = {}) {
opts.preview = !!opts.preview; opts.preview = !!opts.preview;
@ -10,19 +11,47 @@ function getBlobPojo(entityName, entityId, opts = {}) {
throw new NotFoundError(`Entity ${entityName} '${entityId}' was not found.`); throw new NotFoundError(`Entity ${entityName} '${entityId}' was not found.`);
} }
const blob = becca.getBlob(entity.blobId); const blob = becca.getBlob(entity);
const pojo = blob.getPojo(); const pojo = blob.getPojo();
if (!entity.hasStringContent()) { if (!entity.hasStringContent()) {
pojo.content = null; pojo.content = null;
} else if (opts.preview && pojo.content.length > 10000) { } else {
pojo.content = `${pojo.content.substr(0, 10000)}\r\n\r\n... and ${pojo.content.length - 10000} more characters.`; pojo.content = processContent(pojo.content, entity.isProtected, true);
if (opts.preview && pojo.content.length > 10000) {
pojo.content = `${pojo.content.substr(0, 10000)}\r\n\r\n... and ${pojo.content.length - 10000} more characters.`;
}
} }
return pojo; return pojo;
} }
function processContent(content, isProtected, isStringContent) {
if (isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = content === null ? null : protectedSessionService.decrypt(content);
} else {
content = "";
}
}
if (isStringContent) {
return content === null ? "" : content.toString("utf-8");
} else {
// see https://github.com/zadam/trilium/issues/3523
// IIRC a zero-sized buffer can be returned as null from the database
if (content === null) {
// this will force de/encryption
content = Buffer.alloc(0);
}
return content;
}
}
module.exports = { module.exports = {
getBlobPojo getBlobPojo,
processContent
}; };

View File

@ -1,5 +1,5 @@
const log = require("./log"); const log = require("./log");
const revisionService = require("./revisions.js"); const revisionService = require("./revisions");
const becca = require("../becca/becca"); const becca = require("../becca/becca");
const cloningService = require("./cloning"); const cloningService = require("./cloning");
const branchService = require("./branches"); const branchService = require("./branches");

View File

@ -13,7 +13,6 @@ const revisionService = require('./revisions.js');
const becca = require("../becca/becca"); const becca = require("../becca/becca");
const utils = require("../services/utils"); const utils = require("../services/utils");
const {sanitizeAttributeName} = require("./sanitize_attribute_name"); const {sanitizeAttributeName} = require("./sanitize_attribute_name");
const {note} = require("../../spec/search/becca_mocking.js");
const noteTypes = require("../services/note_types").getNoteTypeNames(); const noteTypes = require("../services/note_types").getNoteTypeNames();
class ConsistencyChecks { class ConsistencyChecks {

View File

@ -187,8 +187,6 @@ function importHtml(taskContext, file, parentNote) {
function importAttachment(taskContext, file, parentNote) { function importAttachment(taskContext, file, parentNote) {
const mime = mimeService.getMime(file.originalname) || file.mimetype; const mime = mimeService.getMime(file.originalname) || file.mimetype;
console.log("mime", mime);
if (mime.startsWith("image/")) { if (mime.startsWith("image/")) {
imageService.saveImageToAttachment(parentNote.noteId, file.buffer, file.originalname, taskContext.data.shrinkImages); imageService.saveImageToAttachment(parentNote.noteId, file.buffer, file.originalname, taskContext.data.shrinkImages);

View File

@ -21,7 +21,7 @@ const htmlSanitizer = require("./html_sanitizer");
const ValidationError = require("../errors/validation_error"); const ValidationError = require("../errors/validation_error");
const noteTypesService = require("./note_types"); const noteTypesService = require("./note_types");
const fs = require("fs"); const fs = require("fs");
const ws = require("./ws.js"); const ws = require("./ws");
/** @param {BNote} parentNote */ /** @param {BNote} parentNote */
function getNewNotePosition(parentNote) { function getNewNotePosition(parentNote) {

View File

@ -8,7 +8,6 @@ const shareRoot = require("./share_root");
const contentRenderer = require("./content_renderer"); const contentRenderer = require("./content_renderer");
const assetPath = require("../services/asset_path"); const assetPath = require("../services/asset_path");
const appPath = require("../services/app_path"); const appPath = require("../services/app_path");
const utils = require("../services/utils.js");
function getSharedSubTreeRoot(note) { function getSharedSubTreeRoot(note) {
if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) { if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {