mirror of
https://github.com/zadam/trilium.git
synced 2025-11-14 17:25:52 +01:00
refactored note attachment into note ancillary
This commit is contained in:
@@ -122,12 +122,12 @@ class Becca {
|
||||
return row ? new BNoteRevision(row) : null;
|
||||
}
|
||||
|
||||
/** @returns {BNoteAttachment|null} */
|
||||
getNoteAttachment(noteAttachmentId) {
|
||||
const row = sql.getRow("SELECT * FROM note_attachments WHERE noteAttachmentId = ?", [noteAttachmentId]);
|
||||
/** @returns {BNoteAncillary|null} */
|
||||
getNoteAncillary(noteAncillaryId) {
|
||||
const row = sql.getRow("SELECT * FROM note_ancillaries WHERE noteAncillaryId = ?", [noteAncillaryId]);
|
||||
|
||||
const BNoteAttachment = require("./entities/bnote_attachment"); // avoiding circular dependency problems
|
||||
return row ? new BNoteAttachment(row) : null;
|
||||
const BNoteAncillary = require("./entities/bnote_ancillary"); // avoiding circular dependency problems
|
||||
return row ? new BNoteAncillary(row) : null;
|
||||
}
|
||||
|
||||
/** @returns {BOption|null} */
|
||||
@@ -152,8 +152,8 @@ class Becca {
|
||||
|
||||
if (entityName === 'note_revisions') {
|
||||
return this.getNoteRevision(entityId);
|
||||
} else if (entityName === 'note_attachments') {
|
||||
return this.getNoteAttachment(entityId);
|
||||
} else if (entityName === 'note_ancillaries') {
|
||||
return this.getNoteAncillary(entityId);
|
||||
}
|
||||
|
||||
const camelCaseEntityName = entityName.toLowerCase().replace(/(_[a-z])/g,
|
||||
|
||||
@@ -198,8 +198,8 @@ class BBranch extends AbstractBeccaEntity {
|
||||
relation.markAsDeleted(deleteId);
|
||||
}
|
||||
|
||||
for (const noteAttachment of note.getNoteAttachments()) {
|
||||
noteAttachment.markAsDeleted(deleteId);
|
||||
for (const noteAncillary of note.getNoteAncillaries()) {
|
||||
noteAncillary.markAsDeleted(deleteId);
|
||||
}
|
||||
|
||||
note.markAsDeleted(deleteId);
|
||||
|
||||
@@ -8,7 +8,7 @@ const dateUtils = require('../../services/date_utils');
|
||||
const entityChangesService = require('../../services/entity_changes');
|
||||
const AbstractBeccaEntity = require("./abstract_becca_entity");
|
||||
const BNoteRevision = require("./bnote_revision");
|
||||
const BNoteAttachment = require("./bnote_attachment");
|
||||
const BNoteAncillary = require("./bnote_ancillary");
|
||||
const TaskContext = require("../../services/task_context");
|
||||
const dayjs = require("dayjs");
|
||||
const utc = require('dayjs/plugin/utc');
|
||||
@@ -19,7 +19,7 @@ const LABEL = 'label';
|
||||
const RELATION = 'relation';
|
||||
|
||||
/**
|
||||
* Trilium's main entity which can represent text note, image, code note, file attachment etc.
|
||||
* Trilium's main entity which can represent text note, image, code note, file ancillary etc.
|
||||
*
|
||||
* @extends AbstractBeccaEntity
|
||||
*/
|
||||
@@ -337,7 +337,7 @@ class BNote extends AbstractBeccaEntity {
|
||||
return this.mime === "application/json";
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if this note is JavaScript (code or attachment) */
|
||||
/** @returns {boolean} true if this note is JavaScript (code or ancillary) */
|
||||
isJavaScript() {
|
||||
return (this.type === "code" || this.type === "file" || this.type === 'launcher')
|
||||
&& (this.mime.startsWith("application/javascript")
|
||||
@@ -1136,16 +1136,16 @@ class BNote extends AbstractBeccaEntity {
|
||||
.map(row => new BNoteRevision(row));
|
||||
}
|
||||
|
||||
/** @returns {BNoteAttachment[]} */
|
||||
getNoteAttachments() {
|
||||
return sql.getRows("SELECT * FROM note_attachments WHERE noteId = ? AND isDeleted = 0", [this.noteId])
|
||||
.map(row => new BNoteAttachment(row));
|
||||
/** @returns {BNoteAncillary[]} */
|
||||
getNoteAncillaries() {
|
||||
return sql.getRows("SELECT * FROM note_ancillaries WHERE noteId = ? AND isDeleted = 0", [this.noteId])
|
||||
.map(row => new BNoteAncillary(row));
|
||||
}
|
||||
|
||||
/** @returns {BNoteAttachment|undefined} */
|
||||
getNoteAttachmentByName(name) {
|
||||
return sql.getRows("SELECT * FROM note_attachments WHERE noteId = ? AND name = ? AND isDeleted = 0", [this.noteId, name])
|
||||
.map(row => new BNoteAttachment(row))
|
||||
/** @returns {BNoteAncillary|undefined} */
|
||||
getNoteAncillaryByName(name) {
|
||||
return sql.getRows("SELECT * FROM note_ancillaries WHERE noteId = ? AND name = ? AND isDeleted = 0", [this.noteId, name])
|
||||
.map(row => new BNoteAncillary(row))
|
||||
[0];
|
||||
}
|
||||
|
||||
@@ -1479,28 +1479,28 @@ class BNote extends AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {BNoteAttachment}
|
||||
* @returns {BNoteAncillary}
|
||||
*/
|
||||
saveNoteAttachment(name, mime, content) {
|
||||
let noteAttachment = this.getNoteAttachmentByName(name);
|
||||
saveNoteAncillary(name, mime, content) {
|
||||
let noteAncillary = this.getNoteAncillaryByName(name);
|
||||
|
||||
if (noteAttachment
|
||||
&& noteAttachment.mime === mime
|
||||
&& noteAttachment.contentCheckSum === noteAttachment.calculateCheckSum(content)) {
|
||||
if (noteAncillary
|
||||
&& noteAncillary.mime === mime
|
||||
&& noteAncillary.contentCheckSum === noteAncillary.calculateCheckSum(content)) {
|
||||
|
||||
return noteAttachment; // no change
|
||||
return noteAncillary; // no change
|
||||
}
|
||||
|
||||
noteAttachment = new BNoteAttachment({
|
||||
noteAncillary = new BNoteAncillary({
|
||||
noteId: this.noteId,
|
||||
name,
|
||||
mime,
|
||||
isProtected: this.isProtected
|
||||
});
|
||||
|
||||
noteAttachment.setContent(content);
|
||||
noteAncillary.setContent(content);
|
||||
|
||||
return noteAttachment;
|
||||
return noteAncillary;
|
||||
}
|
||||
|
||||
beforeSaving() {
|
||||
|
||||
@@ -9,29 +9,29 @@ const entityChangesService = require('../../services/entity_changes');
|
||||
const AbstractBeccaEntity = require("./abstract_becca_entity");
|
||||
|
||||
/**
|
||||
* NoteAttachment represent data related/attached to the note. Conceptually similar to attributes, but intended for
|
||||
* NoteAncillary represent data related/attached to the note. Conceptually similar to attributes, but intended for
|
||||
* larger amounts of data and generally not accessible to the user.
|
||||
*
|
||||
* @extends AbstractBeccaEntity
|
||||
*/
|
||||
class BNoteAttachment extends AbstractBeccaEntity {
|
||||
static get entityName() { return "note_attachments"; }
|
||||
static get primaryKeyName() { return "noteAttachmentId"; }
|
||||
static get hashedProperties() { return ["noteAttachmentId", "noteId", "name", "content", "utcDateModified"]; }
|
||||
class BNoteAncillary extends AbstractBeccaEntity {
|
||||
static get entityName() { return "note_ancillaries"; }
|
||||
static get primaryKeyName() { return "noteAncillaryId"; }
|
||||
static get hashedProperties() { return ["noteAncillaryId", "noteId", "name", "content", "utcDateModified"]; }
|
||||
|
||||
constructor(row) {
|
||||
super();
|
||||
|
||||
if (!row.noteId) {
|
||||
throw new Error("'noteId' must be given to initialize a NoteAttachment entity");
|
||||
throw new Error("'noteId' must be given to initialize a NoteAncillary entity");
|
||||
}
|
||||
|
||||
if (!row.name) {
|
||||
throw new Error("'name' must be given to initialize a NoteAttachment entity");
|
||||
throw new Error("'name' must be given to initialize a NoteAncillary entity");
|
||||
}
|
||||
|
||||
/** @type {string} needs to be set at the initialization time since it's used in the .setContent() */
|
||||
this.noteAttachmentId = row.noteAttachmentId || `${this.noteId}_${this.name}`;
|
||||
this.noteAncillaryId = row.noteAncillaryId || `${this.noteId}_${this.name}`;
|
||||
/** @type {string} */
|
||||
this.noteId = row.noteId;
|
||||
/** @type {string} */
|
||||
@@ -57,14 +57,14 @@ class BNoteAttachment extends AbstractBeccaEntity {
|
||||
|
||||
/** @returns {*} */
|
||||
getContent(silentNotFoundError = false) {
|
||||
const res = sql.getRow(`SELECT content FROM note_attachment_contents WHERE noteAttachmentId = ?`, [this.noteAttachmentId]);
|
||||
const res = sql.getRow(`SELECT content FROM note_ancillary_contents WHERE noteAncillaryId = ?`, [this.noteAncillaryId]);
|
||||
|
||||
if (!res) {
|
||||
if (silentNotFoundError) {
|
||||
return undefined;
|
||||
}
|
||||
else {
|
||||
throw new Error(`Cannot find note attachment content for noteAttachmentId=${this.noteAttachmentId}`);
|
||||
throw new Error(`Cannot find note ancillary content for noteAncillaryId=${this.noteAncillaryId}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,10 +92,10 @@ class BNoteAttachment extends AbstractBeccaEntity {
|
||||
setContent(content) {
|
||||
sql.transactional(() => {
|
||||
this.contentCheckSum = this.calculateCheckSum(content);
|
||||
this.save(); // also explicitly save note_attachment to update contentCheckSum
|
||||
this.save(); // also explicitly save note_ancillary to update contentCheckSum
|
||||
|
||||
const pojo = {
|
||||
noteAttachmentId: this.noteAttachmentId,
|
||||
noteAncillaryId: this.noteAncillaryId,
|
||||
content: content,
|
||||
utcDateModified: dateUtils.utcNowDateTime()
|
||||
};
|
||||
@@ -104,15 +104,15 @@ class BNoteAttachment extends AbstractBeccaEntity {
|
||||
if (protectedSessionService.isProtectedSessionAvailable()) {
|
||||
pojo.content = protectedSessionService.encrypt(pojo.content);
|
||||
} else {
|
||||
throw new Error(`Cannot update content of noteAttachmentId=${this.noteAttachmentId} since we're out of protected session.`);
|
||||
throw new Error(`Cannot update content of noteAncillaryId=${this.noteAncillaryId} since we're out of protected session.`);
|
||||
}
|
||||
}
|
||||
|
||||
sql.upsert("note_attachment_contents", "noteAttachmentId", pojo);
|
||||
sql.upsert("note_ancillary_contents", "noteAncillaryId", pojo);
|
||||
|
||||
entityChangesService.addEntityChange({
|
||||
entityName: 'note_attachment_contents',
|
||||
entityId: this.noteAttachmentId,
|
||||
entityName: 'note_ancillary_contents',
|
||||
entityId: this.noteAncillaryId,
|
||||
hash: this.contentCheckSum,
|
||||
isErased: false,
|
||||
utcDateChanged: pojo.utcDateModified,
|
||||
@@ -122,7 +122,7 @@ class BNoteAttachment extends AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
calculateCheckSum(content) {
|
||||
return utils.hash(`${this.noteAttachmentId}|${content.toString()}`);
|
||||
return utils.hash(`${this.noteAncillaryId}|${content.toString()}`);
|
||||
}
|
||||
|
||||
beforeSaving() {
|
||||
@@ -130,7 +130,7 @@ class BNoteAttachment extends AbstractBeccaEntity {
|
||||
throw new Error(`Name must be alphanumerical, "${this.name}" given.`);
|
||||
}
|
||||
|
||||
this.noteAttachmentId = `${this.noteId}_${this.name}`;
|
||||
this.noteAncillaryId = `${this.noteId}_${this.name}`;
|
||||
|
||||
super.beforeSaving();
|
||||
|
||||
@@ -139,7 +139,7 @@ class BNoteAttachment extends AbstractBeccaEntity {
|
||||
|
||||
getPojo() {
|
||||
return {
|
||||
noteAttachmentId: this.noteAttachmentId,
|
||||
noteAncillaryId: this.noteAncillaryId,
|
||||
noteId: this.noteId,
|
||||
name: this.name,
|
||||
mime: this.mime,
|
||||
@@ -159,4 +159,4 @@ class BNoteAttachment extends AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BNoteAttachment;
|
||||
module.exports = BNoteAncillary;
|
||||
@@ -1,6 +1,6 @@
|
||||
const BNote = require('./entities/bnote');
|
||||
const BNoteRevision = require('./entities/bnote_revision');
|
||||
const BNoteAttachment = require("./entities/bnote_attachment");
|
||||
const BNoteAncillary = require("./entities/bnote_ancillary");
|
||||
const BBranch = require('./entities/bbranch');
|
||||
const BAttribute = require('./entities/battribute');
|
||||
const BRecentNote = require('./entities/brecent_note');
|
||||
@@ -14,8 +14,8 @@ const ENTITY_NAME_TO_ENTITY = {
|
||||
"note_contents": BNote,
|
||||
"note_revisions": BNoteRevision,
|
||||
"note_revision_contents": BNoteRevision,
|
||||
"note_attachments": BNoteAttachment,
|
||||
"note_attachment_contents": BNoteAttachment,
|
||||
"note_ancillaries": BNoteAncillary,
|
||||
"note_ancillary_contents": BNoteAncillary,
|
||||
"recent_notes": BRecentNote,
|
||||
"etapi_tokens": BEtapiToken,
|
||||
"options": BOption
|
||||
|
||||
@@ -801,7 +801,7 @@ class FNote {
|
||||
return labels.length > 0 ? labels[0].value : "";
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if this note is JavaScript (code or attachment) */
|
||||
/** @returns {boolean} true if this note is JavaScript (code or ancillary) */
|
||||
isJavaScript() {
|
||||
return (this.type === "code" || this.type === "file" || this.type === 'launcher')
|
||||
&& (this.mime.startsWith("application/javascript")
|
||||
|
||||
@@ -36,7 +36,7 @@ async function processEntityChanges(entityChanges) {
|
||||
|
||||
loadResults.addOption(ec.entity.name);
|
||||
}
|
||||
else if (['etapi_tokens', 'note_attachments', 'note_attachment_contents'].includes(ec.entityName)) {
|
||||
else if (['etapi_tokens', 'note_ancillaries', 'note_ancillary_contents'].includes(ec.entityName)) {
|
||||
// NOOP
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -78,9 +78,9 @@ export default class MermaidWidget extends NoteContextAwareWidget {
|
||||
this.$display.html(renderedSvg);
|
||||
|
||||
// not awaiting intentionally
|
||||
// this is pretty hacky since we update attachment on render
|
||||
// this is pretty hacky since we update ancillary on render
|
||||
// but if nothing changed this should not trigger DB write and sync
|
||||
server.put(`notes/${note.noteId}/attachments/mermaidSvg`, {
|
||||
server.put(`notes/${note.noteId}/ancillaries/mermaidSvg`, {
|
||||
mime: 'image/svg+xml',
|
||||
content: renderedSvg
|
||||
});
|
||||
|
||||
@@ -284,7 +284,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
|
||||
return {
|
||||
content: JSON.stringify(content),
|
||||
attachments: [
|
||||
ancillaries: [
|
||||
{
|
||||
name: 'canvasSvg',
|
||||
mime: 'image/svg+xml',
|
||||
|
||||
@@ -587,7 +587,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
getData() {
|
||||
// TODO: save also image as attachment
|
||||
// TODO: save also image as ancillary
|
||||
return {
|
||||
content: JSON.stringify(this.mapData)
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ export default class TypeWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Object>|*} promise resolving note data. Note data is an object with content and attachments.
|
||||
* @returns {Promise<Object>|*} promise resolving note data. Note data is an object with content and ancillaries.
|
||||
*/
|
||||
getData() {}
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ function createNote(req) {
|
||||
}
|
||||
|
||||
function updateNoteData(req) {
|
||||
const {content, attachments} = req.body;
|
||||
const {content, ancillaries} = req.body;
|
||||
const {noteId} = req.params;
|
||||
|
||||
return noteService.updateNoteData(noteId, content, attachments);
|
||||
return noteService.updateNoteData(noteId, content, ancillaries);
|
||||
}
|
||||
|
||||
function deleteNote(req) {
|
||||
@@ -127,7 +127,7 @@ function setNoteTypeMime(req) {
|
||||
note.save();
|
||||
}
|
||||
|
||||
function saveNoteAttachment(req) {
|
||||
function saveNoteAncillary(req) {
|
||||
const {noteId, name} = req.params;
|
||||
const {mime, content} = req.body;
|
||||
|
||||
@@ -137,7 +137,7 @@ function saveNoteAttachment(req) {
|
||||
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
|
||||
}
|
||||
|
||||
note.saveNoteAttachment(name, mime, content);
|
||||
note.saveNoteAncillary(name, mime, content);
|
||||
}
|
||||
|
||||
function getRelationMap(req) {
|
||||
@@ -354,5 +354,5 @@ module.exports = {
|
||||
getDeleteNotesPreview,
|
||||
uploadModifiedFile,
|
||||
forceSaveNoteRevision,
|
||||
saveNoteAttachment
|
||||
saveNoteAncillary
|
||||
};
|
||||
|
||||
@@ -114,12 +114,12 @@ function forceNoteSync(req) {
|
||||
entityChangesService.moveEntityChangeToTop('note_revision_contents', noteRevisionId);
|
||||
}
|
||||
|
||||
for (const noteAttachmentId of sql.getColumn("SELECT noteAttachmentId FROM note_attachments WHERE noteId = ?", [noteId])) {
|
||||
sql.execute(`UPDATE note_attachments SET utcDateModified = ? WHERE noteAttachmentId = ?`, [now, noteAttachmentId]);
|
||||
entityChangesService.moveEntityChangeToTop('note_attachments', noteAttachmentId);
|
||||
for (const noteAncillaryId of sql.getColumn("SELECT noteAncillaryId FROM note_ancillaries WHERE noteId = ?", [noteId])) {
|
||||
sql.execute(`UPDATE note_ancillaries SET utcDateModified = ? WHERE noteAncillaryId = ?`, [now, noteAncillaryId]);
|
||||
entityChangesService.moveEntityChangeToTop('note_ancillaries', noteAncillaryId);
|
||||
|
||||
sql.execute(`UPDATE note_attachment_contents SET utcDateModified = ? WHERE noteAttachmentId = ?`, [now, noteAttachmentId]);
|
||||
entityChangesService.moveEntityChangeToTop('note_attachment_contents', noteAttachmentId);
|
||||
sql.execute(`UPDATE note_ancillary_contents SET utcDateModified = ? WHERE noteAncillaryId = ?`, [now, noteAncillaryId]);
|
||||
entityChangesService.moveEntityChangeToTop('note_ancillary_contents', noteAncillaryId);
|
||||
}
|
||||
|
||||
log.info(`Forcing note sync for ${noteId}`);
|
||||
|
||||
@@ -126,7 +126,7 @@ function register(app) {
|
||||
apiRoute(PUT, '/api/notes/:noteId/sort-children', notesApiRoute.sortChildNotes);
|
||||
apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectNote);
|
||||
apiRoute(PUT, '/api/notes/:noteId/type', notesApiRoute.setNoteTypeMime);
|
||||
apiRoute(PUT, '/api/notes/:noteId/attachments/:name', notesApiRoute.saveNoteAttachment);
|
||||
apiRoute(PUT, '/api/notes/:noteId/ancillaries/:name', notesApiRoute.saveNoteAncillary);
|
||||
apiRoute(GET, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.getNoteRevisions);
|
||||
apiRoute(DELETE, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.eraseAllNoteRevisions);
|
||||
apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision);
|
||||
|
||||
@@ -200,21 +200,21 @@ class ConsistencyChecks {
|
||||
});
|
||||
|
||||
this.findAndFixIssues(`
|
||||
SELECT noteAttachmentId, note_attachments.noteId AS noteId
|
||||
FROM note_attachments
|
||||
SELECT noteAncillaryId, note_ancillaries.noteId AS noteId
|
||||
FROM note_ancillaries
|
||||
LEFT JOIN notes USING (noteId)
|
||||
WHERE notes.noteId IS NULL
|
||||
AND note_attachments.isDeleted = 0`,
|
||||
({noteAttachmentId, noteId}) => {
|
||||
AND note_ancillaries.isDeleted = 0`,
|
||||
({noteAncillaryId, noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const noteAttachment = becca.getNoteAttachment(noteAttachmentId);
|
||||
noteAttachment.markAsDeleted();
|
||||
const noteAncillary = becca.getNoteAncillary(noteAncillaryId);
|
||||
noteAncillary.markAsDeleted();
|
||||
|
||||
this.reloadNeeded = false;
|
||||
|
||||
logFix(`Note attachment '${noteAttachmentId}' has been deleted since it references missing note '${noteId}'`);
|
||||
logFix(`Note ancillary '${noteAncillaryId}' has been deleted since it references missing note '${noteId}'`);
|
||||
} else {
|
||||
logError(`Note attachment '${noteAttachmentId}' references missing note '${noteId}'`);
|
||||
logError(`Note ancillary '${noteAncillaryId}' references missing note '${noteId}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -326,22 +326,22 @@ class ConsistencyChecks {
|
||||
});
|
||||
|
||||
this.findAndFixIssues(`
|
||||
SELECT noteAttachmentId,
|
||||
note_attachments.noteId AS noteId
|
||||
FROM note_attachments
|
||||
SELECT noteAncillaryId,
|
||||
note_ancillaries.noteId AS noteId
|
||||
FROM note_ancillaries
|
||||
JOIN notes USING (noteId)
|
||||
WHERE notes.isDeleted = 1
|
||||
AND note_attachments.isDeleted = 0`,
|
||||
({noteAttachmentId, noteId}) => {
|
||||
AND note_ancillaries.isDeleted = 0`,
|
||||
({noteAncillaryId, noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const noteAttachment = becca.getNoteAttachment(noteAttachmentId);
|
||||
noteAttachment.markAsDeleted();
|
||||
const noteAncillary = becca.getNoteAncillary(noteAncillaryId);
|
||||
noteAncillary.markAsDeleted();
|
||||
|
||||
this.reloadNeeded = false;
|
||||
|
||||
logFix(`Note attachment '${noteAttachmentId}' has been deleted since associated note '${noteId}' is deleted.`);
|
||||
logFix(`Note ancillary '${noteAncillaryId}' has been deleted since associated note '${noteId}' is deleted.`);
|
||||
} else {
|
||||
logError(`Note attachment '${noteAttachmentId}' is not deleted even though associated note '${noteId}' is deleted.`)
|
||||
logError(`Note ancillary '${noteAncillaryId}' is not deleted even though associated note '${noteId}' is deleted.`)
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -637,8 +637,8 @@ class ConsistencyChecks {
|
||||
this.runEntityChangeChecks("note_contents", "noteId");
|
||||
this.runEntityChangeChecks("note_revisions", "noteRevisionId");
|
||||
this.runEntityChangeChecks("note_revision_contents", "noteRevisionId");
|
||||
this.runEntityChangeChecks("note_attachments", "noteAttachmentId");
|
||||
this.runEntityChangeChecks("note_attachment_contents", "noteAttachmentId");
|
||||
this.runEntityChangeChecks("note_ancillaries", "noteAncillaryId");
|
||||
this.runEntityChangeChecks("note_ancillary_contents", "noteAncillaryId");
|
||||
this.runEntityChangeChecks("branches", "branchId");
|
||||
this.runEntityChangeChecks("attributes", "attributeId");
|
||||
this.runEntityChangeChecks("etapi_tokens", "etapiTokenId");
|
||||
@@ -734,7 +734,7 @@ class ConsistencyChecks {
|
||||
return `${tableName}: ${count}`;
|
||||
}
|
||||
|
||||
const tables = [ "notes", "note_revisions", "note_attachments", "branches", "attributes", "etapi_tokens" ];
|
||||
const tables = [ "notes", "note_revisions", "note_ancillaries", "branches", "attributes", "etapi_tokens" ];
|
||||
|
||||
log.info(`Table counts: ${tables.map(tableName => getTableRowCount(tableName)).join(", ")}`);
|
||||
}
|
||||
|
||||
@@ -128,8 +128,8 @@ function fillAllEntityChanges() {
|
||||
fillEntityChanges("branches", "branchId");
|
||||
fillEntityChanges("note_revisions", "noteRevisionId");
|
||||
fillEntityChanges("note_revision_contents", "noteRevisionId");
|
||||
fillEntityChanges("note_attachments", "noteAttachmentId");
|
||||
fillEntityChanges("note_attachment_contents", "noteAttachmentId");
|
||||
fillEntityChanges("note_ancillaries", "noteAncillaryId");
|
||||
fillEntityChanges("note_ancillary_contents", "noteAncillaryId");
|
||||
fillEntityChanges("attributes", "attributeId");
|
||||
fillEntityChanges("etapi_tokens", "etapiTokenId");
|
||||
fillEntityChanges("options", "name", 'isSynced = 1');
|
||||
|
||||
@@ -170,19 +170,19 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
||||
meta.dataFileName = getDataFileName(note.type, note.mime, baseFileName, existingFileNames);
|
||||
}
|
||||
|
||||
const attachments = note.getNoteAttachments();
|
||||
const ancillaries = note.getNoteAncillaries();
|
||||
|
||||
if (attachments.length > 0) {
|
||||
meta.attachments = attachments
|
||||
.filter(attachment => ["canvasSvg", "mermaidSvg"].includes(attachment.name))
|
||||
.map(attachment => ({
|
||||
if (ancillaries.length > 0) {
|
||||
meta.ancillaries = ancillaries
|
||||
.filter(ancillary => ["canvasSvg", "mermaidSvg"].includes(ancillary.name))
|
||||
.map(ancillary => ({
|
||||
|
||||
name: attachment.name,
|
||||
mime: attachment.mime,
|
||||
name: ancillary.name,
|
||||
mime: ancillary.mime,
|
||||
dataFileName: getDataFileName(
|
||||
null,
|
||||
attachment.mime,
|
||||
baseFileName + "_" + attachment.name,
|
||||
ancillary.mime,
|
||||
baseFileName + "_" + ancillary.name,
|
||||
existingFileNames
|
||||
)
|
||||
}));
|
||||
@@ -234,11 +234,11 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
||||
|
||||
const meta = noteIdToMeta[targetPath[targetPath.length - 1]];
|
||||
|
||||
// for some note types it's more user-friendly to see the attachment (if exists) instead of source note
|
||||
const preferredAttachment = (meta.attachments || []).find(attachment => ['mermaidSvg', 'canvasSvg'].includes(attachment.name));
|
||||
// for some note types it's more user-friendly to see the ancillary (if exists) instead of source note
|
||||
const preferredAncillary = (meta.ancillaries || []).find(ancillary => ['mermaidSvg', 'canvasSvg'].includes(ancillary.name));
|
||||
|
||||
if (preferredAttachment) {
|
||||
url += encodeURIComponent(preferredAttachment.dataFileName);
|
||||
if (preferredAncillary) {
|
||||
url += encodeURIComponent(preferredAncillary.dataFileName);
|
||||
} else {
|
||||
// link can target note which is only "folder-note" and as such will not have a file in an export
|
||||
url += encodeURIComponent(meta.dataFileName || meta.dirFileName);
|
||||
@@ -344,12 +344,12 @@ ${markdownContent}`;
|
||||
|
||||
taskContext.increaseProgressCount();
|
||||
|
||||
for (const attachmentMeta of noteMeta.attachments || []) {
|
||||
const noteAttachment = note.getNoteAttachmentByName(attachmentMeta.name);
|
||||
const content = noteAttachment.getContent();
|
||||
for (const ancillaryMeta of noteMeta.ancillaries || []) {
|
||||
const noteAncillary = note.getNoteAncillaryByName(ancillaryMeta.name);
|
||||
const content = noteAncillary.getContent();
|
||||
|
||||
archive.append(content, {
|
||||
name: filePathPrefix + attachmentMeta.dataFileName,
|
||||
name: filePathPrefix + ancillaryMeta.dataFileName,
|
||||
date: dateUtils.parseDateTime(note.utcDateModified)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const treeService = require("../tree");
|
||||
const yauzl = require("yauzl");
|
||||
const htmlSanitizer = require('../html_sanitizer');
|
||||
const becca = require("../../becca/becca");
|
||||
const BNoteAttachment = require("../../becca/entities/bnote_attachment");
|
||||
const BNoteAncillary = require("../../becca/entities/bnote_ancillary");
|
||||
|
||||
/**
|
||||
* @param {TaskContext} taskContext
|
||||
@@ -65,7 +65,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
};
|
||||
|
||||
let parent;
|
||||
let attachmentMeta = false;
|
||||
let ancillaryMeta = false;
|
||||
|
||||
for (const segment of pathSegments) {
|
||||
if (!cursor || !cursor.children || cursor.children.length === 0) {
|
||||
@@ -77,10 +77,10 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
|
||||
if (!cursor) {
|
||||
for (const file of parent.children) {
|
||||
for (const attachment of file.attachments || []) {
|
||||
if (attachment.dataFileName === segment) {
|
||||
for (const ancillary of file.ancillaries || []) {
|
||||
if (ancillary.dataFileName === segment) {
|
||||
cursor = file;
|
||||
attachmentMeta = attachment;
|
||||
ancillaryMeta = ancillary;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
return {
|
||||
parentNoteMeta: parent,
|
||||
noteMeta: cursor,
|
||||
attachmentMeta
|
||||
ancillaryMeta
|
||||
};
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
}
|
||||
|
||||
function saveNote(filePath, content) {
|
||||
const {parentNoteMeta, noteMeta, attachmentMeta} = getMeta(filePath);
|
||||
const {parentNoteMeta, noteMeta, ancillaryMeta} = getMeta(filePath);
|
||||
|
||||
if (noteMeta?.noImport) {
|
||||
return;
|
||||
@@ -381,14 +381,14 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
|
||||
const noteId = getNoteId(noteMeta, filePath);
|
||||
|
||||
if (attachmentMeta) {
|
||||
const noteAttachment = new BNoteAttachment({
|
||||
if (ancillaryMeta) {
|
||||
const noteAncillary = new BNoteAncillary({
|
||||
noteId,
|
||||
name: attachmentMeta.name,
|
||||
mime: attachmentMeta.mime
|
||||
name: ancillaryMeta.name,
|
||||
mime: ancillaryMeta.mime
|
||||
});
|
||||
|
||||
noteAttachment.setContent(content);
|
||||
noteAncillary.setContent(content);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
37
src/services/note_ancillaries.js
Normal file
37
src/services/note_ancillaries.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const protectedSession = require("./protected_session.js");
|
||||
const log = require("./log.js");
|
||||
|
||||
/**
|
||||
* @param {BNote} note
|
||||
*/
|
||||
function protectNoteAncillaries(note) {
|
||||
for (const noteAncillary of note.getNoteAncillaries()) {
|
||||
if (note.isProtected !== noteAncillary.isProtected) {
|
||||
if (!protectedSession.isProtectedSessionAvailable()) {
|
||||
log.error("Protected session is not available to fix note ancillaries.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = noteAncillary.getContent();
|
||||
|
||||
noteAncillary.isProtected = note.isProtected;
|
||||
|
||||
// this will force de/encryption
|
||||
noteAncillary.setContent(content);
|
||||
|
||||
noteAncillary.save();
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Could not un/protect note ancillary ID = ${noteAncillary.noteAncillaryId}`);
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
protectNoteAncillaries
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
const protectedSession = require("./protected_session.js");
|
||||
const log = require("./log.js");
|
||||
|
||||
/**
|
||||
* @param {BNote} note
|
||||
*/
|
||||
function protectNoteAttachments(note) {
|
||||
for (const noteAttachment of note.getNoteAttachments()) {
|
||||
if (note.isProtected !== noteAttachment.isProtected) {
|
||||
if (!protectedSession.isProtectedSessionAvailable()) {
|
||||
log.error("Protected session is not available to fix note attachments.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = noteAttachment.getContent();
|
||||
|
||||
noteAttachment.isProtected = note.isProtected;
|
||||
|
||||
// this will force de/encryption
|
||||
noteAttachment.setContent(content);
|
||||
|
||||
noteAttachment.save();
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Could not un/protect note attachment ID = ${noteAttachment.noteAttachmentId}`);
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
protectNoteAttachments
|
||||
}
|
||||
@@ -9,7 +9,7 @@ const protectedSessionService = require('../services/protected_session');
|
||||
const log = require('../services/log');
|
||||
const utils = require('../services/utils');
|
||||
const noteRevisionService = require('../services/note_revisions');
|
||||
const noteAttachmentService = require('../services/note_attachments');
|
||||
const noteAncillarieservice = require('../services/note_ancillaries');
|
||||
const attributeService = require('../services/attributes');
|
||||
const request = require('./request');
|
||||
const path = require('path');
|
||||
@@ -18,7 +18,7 @@ const becca = require('../becca/becca');
|
||||
const BBranch = require('../becca/entities/bbranch');
|
||||
const BNote = require('../becca/entities/bnote');
|
||||
const BAttribute = require('../becca/entities/battribute');
|
||||
const BNoteAttachment = require("../becca/entities/bnote_attachment");
|
||||
const BNoteAncillary = require("../becca/entities/bnote_ancillary");
|
||||
const dayjs = require("dayjs");
|
||||
const htmlSanitizer = require("./html_sanitizer");
|
||||
const ValidationError = require("../errors/validation_error");
|
||||
@@ -302,7 +302,7 @@ function protectNote(note, protect) {
|
||||
}
|
||||
|
||||
noteRevisionService.protectNoteRevisions(note);
|
||||
noteAttachmentService.protectNoteAttachments(note);
|
||||
noteAncillarieservice.protectNoteAncillaries(note);
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Could not un/protect note ID = ${note.noteId}`);
|
||||
@@ -593,7 +593,7 @@ function saveNoteRevisionIfNeeded(note) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateNoteData(noteId, content, attachments = []) {
|
||||
function updateNoteData(noteId, content, ancillaries = []) {
|
||||
const note = becca.getNote(noteId);
|
||||
|
||||
if (!note.isContentAvailable()) {
|
||||
@@ -606,8 +606,8 @@ function updateNoteData(noteId, content, attachments = []) {
|
||||
|
||||
note.setContent(content);
|
||||
|
||||
for (const {name, mime, content} of attachments) {
|
||||
note.saveNoteAttachment(name, mime, content);
|
||||
for (const {name, mime, content} of ancillaries) {
|
||||
note.saveNoteAncillary(name, mime, content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,14 +675,14 @@ function undeleteBranch(branchId, deleteId, taskContext) {
|
||||
new BAttribute(attribute).save({skipValidation: true});
|
||||
}
|
||||
|
||||
const noteAttachments = sql.getRows(`
|
||||
SELECT * FROM note_attachments
|
||||
const noteAncillaries = sql.getRows(`
|
||||
SELECT * FROM note_ancillaries
|
||||
WHERE isDeleted = 1
|
||||
AND deleteId = ?
|
||||
AND noteId = ?`, [deleteId, note.noteId]);
|
||||
|
||||
for (const noteAttachment of noteAttachments) {
|
||||
new BNoteAttachment(noteAttachment).save();
|
||||
for (const noteAncillary of noteAncillaries) {
|
||||
new BNoteAncillary(noteAncillary).save();
|
||||
}
|
||||
|
||||
const childBranchIds = sql.getColumn(`
|
||||
@@ -737,7 +737,7 @@ function runOcr(note, buffer) {
|
||||
try {
|
||||
const plainText = textExtractingService.ocrTextFromBuffer(buffer);
|
||||
|
||||
note.saveNoteAttachment('plainText', 'text/plain', plainText);
|
||||
note.saveNoteAncillary('plainText', 'text/plain', plainText);
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`OCR on note '${note.noteId}' failed with error '${e.message}', stack ${e.stack}`);
|
||||
@@ -780,10 +780,10 @@ function eraseNotes(noteIdsToErase) {
|
||||
|
||||
noteRevisionService.eraseNoteRevisions(noteRevisionIdsToErase);
|
||||
|
||||
const noteAttachmentIdsToErase = sql.getManyRows(`SELECT noteAttachmentId FROM note_attachments WHERE noteId IN (???)`, noteIdsToErase)
|
||||
.map(row => row.noteAttachmentId);
|
||||
const noteAncillaryIdsToErase = sql.getManyRows(`SELECT noteAncillaryId FROM note_ancillaries WHERE noteId IN (???)`, noteIdsToErase)
|
||||
.map(row => row.noteAncillaryId);
|
||||
|
||||
eraseNoteAttachments(noteAttachmentIdsToErase);
|
||||
eraseNoteAncillaries(noteAncillaryIdsToErase);
|
||||
|
||||
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
|
||||
}
|
||||
@@ -820,18 +820,18 @@ function eraseAttributes(attributeIdsToErase) {
|
||||
log.info(`Erased attributes: ${JSON.stringify(attributeIdsToErase)}`);
|
||||
}
|
||||
|
||||
function eraseNoteAttachments(noteAttachmentIdsToErase) {
|
||||
if (noteAttachmentIdsToErase.length === 0) {
|
||||
function eraseNoteAncillaries(noteAncillaryIdsToErase) {
|
||||
if (noteAncillaryIdsToErase.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(`Removing note attachments: ${JSON.stringify(noteAttachmentIdsToErase)}`);
|
||||
log.info(`Removing note ancillaries: ${JSON.stringify(noteAncillaryIdsToErase)}`);
|
||||
|
||||
sql.executeMany(`DELETE FROM note_attachments WHERE noteAttachmentId IN (???)`, noteAttachmentIdsToErase);
|
||||
sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'note_attachments' AND entityId IN (???)`, noteAttachmentIdsToErase);
|
||||
sql.executeMany(`DELETE FROM note_ancillaries WHERE noteAncillaryId IN (???)`, noteAncillaryIdsToErase);
|
||||
sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'note_ancillaries' AND entityId IN (???)`, noteAncillaryIdsToErase);
|
||||
|
||||
sql.executeMany(`DELETE FROM note_attachment_contents WHERE noteAttachmentId IN (???)`, noteAttachmentIdsToErase);
|
||||
sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'note_attachment_contents' AND entityId IN (???)`, noteAttachmentIdsToErase);
|
||||
sql.executeMany(`DELETE FROM note_ancillary_contents WHERE noteAncillaryId IN (???)`, noteAncillaryIdsToErase);
|
||||
sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'note_ancillary_contents' AND entityId IN (???)`, noteAncillaryIdsToErase);
|
||||
}
|
||||
|
||||
function eraseDeletedEntities(eraseEntitiesAfterTimeInSeconds = null) {
|
||||
@@ -968,16 +968,16 @@ function duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapp
|
||||
attr.save();
|
||||
}
|
||||
|
||||
for (const noteAttachment of origNote.getNoteAttachments()) {
|
||||
const duplNoteAttachment = new BNoteAttachment({
|
||||
...noteAttachment,
|
||||
noteAttachmentId: undefined,
|
||||
for (const noteAncillary of origNote.getNoteAncillaries()) {
|
||||
const duplNoteAncillary = new BNoteAncillary({
|
||||
...noteAncillary,
|
||||
noteAncillaryId: undefined,
|
||||
noteId: newNote.noteId
|
||||
});
|
||||
|
||||
duplNoteAttachment.save();
|
||||
duplNoteAncillary.save();
|
||||
|
||||
duplNoteAttachment.setContent(noteAttachment.getContent());
|
||||
duplNoteAncillary.setContent(noteAncillary.getContent());
|
||||
}
|
||||
|
||||
for (const childBranch of origNote.getChildBranches()) {
|
||||
|
||||
@@ -50,7 +50,7 @@ class NoteContentFulltextExp extends Expression {
|
||||
|
||||
for (const row of sql.iterateRows(`
|
||||
SELECT noteId, 'plainText' as type, mime, content, isProtected
|
||||
FROM note_attachments JOIN note_attachment_contents USING (noteAttachmentId)
|
||||
FROM note_ancillaries JOIN note_ancillary_contents USING (noteAncillaryId)
|
||||
WHERE name IN ('plainText') AND isDeleted = 0`)) {
|
||||
|
||||
if (!resultNoteSet.hasNoteId(row.noteId)) {
|
||||
|
||||
@@ -321,7 +321,7 @@ function getEntityChangeRow(entityName, entityId) {
|
||||
throw new Error(`Entity ${entityName} ${entityId} not found.`);
|
||||
}
|
||||
|
||||
if (['note_contents', 'note_revision_contents', 'note_attachment_contents'].includes(entityName) && entity.content !== null) {
|
||||
if (['note_contents', 'note_revision_contents', 'note_ancillary_contents'].includes(entityName) && entity.content !== null) {
|
||||
if (typeof entity.content === 'string') {
|
||||
entity.content = Buffer.from(entity.content, 'UTF-8');
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ function updateNormalEntity(remoteEntityChange, remoteEntityRow, instanceId) {
|
||||
|| localEntityChange.utcDateChanged < remoteEntityChange.utcDateChanged
|
||||
|| localEntityChange.hash !== remoteEntityChange.hash // sync error, we should still update
|
||||
) {
|
||||
if (['note_contents', 'note_revision_contents', 'note_attachment_contents'].includes(remoteEntityChange.entityName)) {
|
||||
if (['note_contents', 'note_revision_contents', 'note_ancillary_contents'].includes(remoteEntityChange.entityName)) {
|
||||
remoteEntityRow.content = handleContent(remoteEntityRow.content);
|
||||
}
|
||||
|
||||
@@ -116,8 +116,8 @@ function eraseEntity(entityChange, instanceId) {
|
||||
"attributes",
|
||||
"note_revisions",
|
||||
"note_revision_contents",
|
||||
"note_attachments",
|
||||
"note_attachment_contents"
|
||||
"note_ancillaries",
|
||||
"note_ancillary_contents"
|
||||
];
|
||||
|
||||
if (!entityNames.includes(entityName)) {
|
||||
|
||||
@@ -96,7 +96,7 @@ async function extractTextFromPdf(note, buffer) {
|
||||
|
||||
strings = strings.filter(str => str?.trim());
|
||||
|
||||
note.saveNoteAttachment('plainText', 'text/plain', strings.join(" "));
|
||||
note.saveNoteAncillary('plainText', 'text/plain', strings.join(" "));
|
||||
}
|
||||
catch (e) {
|
||||
log.info(`Extracting text from PDF on note '${note.noteId}' failed with error '${e.message}', stack ${e.stack}`);
|
||||
@@ -126,4 +126,4 @@ async function ocrTextFromBuffer(buffer) {
|
||||
module.exports = {
|
||||
ocrTextFromBuffer,
|
||||
extractTextFromPdf
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user