diff --git a/docs/backend_api/AbstractBeccaEntity.html b/docs/backend_api/AbstractBeccaEntity.html index 739d9d2ca..bc506e960 100644 --- a/docs/backend_api/AbstractBeccaEntity.html +++ b/docs/backend_api/AbstractBeccaEntity.html @@ -183,7 +183,7 @@
api.log(api.startNote.
- Source:
@@ -240,7 +240,7 @@ available in the JS backend notes. You can use e.g. api.log(api.startNote.
- Source:
@@ -352,7 +352,7 @@ available in the JS backend notes. You can use e.g. api.log(api.startNote.
- Source:
@@ -462,7 +462,7 @@ available in the JS backend notes. You can use e.g. api.log(api.startNote.
- Source:
@@ -572,7 +572,7 @@ available in the JS backend notes. You can use e.g. api.log(api.startNote.
- Source:
@@ -682,7 +682,7 @@ available in the JS backend notes. You can use e.g. api.log(api.startNote.
- Source:
@@ -792,7 +792,7 @@ available in the JS backend notes. You can use e.g. api.log(api.startNote.
- Source:
@@ -902,7 +902,7 @@ available in the JS backend notes. You can use e.g. api.log(api.startNote.
- Source:
@@ -1012,7 +1012,7 @@ available in the JS backend notes. You can use e.g. api.log(api.startNote.
- Source:
@@ -1188,7 +1188,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -1691,7 +1691,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -2311,7 +2311,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -2817,7 +2817,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -3018,7 +3018,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -3200,7 +3200,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -3401,7 +3401,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -3552,7 +3552,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -3753,7 +3753,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -3855,7 +3855,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -4013,7 +4013,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -4167,7 +4167,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -4368,7 +4368,7 @@ JSON MIME type. See also createNewNote() for more options.
- Source:
@@ -4478,7 +4478,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -4679,7 +4679,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -4833,7 +4833,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -5034,7 +5034,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -5235,7 +5235,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -5341,7 +5341,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -5511,7 +5511,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -5814,7 +5814,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -6015,7 +6015,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -6168,7 +6168,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -6323,7 +6323,7 @@ if some action needs to happen on only one specific instance.
- Source:
@@ -6508,7 +6508,7 @@ instances execute the given function.
- Source:
@@ -6566,6 +6566,170 @@ instances execute the given function.
+ runOutsideOfSync(callback) → {Promise}
+
+
+
+
+
+
+
+ Sync process can make data intermittently inconsistent. Scripts which require strong data consistency
+can use this function to wait for a possible sync process to finish and prevent new sync process from starting
+while it is running.
+
+Because this is an async process, the inner callback doesn't have automatic transaction handling, so in case
+you need to make some DB changes, you need to surround your call with api.transactional(...)
+
+
+
+
+
+
+
+
+
+
+ Parameters:
+
+
+
+
+
+
+ Name
+
+
+ Type
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+ callback
+
+
+
+
+
+function
+
+
+
+
+
+
+
+
+
+ function to be executed while sync process is not running
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ - Source:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+
+
+ - resolves once the callback is finished (callback is awaited)
+
+
+
+
+
+ -
+ Type
+
+ -
+
+Promise
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
searchForNote(query, searchParamsopt) → {BNote|null}
@@ -6711,7 +6875,7 @@ instances execute the given function.
- Source:
@@ -6913,7 +7077,7 @@ instances execute the given function.
- Source:
@@ -7122,7 +7286,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
- Source:
@@ -7465,7 +7629,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
- Source:
@@ -7689,7 +7853,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
- Source:
@@ -7845,7 +8009,7 @@ exists, then we'll use that transaction.
- Source:
@@ -8000,7 +8164,7 @@ exists, then we'll use that transaction.
- Source:
diff --git a/docs/backend_api/becca_entities_abstract_becca_entity.js.html b/docs/backend_api/becca_entities_abstract_becca_entity.js.html
index d54522bc1..e60c3d4b5 100644
--- a/docs/backend_api/becca_entities_abstract_becca_entity.js.html
+++ b/docs/backend_api/becca_entities_abstract_becca_entity.js.html
@@ -46,31 +46,11 @@ let becca = null;
class AbstractBeccaEntity {
/** @protected */
beforeSaving() {
- this.generateIdIfNecessary();
- }
-
- /** @protected */
- generateIdIfNecessary() {
if (!this[this.constructor.primaryKeyName]) {
this[this.constructor.primaryKeyName] = utils.newEntityId();
}
}
- /** @protected */
- generateHash(isDeleted = false) {
- let contentToHash = "";
-
- for (const propertyName of this.constructor.hashedProperties) {
- contentToHash += `|${this[propertyName]}`;
- }
-
- if (isDeleted) {
- contentToHash += "|deleted";
- }
-
- return utils.hash(contentToHash).substr(0, 10);
- }
-
/** @protected */
getUtcDateChanged() {
return this.utcDateModified || this.utcDateCreated;
@@ -89,7 +69,7 @@ class AbstractBeccaEntity {
}
/** @protected */
- putEntityChange(isDeleted = false) {
+ putEntityChange(isDeleted) {
entityChangesService.putEntityChange({
entityName: this.constructor.entityName,
entityId: this[this.constructor.primaryKeyName],
@@ -100,11 +80,37 @@ class AbstractBeccaEntity {
});
}
+ /**
+ * @protected
+ * @returns {string}
+ */
+ generateHash(isDeleted) {
+ let contentToHash = "";
+
+ for (const propertyName of this.constructor.hashedProperties) {
+ contentToHash += `|${this[propertyName]}`;
+ }
+
+ if (isDeleted) {
+ contentToHash += "|deleted";
+ }
+
+ return utils.hash(contentToHash).substr(0, 10);
+ }
+
/** @protected */
getPojoToSave() {
return this.getPojo();
}
+ /**
+ * @protected
+ * @abstract
+ */
+ getPojo() {
+ throw new Error(`Unimplemented getPojo() for entity '${this.constructor.name}'`)
+ }
+
/**
* Saves entity - executes SQL, but doesn't commit the transaction on its own
*
@@ -116,9 +122,7 @@ class AbstractBeccaEntity {
const isNewEntity = !this[primaryKeyName];
- if (this.beforeSaving) {
- this.beforeSaving(opts);
- }
+ this.beforeSaving(opts);
const pojo = this.getPojoToSave();
@@ -129,7 +133,7 @@ class AbstractBeccaEntity {
return;
}
- this.putEntityChange(false);
+ this.putEntityChange(!!this.isDeleted);
if (!cls.isEntityEventsDisabled()) {
const eventPayload = {
diff --git a/docs/backend_api/becca_entities_battachment.js.html b/docs/backend_api/becca_entities_battachment.js.html
index f76056dfb..6a389b12f 100644
--- a/docs/backend_api/becca_entities_battachment.js.html
+++ b/docs/backend_api/becca_entities_battachment.js.html
@@ -48,8 +48,7 @@ const attachmentRoleToNoteTypeMapping = {
class BAttachment extends AbstractBeccaEntity {
static get entityName() { return "attachments"; }
static get primaryKeyName() { return "attachmentId"; }
- static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId",
- "utcDateScheduledForErasureSince", "utcDateModified"]; }
+ static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId", "utcDateScheduledForErasureSince"]; }
constructor(row) {
super();
diff --git a/docs/backend_api/becca_entities_bnote.js.html b/docs/backend_api/becca_entities_bnote.js.html
index 6ef8952f1..8d3cd266b 100644
--- a/docs/backend_api/becca_entities_bnote.js.html
+++ b/docs/backend_api/becca_entities_bnote.js.html
@@ -160,11 +160,17 @@ class BNote extends AbstractBeccaEntity {
*/
this.contentSize = null;
/**
- * size of the content and note revision contents in bytes
+ * size of the note content, attachment contents in bytes
* @type {int|null}
* @private
*/
- this.noteSize = null;
+ this.contentAndAttachmentsSize = null;
+ /**
+ * size of the note content, attachment contents and revision contents in bytes
+ * @type {int|null}
+ * @private
+ */
+ this.contentAndAttachmentsAndRevisionsSize = null;
/**
* number of note revisions for this note
* @type {int|null}
@@ -1635,16 +1641,12 @@ class BNote extends AbstractBeccaEntity {
revision.save(); // to generate revisionId, which is then used to save attachments
- if (this.type === 'text') {
- for (const noteAttachment of this.getAttachments()) {
- if (noteAttachment.utcDateScheduledForErasureSince) {
- continue;
- }
-
- const revisionAttachment = noteAttachment.copy();
- revisionAttachment.ownerId = revision.revisionId;
- revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
+ for (const noteAttachment of this.getAttachments()) {
+ const revisionAttachment = noteAttachment.copy();
+ revisionAttachment.ownerId = revision.revisionId;
+ revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
+ if (this.type === 'text') {
// content is rewritten to point to the revision attachments
noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`,
`attachments/${revisionAttachment.attachmentId}`);
diff --git a/docs/backend_api/becca_entities_brevision.js.html b/docs/backend_api/becca_entities_brevision.js.html
index 331e8cd54..009e72a2e 100644
--- a/docs/backend_api/becca_entities_brevision.js.html
+++ b/docs/backend_api/becca_entities_brevision.js.html
@@ -114,6 +114,29 @@ class BRevision extends AbstractBeccaEntity {
return this._getContent();
}
+ /**
+ * @returns {*}
+ * @throws Error in case of invalid JSON */
+ getJsonContent() {
+ const content = this.getContent();
+
+ if (!content || !content.trim()) {
+ return null;
+ }
+
+ return JSON.parse(content);
+ }
+
+ /** @returns {*|null} valid object or null if the content cannot be parsed as JSON */
+ getJsonContentSafely() {
+ try {
+ return this.getJsonContent();
+ }
+ catch (e) {
+ return null;
+ }
+ }
+
/**
* @param content
* @param {object} [opts]
@@ -133,6 +156,45 @@ class BRevision extends AbstractBeccaEntity {
.map(row => new BAttachment(row));
}
+ /** @returns {BAttachment|null} */
+ getAttachmentById(attachmentId, opts = {}) {
+ opts.includeContentLength = !!opts.includeContentLength;
+
+ const query = opts.includeContentLength
+ ? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
+ FROM attachments
+ JOIN blobs USING (blobId)
+ WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`
+ : `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`;
+
+ return sql.getRows(query, [this.revisionId, attachmentId])
+ .map(row => new BAttachment(row))[0];
+ }
+
+ /** @returns {BAttachment[]} */
+ getAttachmentsByRole(role) {
+ return sql.getRows(`
+ SELECT attachments.*
+ FROM attachments
+ WHERE ownerId = ?
+ AND role = ?
+ AND isDeleted = 0
+ ORDER BY position`, [this.revisionId, role])
+ .map(row => new BAttachment(row));
+ }
+
+ /** @returns {BAttachment} */
+ getAttachmentByTitle(title) {
+ return sql.getRows(`
+ SELECT attachments.*
+ FROM attachments
+ WHERE ownerId = ?
+ AND title = ?
+ AND isDeleted = 0
+ ORDER BY position`, [this.revisionId, title])
+ .map(row => new BAttachment(row))[0];
+ }
+
beforeSaving() {
super.beforeSaving();
diff --git a/docs/backend_api/services_backend_script_api.js.html b/docs/backend_api/services_backend_script_api.js.html
index a0a6c60f1..4bc787c46 100644
--- a/docs/backend_api/services_backend_script_api.js.html
+++ b/docs/backend_api/services_backend_script_api.js.html
@@ -47,6 +47,7 @@ const SpacedUpdate = require("./spaced_update");
const specialNotesService = require("./special_notes");
const branchService = require("./branches");
const exportService = require("./export/zip");
+const syncMutex = require("./sync_mutex.js");
/**
* <p>This is the main backend API interface for scripts. All the properties and methods are published in the "api" object
@@ -599,6 +600,20 @@ function BackendScriptApi(currentNote, apiParams) {
}
};
+ /**
+ * Sync process can make data intermittently inconsistent. Scripts which require strong data consistency
+ * can use this function to wait for a possible sync process to finish and prevent new sync process from starting
+ * while it is running.
+ *
+ * Because this is an async process, the inner callback doesn't have automatic transaction handling, so in case
+ * you need to make some DB changes, you need to surround your call with api.transactional(...)
+ *
+ * @method
+ * @param {function} callback - function to be executed while sync process is not running
+ * @returns {Promise} - resolves once the callback is finished (callback is awaited)
+ */
+ this.runOutsideOfSync = syncMutex.doExclusively;
+
/**
* This object contains "at your risk" and "no BC guarantees" objects for advanced use cases.
*
diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js
index 3cad85535..5854e002f 100644
--- a/src/services/backend_script_api.js
+++ b/src/services/backend_script_api.js
@@ -19,6 +19,7 @@ const SpacedUpdate = require("./spaced_update");
const specialNotesService = require("./special_notes");
const branchService = require("./branches");
const exportService = require("./export/zip");
+const syncMutex = require("./sync_mutex.js");
/**
* This is the main backend API interface for scripts. All the properties and methods are published in the "api" object
@@ -571,6 +572,20 @@ function BackendScriptApi(currentNote, apiParams) {
}
};
+ /**
+ * Sync process can make data intermittently inconsistent. Scripts which require strong data consistency
+ * can use this function to wait for a possible sync process to finish and prevent new sync process from starting
+ * while it is running.
+ *
+ * Because this is an async process, the inner callback doesn't have automatic transaction handling, so in case
+ * you need to make some DB changes, you need to surround your call with api.transactional(...)
+ *
+ * @method
+ * @param {function} callback - function to be executed while sync process is not running
+ * @returns {Promise} - resolves once the callback is finished (callback is awaited)
+ */
+ this.runOutsideOfSync = syncMutex.doExclusively;
+
/**
* This object contains "at your risk" and "no BC guarantees" objects for advanced use cases.
*
diff --git a/src/services/sync_mutex.js b/src/services/sync_mutex.js
index 819371e80..fb95d03c4 100644
--- a/src/services/sync_mutex.js
+++ b/src/services/sync_mutex.js
@@ -1,5 +1,5 @@
/**
- * Sync makes process can make data intermittently inconsistent. Processes which require strong data consistency
+ * Sync process can make data intermittently inconsistent. Processes which require strong data consistency
* (like consistency checks) can use this mutex to make sure sync isn't currently running.
*/