diff --git a/main.go b/main.go index 34545e6..5946e06 100644 --- a/main.go +++ b/main.go @@ -124,7 +124,8 @@ func main() { // {{{ http.HandleFunc("/user/authenticate", AuthManager.AuthenticationHandler) - http.HandleFunc("/sync/node/{sequence}/{offset}", authenticated(actionSyncNode)) + http.HandleFunc("/sync/from_server/{sequence}/{offset}", authenticated(actionSyncNode)) + http.HandleFunc("/sync/to_server/{client}", authenticated(actionSyncNode)) http.HandleFunc("/node/retrieve/{uuid}", authenticated(actionNodeRetrieve)) diff --git a/node.go b/node.go index 83b1c5a..2c84563 100644 --- a/node.go +++ b/node.go @@ -228,7 +228,7 @@ func NodeCrumbs(nodeUUID string) (nodes []Node, err error) { // {{{ SELECT n.uuid, - COALESCE(n.parent_uuid, 0) AS parent_uuid, + COALESCE(n.parent_uuid, '') AS parent_uuid, n.name FROM node n INNER JOIN nodes nr ON n.uuid = nr.parent_uuid diff --git a/static/js/node.mjs b/static/js/node.mjs index d234789..0374be9 100644 --- a/static/js/node.mjs +++ b/static/js/node.mjs @@ -37,7 +37,7 @@ export class NodeUI extends Component { const crumbDivs = [ html`
_notes2.current.goToNode(ROOT_NODE)}>Start
` ] - for (let i = this.crumbs.length-1; i >= 0; i--) { + for (let i = this.crumbs.length - 1; i >= 0; i--) { const crumbNode = this.crumbs[i] crumbDivs.push(html`
_notes2.current.goToNode(crumbNode.UUID)}>${crumbNode.get('Name')}
`) } @@ -167,13 +167,32 @@ export class NodeUI extends Component { if (!this.nodeModified.value) return - await nodeStore.copyToNodesHistory(this.node.value) + /* The node history is a local store for node history. + * This could be provisioned from the server or cleared if + * deemed unnecessary. + * + * The send queue is what will be sent back to the server + * to have a recorded history of the notes. + * + * A setting to be implemented in the future could be to + * not save the history locally at all. */ + const node = this.node.value + + // The node is still in its old state and will present + // the unmodified content to the node store. + const history = nodeStore.nodesHistory.add(node) // Prepares the node object for saving. // Sets Updated value to current date and time. - const node = this.node.value - node.save() - await nodeStore.add([node]) + await node.save() + + // Updated node is added to the send queue to be stored on server. + const sendQueue = nodeStore.sendQueue.add(this.node.value) + + // Updated node is saved to the primary node store. + const nodeStoreAdding = nodeStore.add([node]) + + await Promise.all([history, sendQueue, nodeStoreAdding]) this.nodeModified.value = false }//}}} @@ -315,6 +334,7 @@ export class Node { this._children_fetched = false this.Children = [] + this.Ancestors = [] this._content = this.data.Content this._modified = false @@ -372,10 +392,15 @@ export class Node { this._decrypted = true */ }//}}} - save() {//{{{ + async save() {//{{{ this.data.Content = this._content this.data.Updated = new Date().toISOString() this._modified = false + + // When stored into database and ancestry was changed, + // the ancestry path could be interesting. + const ancestors = await nodeStore.getNodeAncestry(this) + this.data.Ancestors = ancestors.map(a => a.get('Name')).reverse() }//}}} } diff --git a/static/js/node_store.mjs b/static/js/node_store.mjs index a1a8a1a..2052985 100644 --- a/static/js/node_store.mjs +++ b/static/js/node_store.mjs @@ -10,6 +10,8 @@ export class NodeStore { this.db = null this.nodes = {} + this.sendQueue = null + this.nodesHistory = null }//}}} async initializeDB() {//{{{ return new Promise((resolve, reject) => { @@ -65,6 +67,8 @@ export class NodeStore { req.onsuccess = (event) => { this.db = event.target.result + this.sendQueue = new SimpleNodeStore(this.db, 'send_queue') + this.nodesHistory = new SimpleNodeStore(this.db, 'nodes_history') this.initializeRootNode() .then(() => this.initializeClientUUID()) .then(() => resolve()) @@ -160,64 +164,6 @@ export class NodeStore { }) }//}}} - async moveToSendQueue(nodeToMove, replaceWithNode) {//{{{ - return new Promise((resolve, reject) => { - const t = this.db.transaction(['nodes', 'send_queue'], 'readwrite') - const nodeStore = t.objectStore('nodes') - const sendQueue = t.objectStore('send_queue') - t.onerror = (event) => { - console.log('transaction error', event.target.error) - reject(event.target.error) - } - t.oncomplete = () => { - resolve() - } - - // Node to be moved is first stored in the new queue. - const queueReq = sendQueue.put(nodeToMove.data) - queueReq.onsuccess = () => { - // When added to the send queue, the node is either deleted - // or replaced with a new node. - console.debug(`Queueing ${nodeToMove.UUID} (${nodeToMove.get('Name')})`) - let nodeReq - if (replaceWithNode) - nodeReq = nodeStore.put(replaceWithNode.data) - else - nodeReq = nodeStore.delete(nodeToMove.UUID) - nodeReq.onsuccess = () => { - resolve() - } - nodeReq.onerror = (event) => { - console.log(`Error moving ${nodeToMove.UUID}`, event.target.error) - reject(event.target.error) - } - - } - queueReq.onerror = (event) => { - console.log(`Error queueing ${nodeToMove.UUID}`, event.target.error) - reject(event.target.error) - } - }) - }//}}} - async copyToNodesHistory(nodeToCopy) {//{{{ - return new Promise((resolve, reject) => { - const t = this.db.transaction('nodes_history', 'readwrite') - const nodesHistory = t.objectStore('nodes_history') - t.oncomplete = () => { - resolve() - } - t.onerror = (event) => { - console.log('transaction error', event.target.error) - reject(event.target.error) - } - - const historyReq = nodesHistory.put(nodeToCopy.data) - historyReq.onerror = (event) => { - console.log(`Error copying ${nodeToCopy.UUID}`, event.target.error) - reject(event.target.error) - } - }) - }//}}} async storeNode(node) {//{{{ return new Promise((resolve, reject) => { const t = this.db.transaction('nodes', 'readwrite') @@ -397,4 +343,59 @@ export class NodeStore { }//}}} } +class SimpleNodeStore { + constructor(db, storeName) {//{{{ + this.db = db + this.storeName = storeName + }//}}} + async add(node) {//{{{ + return new Promise((resolve, reject) => { + const t = this.db.transaction(['nodes', this.storeName], 'readwrite') + const store = t.objectStore(this.storeName) + t.onerror = (event) => { + console.log('transaction error', event.target.error) + reject(event.target.error) + } + + // Node to be moved is first stored in the new queue. + const req = store.put(node.data) + req.onsuccess = () => { + resolve() + } + req.onerror = (event) => { + console.log(`Error adding ${node.UUID}`, event.target.error) + reject(event.target.error) + } + }) + }//}}} + async retrieve(limit) {//{{{ + return new Promise((resolve, reject) => { + const cursorReq = this.db + .transaction(['nodes', this.storeName], 'readonly') + .objectStore(this.storeName) + .index('updated') + .openCursor() + + let retrieved = 0 + const nodes = [] + + cursorReq.onsuccess = (event) => { + const cursor = event.target.result + if (!cursor) { + resolve(nodes) + return + } + retrieved++ + nodes.push(cursor.value) + if (retrieved === limit) { + resolve(nodes) + return + } + + cursor.continue() + } + }) + }//}}} +} + // vim: foldmethod=marker diff --git a/static/js/notes2.mjs b/static/js/notes2.mjs index 5e62ffa..3a91a29 100644 --- a/static/js/notes2.mjs +++ b/static/js/notes2.mjs @@ -13,7 +13,7 @@ export class Notes2 extends Component { startNode: null, } - Sync.nodes().then(durationNodes => + Sync.nodesFromServer().then(durationNodes => console.log(`Total time: ${Math.round(100 * durationNodes) / 100}s`) ) diff --git a/static/js/sync.mjs b/static/js/sync.mjs index 2814fe4..8388098 100644 --- a/static/js/sync.mjs +++ b/static/js/sync.mjs @@ -6,7 +6,7 @@ export class Sync { this.foo = '' } - static async nodes() { + static async nodesFromServer() {//{{{ let duration = 0 const syncStart = Date.now() try { @@ -22,7 +22,7 @@ export class Sync { let batch = 0 do { batch++ - res = await API.query('POST', `/sync/node/${oldMax}/${offset}`, { ClientUUID: clientUUID.value }) + res = await API.query('POST', `/sync/from_server/${oldMax}/${offset}`, { ClientUUID: clientUUID.value }) if (res.Nodes.length > 0) console.log(`Node sync batch #${batch}`) offset += res.Nodes.length @@ -55,8 +55,8 @@ export class Sync { console.log(`Node sync took ${duration}s`, count) } return duration - } - static async handleNode(backendNode) { + }//}}} + static async handleNode(backendNode) {//{{{ try { /* Retrieving the local copy of this node from IndexedDB. * The backend node can be discarded if it is older than @@ -69,16 +69,38 @@ export class Sync { return } - // local node is older than the backend node - // and moved into the send_queue table for later sync to backend. - return nodeStore.moveToSendQueue(localNode, backendNode) + /* If the local node hasn't seen unsynchronized change, + * it can be replaced without anything else being done + * since it is already on the backend server. + * + * If the local node has seen change, the change is already + * placed into the send_queue anyway. */ + return nodeStore.add([backendNode]) + }) - .catch(async e => { + .catch(async () => { // Not found in IndexedDB - OK to just insert since it only exists in backend. return nodeStore.add([backendNode]) }) } catch (e) { console.error(e) } - } + }//}}} + + static async nodesToServer() {//{{{ + try { + const nodesToSend = await nodeStore.sendQueue.retrieve(100) + const clientUUID = await nodeStore.getAppState('client_uuid') + const request = { + Nodes: nodesToSend, + ClientUUID: clientUUID.value, + } + res = await API.query('POST', `/sync/from_server/${oldMax}/${offset}`, { ClientUUID: clientUUID.value }) + console.log(res) + + } catch (e) { + console.log(e) + alert(e) + } + }//}}} }