refactored note attachment into note ancillary

This commit is contained in:
zadam 2023-01-28 13:13:46 +01:00
parent 78954268ab
commit 37ba76fdd8
30 changed files with 212 additions and 211 deletions

View File

@ -4,7 +4,7 @@ UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share'
UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_revisions SET title = 'title';
UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_attachment_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_ancillary_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'workspaceInbox', 'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteContentChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'workspaceInbox', 'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteContentChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');

View File

@ -1,6 +1,6 @@
CREATE TABLE IF NOT EXISTS "note_attachments"
CREATE TABLE IF NOT EXISTS "note_ancillaries"
(
noteAttachmentId TEXT not null primary key,
noteAncillaryId TEXT not null primary key,
noteId TEXT not null,
name TEXT not null,
mime TEXT not null,
@ -10,11 +10,11 @@ CREATE TABLE IF NOT EXISTS "note_attachments"
isDeleted INT not null,
`deleteId` TEXT DEFAULT NULL);
CREATE TABLE IF NOT EXISTS "note_attachment_contents" (`noteAttachmentId` TEXT NOT NULL PRIMARY KEY,
CREATE TABLE IF NOT EXISTS "note_ancillary_contents" (`noteAncillaryId` TEXT NOT NULL PRIMARY KEY,
`content` TEXT DEFAULT NULL,
`utcDateModified` TEXT NOT NULL);
CREATE INDEX IDX_note_attachments_name
on note_attachments (name);
CREATE UNIQUE INDEX IDX_note_attachments_noteId_name
on note_attachments (noteId, name);
CREATE INDEX IDX_note_ancillaries_name
on note_ancillaries (name);
CREATE UNIQUE INDEX IDX_note_ancillaries_noteId_name
on note_ancillaries (noteId, name);

View File

@ -29,11 +29,11 @@ module.exports = async () => {
}
}
catch (e) {
log.info(`Could not create a note attachment for canvas "${note.noteId}" with error: ${e.message} ${e.stack}`);
log.info(`Could not create a note ancillary for canvas "${note.noteId}" with error: ${e.message} ${e.stack}`);
continue;
}
note.saveNoteAttachment('canvasSvg', 'image/svg+xml', svg);
note.saveNoteAncillary('canvasSvg', 'image/svg+xml', svg);
}
});
};
};

View File

@ -112,9 +112,9 @@ CREATE TABLE IF NOT EXISTS "recent_notes"
notePath TEXT not null,
utcDateCreated TEXT not null
);
CREATE TABLE IF NOT EXISTS "note_attachments"
CREATE TABLE IF NOT EXISTS "note_ancillaries"
(
noteAttachmentId TEXT not null primary key,
noteAncillaryId TEXT not null primary key,
noteId TEXT not null,
name TEXT not null,
mime TEXT not null,
@ -123,10 +123,10 @@ CREATE TABLE IF NOT EXISTS "note_attachments"
utcDateModified TEXT not null,
isDeleted INT not null,
`deleteId` TEXT DEFAULT NULL);
CREATE TABLE IF NOT EXISTS "note_attachment_contents" (`noteAttachmentId` TEXT NOT NULL PRIMARY KEY,
CREATE TABLE IF NOT EXISTS "note_ancillary_contents" (`noteAncillaryId` TEXT NOT NULL PRIMARY KEY,
`content` TEXT DEFAULT NULL,
`utcDateModified` TEXT NOT NULL);
CREATE INDEX IDX_note_attachments_name
on note_attachments (name);
CREATE UNIQUE INDEX IDX_note_attachments_noteId_name
on note_attachments (noteId, name);
CREATE INDEX IDX_note_ancillaries_name
on note_ancillaries (name);
CREATE UNIQUE INDEX IDX_note_ancillaries_noteId_name
on note_ancillaries (noteId, name);

1
package-lock.json generated
View File

@ -5,6 +5,7 @@
"requires": true,
"packages": {
"": {
"name": "trilium",
"version": "0.58.7",
"hasInstallScript": true,
"license": "AGPL-3.0-only",

View File

@ -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,

View File

@ -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);

View File

@ -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() {

View File

@ -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;

View File

@ -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

View File

@ -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")

View File

@ -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 {

View File

@ -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
});

View File

@ -284,7 +284,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
return {
content: JSON.stringify(content),
attachments: [
ancillaries: [
{
name: 'canvasSvg',
mime: 'image/svg+xml',

View File

@ -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)
};

View File

@ -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() {}

View File

@ -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
};

View File

@ -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}`);

View File

@ -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);

View File

@ -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(", ")}`);
}

View File

@ -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');

View File

@ -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)
});
}

View File

@ -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;
}

View 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
}

View File

@ -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
}

View File

@ -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()) {

View File

@ -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)) {

View File

@ -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');
}

View File

@ -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)) {

View File

@ -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
};
};