Indexation of files to nodes
This commit is contained in:
parent
70b5285e16
commit
f3ca7b9a44
3 changed files with 115 additions and 43 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}//}}}
|
}//}}}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue