Indexation of files to nodes

This commit is contained in:
Magnus Åhall 2026-06-26 09:45:54 +02:00
parent 70b5285e16
commit f3ca7b9a44
3 changed files with 115 additions and 43 deletions

View file

@ -21,7 +21,7 @@ export class NodeStore {
}//}}} }//}}}
initializeDB() {//{{{ initializeDB() {//{{{
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const req = indexedDB.open('notes', 10) const req = indexedDB.open('notes', 12)
// Schema upgrades for IndexedDB. // Schema upgrades for IndexedDB.
// These can start from different points depending on updates to Notes2 since a device was online. // These can start from different points depending on updates to Notes2 since a device was online.
@ -31,6 +31,7 @@ export class NodeStore {
let sendQueue let sendQueue
let nodesHistory let nodesHistory
let files let files
let filesNodelink
const db = event.target.result const db = event.target.result
const trx = event.target.transaction const trx = event.target.transaction
@ -80,6 +81,14 @@ export class NodeStore {
case 10: case 10:
trx.objectStore('files_metadata').createIndex('byUploaded', 'uploaded', { unique: false }) trx.objectStore('files_metadata').createIndex('byUploaded', 'uploaded', { unique: false })
break break
case 11:
db.createObjectStore('files_nodelink', { keyPath: 'UUID' })
break
case 12:
trx.objectStore('files_nodelink').createIndex('byNodeCount', 'NodeCount', { unique: false })
break
} }
} }
} }
@ -90,6 +99,7 @@ export class NodeStore {
this.nodesHistory = new NodeHistoryStore(this.db, 'nodes_history') this.nodesHistory = new NodeHistoryStore(this.db, 'nodes_history')
this.files = new SimpleNodeStore(this.db, 'files') this.files = new SimpleNodeStore(this.db, 'files')
this.filesMetadata = new SimpleNodeStore(this.db, 'files_metadata') this.filesMetadata = new SimpleNodeStore(this.db, 'files_metadata')
this.filesNodelink = new FileNodesLinkStore(this.db, 'files_nodelink')
resolve() resolve()
} }
@ -231,7 +241,6 @@ export class NodeStore {
nodeStore = t.objectStore('nodes') nodeStore = t.objectStore('nodes')
t.oncomplete = (_event) => { t.oncomplete = (_event) => {
console.log('complete')
resolve() resolve()
} }
@ -572,6 +581,51 @@ class NodeHistoryStore extends SimpleNodeStore {
}// }}} }// }}}
} }
class FileNodesLinkStore extends SimpleNodeStore {
async removeFileUUIDs(nodeUUID, fileUUIDs) {// {{{
for (const fileUUID of fileUUIDs) {
let nodelink = await this.get(fileUUID)
// Nothing to remove from.
if (nodelink === undefined)
continue
const idx = nodelink.Nodes.indexOf(nodeUUID)
if (idx > -1)
nodelink.Nodes.splice(idx, 1)
// Maintains an index to quickly find orphaned files.
nodelink.NodeCount = nodelink.Nodes.length
console.log('removing', nodelink)
await this.add({data: nodelink})
}
}// }}}
async addFileUUIDs(nodeUUID, fileUUIDs) {// {{{
for (const fileUUID of fileUUIDs) {
let nodelink = await this.get(fileUUID)
// Needs to be initialized if not existing already.
if (nodelink === undefined)
nodelink = {
UUID: fileUUID,
Nodes: [nodeUUID],
}
// Can get called with the same UUID as already there.
// No duplicates, only append if missing.
if (!nodelink.Nodes.includes(nodeUUID))
nodelink.Nodes.push(nodeUUID)
// Maintains an index to quickly find orphaned files.
nodelink.NodeCount = nodelink.Nodes.length
console.log('pushing', nodelink)
await this.add({data: nodelink})
}
}// }}}
}
export function uuidv7() {// {{{ export function uuidv7() {// {{{
// random bytes // random bytes
const value = new Uint8Array(16) const value = new Uint8Array(16)

View file

@ -453,6 +453,8 @@ export class Node {
}//}}} }//}}}
reset() {// {{{ reset() {// {{{
// this._content is the runtime content in the editor.
// this.data.Content is the original data.
this._content = this.data.Content this._content = this.data.Content
this._modified = false this._modified = false
}// }}} }// }}}
@ -522,10 +524,7 @@ export class Node {
return this.data.Special return this.data.Special
}// }}} }// }}}
content() {//{{{ content() {//{{{
/* TODO - implement crypto // TODO - implement crypto
if (this.CryptoKeyID != 0 && !this._decrypted)
this.#decrypt()
*/
return this._content return this._content
}//}}} }//}}}
setContent(new_content) {//{{{ setContent(new_content) {//{{{
@ -542,48 +541,67 @@ export class Node {
_mbus.dispatch('NODE_MODIFIED', { node: this }) _mbus.dispatch('NODE_MODIFIED', { node: this })
}// }}} }// }}}
async save() {//{{{ async save() {//{{{
// Just safeguarding not using the root node, try {
// which sort of exist but isn't supposed to communicate to server. const dblink = /db:\/\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/gi
if (this.UUID == ROOT_NODE)
return
this.data.Content = this._content // Just safeguarding not using the root node,
this.data.Updated = new Date().toISOString() // which sort of exist but isn't supposed to communicate to server.
this.data.HistoryUUID = uuidv7() // every time the node is saved a new history UUID identifies the changed node. if (this.UUID == ROOT_NODE)
this._modified = false return
_mbus.dispatch('NODE_UNMODIFIED')
// When stored into database and ancestry was changed, // File links from the original, unmodified content are found to compare to links from the new content.
// the ancestry path could be interesting. // All files (images) needs to be indexed to nodes in order to track orphaned files.
/* const before = new Set([...this.data.Content.matchAll(dblink)].map(m => m[1]))
const ancestors = await nodeStore.getNodeAncestry(this) const after = new Set([...this._content.matchAll(dblink)].map(m => m[1]))
this.data.Ancestors = ancestors.map(a => a.get('Name')).reverse() const removed = before.difference(after)
*/ const added = after.difference(before)
/* 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. */
// Current node is added to history. It will be duplicated with the "nodes" store await nodeStore.filesNodelink.removeFileUUIDs(this.UUID, removed)
// for simplicity, to hopefully avoid bugs. await nodeStore.filesNodelink.addFileUUIDs(this.UUID, added)
const history = nodeStore.nodesHistory.add(this)
// Updated node is added to the send queue to be stored on server. // Authoritative content and metadata is set to prepare it for IndexedDB update.
this.data.Content = this._content
this.data.Updated = new Date().toISOString()
this.data.HistoryUUID = uuidv7() // every time the node is saved a new history UUID identifies the changed node.
const sendQueue = nodeStore.sendQueue.add(this) _mbus.dispatch('NODE_UNMODIFIED')
this._modified = false
// Updated node is saved to the primary node store. // When stored into database and ancestry was changed,
const nodeStoreAdding = nodeStore.add([this]) // the ancestry path could be interesting.
/*
const ancestors = await nodeStore.getNodeAncestry(this)
this.data.Ancestors = ancestors.map(a => a.get('Name')).reverse()
*/
console.log('waiting') /* The node history is a local store for node history.
await Promise.all([history, sendQueue, nodeStoreAdding]) * This could be provisioned from the server or cleared if
console.log('waiting done') * 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. */
// Current node is added to history. It will be duplicated with the "nodes" store
// for simplicity, to hopefully avoid bugs.
const history = nodeStore.nodesHistory.add(this)
// Updated node is added to the send queue to be stored on server.
const sendQueue = nodeStore.sendQueue.add(this)
// Updated node is saved to the primary node store.
const nodeStoreAdding = nodeStore.add([this])
await Promise.all([history, sendQueue, nodeStoreAdding])
} catch (err) {
console.error(err)
alert(err.message)
}
return return
}//}}} }//}}}

View file

@ -164,9 +164,9 @@ export class Sync {
} }
} }
}//}}} }//}}}
async filesToServer() { async filesToServer() {// {{{
try { try {
const BLOCKSIZE = 128*1024 const BLOCKSIZE = 128 * 1024
let sentBytes = 0 let sentBytes = 0
const { file, metadata } = await nodeStore.nextFileToSync() const { file, metadata } = await nodeStore.nextFileToSync()
@ -193,7 +193,7 @@ export class Sync {
console.error(e) console.error(e)
alert(e.message) alert(e.message)
} }
} }// }}}
} }
export class N2SyncProgress extends CustomHTMLElement { export class N2SyncProgress extends CustomHTMLElement {