unify .setContent() .getContent() handling across notes, revisions, attachments

This commit is contained in:
zadam
2023-03-16 15:19:26 +01:00
parent bb45c67e60
commit 2b84f1be00
7 changed files with 151 additions and 249 deletions

View File

@@ -7,6 +7,7 @@ const eventService = require("../../services/events");
const dateUtils = require("../../services/date_utils");
const cls = require("../../services/cls");
const log = require("../../services/log");
const protectedSessionService = require("../../services/protected_session.js");
let becca = null;
@@ -118,6 +119,113 @@ class AbstractBeccaEntity {
return this;
}
/** @protected */
_isHot() {
return false;
}
/** @protected */
_setContent(content) {
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}'`);
}
if (this.isStringNote()) {
content = content.toString();
}
else {
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
}
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = protectedSessionService.encrypt(content);
}
else {
throw new Error(`Cannot update content of blob since we're out of protected session.`);
}
}
sql.transactional(() => {
let newBlobId = this._saveBlob(content);
if (newBlobId !== this.blobId) {
this.blobId = newBlobId;
this.save();
}
});
}
/** @protected */
_saveBlob(content) {
let newBlobId;
let blobNeedsInsert;
if (this._isHot()) {
newBlobId = this.blobId || utils.randomBlobId();
blobNeedsInsert = true;
} else {
newBlobId = utils.hashedBlobId(content);
blobNeedsInsert = !sql.getValue('SELECT 1 FROM blobs WHERE blobId = ?', [newBlobId]);
}
if (blobNeedsInsert) {
const pojo = {
blobId: newBlobId,
content: content,
dateModified: dateUtils.localNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime()
};
sql.upsert("blobs", "blobId", pojo);
const hash = utils.hash(`${newBlobId}|${pojo.content.toString()}`);
entityChangesService.addEntityChange({
entityName: 'blobs',
entityId: newBlobId,
hash: hash,
isErased: false,
utcDateChanged: pojo.utcDateModified,
isSynced: true
});
eventService.emit(eventService.ENTITY_CHANGED, {
entityName: 'blobs',
entity: this
});
}
return newBlobId;
}
/** @protected */
_getContent() {
const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
if (!row) {
throw new Error(`Cannot find content for ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}', blobId '${this.blobId}'`);
}
let content = row.content;
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = content === null ? null : protectedSessionService.decrypt(content);
} else {
content = "";
}
}
if (this.isStringNote()) {
return content === null
? ""
: content.toString("UTF-8");
} else {
return content;
}
}
/**
* Mark the entity as (soft) deleted. It will be completely erased later.
*

View File

@@ -17,7 +17,8 @@ const AbstractBeccaEntity = require("./abstract_becca_entity");
class BAttachment extends AbstractBeccaEntity {
static get entityName() { return "attachments"; }
static get primaryKeyName() { return "attachmentId"; }
static get hashedProperties() { return ["attachmentId", "parentId", "role", "mime", "title", "utcDateModified"]; }
static get hashedProperties() { return ["attachmentId", "parentId", "role", "mime", "title", "blobId",
"utcDateScheduledForDeletionSince", "utcDateModified"]; }
constructor(row) {
super();
@@ -32,7 +33,7 @@ class BAttachment extends AbstractBeccaEntity {
throw new Error("'title' must be given to initialize a Attachment entity");
}
/** @type {string} needs to be set at the initialization time since it's used in the .setContent() */
/** @type {string} */
this.attachmentId = row.attachmentId || `${this.noteId}_${this.name}`; // FIXME
/** @type {string} either noteId or noteRevisionId to which this attachment belongs */
this.parentId = row.parentId;
@@ -45,6 +46,8 @@ class BAttachment extends AbstractBeccaEntity {
/** @type {boolean} */
this.isProtected = !!row.isProtected;
/** @type {string} */
this.utcDateScheduledForDeletionSince = row.utcDateScheduledForDeletionSince;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
}
@@ -58,68 +61,12 @@ class BAttachment extends AbstractBeccaEntity {
}
/** @returns {*} */
getContent(silentNotFoundError = false) {
const res = sql.getRow(`SELECT content FROM attachment_contents WHERE attachmentId = ?`, [this.attachmentId]);
if (!res) {
if (silentNotFoundError) {
return undefined;
}
else {
throw new Error(`Cannot find note attachment content for attachmentId=${this.attachmentId}`);
}
}
let content = res.content;
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = protectedSessionService.decrypt(content);
}
else {
content = "";
}
}
if (this.isStringNote()) {
return content === null
? ""
: content.toString("UTF-8");
}
else {
return content;
}
getContent() {
return this._getContent();
}
setContent(content) {
sql.transactional(() => {
this.save(); // also explicitly save attachment to update contentCheckSum
const pojo = {
attachmentId: this.attachmentId,
content: content,
utcDateModified: dateUtils.utcNowDateTime()
};
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
pojo.content = protectedSessionService.encrypt(pojo.content);
} else {
throw new Error(`Cannot update content of attachmentId=${this.attachmentId} since we're out of protected session.`);
}
}
sql.upsert("attachment_contents", "attachmentId", pojo);
entityChangesService.addEntityChange({
entityName: 'attachment_contents',
entityId: this.attachmentId,
hash: this.contentCheckSum, // FIXME
isErased: false,
utcDateChanged: pojo.utcDateModified,
isSynced: true
});
});
this._setContent(content);
}
calculateCheckSum(content) {

View File

@@ -208,37 +208,8 @@ class BNote extends AbstractBeccaEntity {
*/
/** @returns {*} */
getContent(silentNotFoundError = false) {
const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
if (!row) {
if (silentNotFoundError) {
return undefined;
}
else {
throw new Error(`Cannot find note content for noteId '${this.noteId}', blobId '${this.blobId}'.`);
}
}
let content = row.content;
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = content === null ? null : protectedSessionService.decrypt(content);
}
else {
content = "";
}
}
if (this.isStringNote()) {
return content === null
? ""
: content.toString("UTF-8");
}
else {
return content;
}
getContent() {
return this._getContent();
}
/** @returns {{contentLength, dateModified, utcDateModified}} */
@@ -252,6 +223,29 @@ class BNote extends AbstractBeccaEntity {
WHERE blobId = ?`, [this.blobId]);
}
/** @returns {*} */
getJsonContent() {
const content = this.getContent();
if (!content || !content.trim()) {
return null;
}
return JSON.parse(content);
}
_isHot() {
return ['text', 'code', 'relationMap', 'canvas', 'mermaid'].includes(this.type);
}
setContent(content) {
this._setContent(content)
}
setJsonContent(content) {
this.setContent(JSON.stringify(content, null, '\t'));
}
get dateCreatedObj() {
return this.dateCreated === null ? null : dayjs(this.dateCreated);
}
@@ -268,90 +262,6 @@ class BNote extends AbstractBeccaEntity {
return this.utcDateModified === null ? null : dayjs.utc(this.utcDateModified);
}
/** @returns {*} */
getJsonContent() {
const content = this.getContent();
if (!content || !content.trim()) {
return null;
}
return JSON.parse(content);
}
isHot() {
return ['text', 'code', 'relationMap', 'canvas', 'mermaid'].includes(this.type);
}
setContent(content, ignoreMissingProtectedSession = false) {
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to note '${this.noteId}'`);
}
if (this.isStringNote()) {
content = content.toString();
}
else {
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
}
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = protectedSessionService.encrypt(content);
}
else if (!ignoreMissingProtectedSession) {
throw new Error(`Cannot update content of noteId '${this.noteId}' since we're out of protected session.`);
}
}
let newBlobId;
let blobNeedsInsert;
if (this.isHot()) {
newBlobId = this.blobId || utils.randomBlobId();
blobNeedsInsert = true;
} else {
newBlobId = utils.hashedBlobId(content);
blobNeedsInsert = !sql.getValue('SELECT 1 FROM blobs WHERE blobId = ?', [newBlobId]);
}
if (blobNeedsInsert) {
const pojo = {
blobId: this.blobId,
content: content,
dateModified: dateUtils.localNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime()
};
sql.upsert("blobs", "blobId", pojo);
const hash = utils.hash(`${this.blobId}|${pojo.content.toString()}`);
entityChangesService.addEntityChange({
entityName: 'blobs',
entityId: this.blobId,
hash: hash,
isErased: false,
utcDateChanged: pojo.utcDateModified,
isSynced: true
});
eventService.emit(eventService.ENTITY_CHANGED, {
entityName: 'blobs',
entity: this
});
}
if (newBlobId !== this.blobId) {
this.blobId = newBlobId;
this.save();
}
}
setJsonContent(content) {
this.setContent(JSON.stringify(content, null, '\t'));
}
/** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */
isRoot() {
return this.noteId === 'root';

View File

@@ -75,76 +75,12 @@ class BNoteRevision extends AbstractBeccaEntity {
*/
/** @returns {*} */
getContent(silentNotFoundError = false) {
const res = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
if (!res) {
if (silentNotFoundError) {
return undefined;
}
else {
throw new Error(`Cannot find note revision content for noteRevisionId '${this.noteRevisionId}', blobId '${this.blobId}'`);
}
}
let content = res.content;
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = protectedSessionService.decrypt(content);
}
else {
content = "";
}
}
if (this.isStringNote()) {
return content === null
? ""
: content.toString("UTF-8");
}
else {
return content;
}
getContent() {
return this._getContent();
}
setContent(content) {
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = protectedSessionService.encrypt(content);
}
else {
throw new Error(`Cannot update content of noteRevisionId '${this.noteRevisionId}' since we're out of protected session.`);
}
}
this.blobId = utils.hashedBlobId(content);
const blobAlreadyExists = !!sql.getValue('SELECT 1 FROM blobs WHERE blobId = ?', [this.blobId]);
if (!blobAlreadyExists) {
const pojo = {
blobId: this.blobId,
content: content,
dateModified: dateUtils.localNowDate(),
utcDateModified: dateUtils.utcNowDateTime()
};
sql.insert("blobs", pojo);
const hash = utils.hash(`${this.noteRevisionId}|${pojo.content.toString()}`);
entityChangesService.addEntityChange({
entityName: 'blobs',
entityId: this.blobId,
hash: hash,
isErased: false,
utcDateChanged: this.getUtcDateChanged(),
isSynced: true
});
}
this.save(); // saving this.blobId
this._setContent(content);
}
beforeSaving() {

View File

@@ -8,7 +8,7 @@ function protectAttachments(note) {
for (const attachment of note.getAttachments()) {
if (note.isProtected !== attachment.isProtected) {
if (!protectedSession.isProtectedSessionAvailable()) {
log.error("Protected session is not available to fix note attachments.");
log.error("Protected session is not available to fix attachments.");
return;
}
@@ -24,7 +24,7 @@ function protectAttachments(note) {
attachment.save();
}
catch (e) {
log.error(`Could not un/protect note attachment ID = ${attachment.attachmentId}`);
log.error(`Could not un/protect attachment ID = ${attachment.attachmentId}`);
throw e;
}

View File

@@ -231,9 +231,9 @@ class ConsistencyChecks {
this.reloadNeeded = false;
logFix(`Note attachment '${attachmentId}' has been deleted since it references missing note/revision '${parentId}'`);
logFix(`Attachment '${attachmentId}' has been deleted since it references missing note/revision '${parentId}'`);
} else {
logError(`Note attachment '${attachmentId}' references missing note/revision '${parentId}'`);
logError(`Attachment '${attachmentId}' references missing note/revision '${parentId}'`);
}
});
}
@@ -358,9 +358,9 @@ class ConsistencyChecks {
this.reloadNeeded = false;
logFix(`Note attachment '${attachmentId}' has been deleted since associated note '${noteId}' is deleted.`);
logFix(`Attachment '${attachmentId}' has been deleted since associated note '${noteId}' is deleted.`);
} else {
logError(`Note attachment '${attachmentId}' is not deleted even though associated note '${noteId}' is deleted.`)
logError(`Attachment '${attachmentId}' is not deleted even though associated note '${noteId}' is deleted.`)
}
});
}