Sync progress bar somewhat working

This commit is contained in:
Magnus Åhall 2025-01-21 18:20:50 +01:00
parent f33e5d54af
commit 3453dffb53
8 changed files with 250 additions and 42 deletions

View file

@ -2,6 +2,7 @@ import { h, Component, createRef } from 'preact'
import htm from 'htm'
import { signal } from 'preact/signals'
import { ROOT_NODE } from 'node_store'
import { SyncProgress } from 'sync'
const html = htm.bind(h)
export class NodeUI extends Component {
@ -15,6 +16,7 @@ export class NodeUI extends Component {
this.keys = signal([])
this.page = signal('node')
this.crumbs = []
this.syncProgress = createRef()
window.addEventListener('popstate', evt => {
if (evt.state?.hasOwnProperty('nodeUUID'))
_notes2.current.goToNode(evt.state.nodeUUID, true)
@ -52,6 +54,7 @@ export class NodeUI extends Component {
${crumbDivs}
</div>
</div>
<${SyncProgress} ref=${this.syncProgress} />
<div id="name">${node.get('Name')}</div>
<${NodeContent} key=${node.UUID} node=${node} ref=${this.nodeContent} />
<div id="blank"></div>

View file

@ -246,9 +246,6 @@ export class NodeStore {
console.log('transaction error', event.target.error)
reject(event.target.error)
}
t.oncomplete = () => {
console.log('OK')
}
// records is an object, not an array.
const promises = []
@ -395,16 +392,26 @@ class SimpleNodeStore {
const promises = []
for (const key of keys) {
const p = new Promise((resolve, reject)=>{
const p = new Promise((resolve, reject) => {
// TODO - implement a way to add an error to a page-global error log.
const request = store.delete(key)
request.onsuccess = (event)=>resolve(event)
request.onerror = (event)=>reject(event)
request.onsuccess = (event) => resolve(event)
request.onerror = (event) => reject(event)
})
promises.push(p)
}
return Promise.all(promises)
}//}}}
async count() {//{{{
const store = this.db
.transaction(['nodes', this.storeName], 'readonly')
.objectStore(this.storeName)
return new Promise((resolve, reject) => {
const request = store.count()
request.onsuccess = (event) => resolve(event.target.result)
request.onerror = (event) => reject(event.target.error)
})
}//}}}
}
// vim: foldmethod=marker

View file

@ -13,9 +13,8 @@ export class Notes2 extends Component {
startNode: null,
}
Sync.nodesFromServer().then(durationNodes =>
console.log(`Total time: ${Math.round(100 * durationNodes) / 100}s`)
)
window._sync = new Sync()
window._sync.run()
this.getStartNode()
}//}}}

View file

@ -1,21 +1,68 @@
import { API } from 'api'
import { Node } from 'node'
import { h, Component, createRef } from 'preact'
import htm from 'htm'
const html = htm.bind(h)
const SYNC_COUNT = 1
const SYNC_HANDLED = 2
const SYNC_DONE = 3
export class Sync {
constructor() {
this.foo = ''
}
static async nodesFromServer() {//{{{
let duration = 0
const syncStart = Date.now()
constructor() {//{{{
this.listeners = []
this.messagesReceived = []
}//}}}
addListener(fn, runMessageQueue) {//{{{
// Some handlers won't be added until a time after sync messages have been added to the queue.
// This is an opportunity for the handler to receive the old messages in order.
if (runMessageQueue)
for (const msg of this.messagesReceived)
fn(msg)
this.listeners.push(fn)
}//}}}
pushMessage(msg) {//{{{
this.messagesReceived.push(msg)
for (const fn of this.listeners)
fn(msg)
}//}}}
async run() {//{{{
try {
let duration = 0 // in ms
// The latest sync node value is used to retrieve the changes
// from the backend.
const state = await nodeStore.getAppState('latest_sync_node')
const oldMax = (state?.value ? state.value : 0)
let currMax = oldMax
let nodeCount = await this.getNodeCount(oldMax)
nodeCount += await nodeStore.sendQueue.count()
const msg = { op: SYNC_COUNT, count: nodeCount }
this.pushMessage(msg)
await this.nodesFromServer(oldMax)
.then(durationNodes => {
duration = durationNodes // in ms
console.log(`Total time: ${Math.round(1000 * durationNodes) / 1000}s`)
})
await this.nodesToServer()
} finally {
this.pushMessage({ op: SYNC_DONE, })
}
}//}}}
async getNodeCount(oldMax) {//{{{
// Retrieve the amount of values the server will send us.
const res = await API.query('POST', `/sync/from_server/count/${oldMax}`)
return res?.Count
}//}}}
async nodesFromServer(oldMax) {//{{{
const syncStart = Date.now()
let syncEnd
try {
let currMax = oldMax
let offset = 0
let res = { Continue: false }
let batch = 0
@ -39,7 +86,7 @@ export class Sync {
let backendNode = null
for (const i in res.Nodes) {
backendNode = new Node(res.Nodes[i], -1)
await Sync.handleNode(backendNode)
await window._sync.handleNode(backendNode)
}
} while (res.Continue)
@ -48,14 +95,14 @@ export class Sync {
} catch (e) {
console.log('sync node tree', e)
} finally {
const syncEnd = Date.now()
duration = (syncEnd - syncStart) / 1000
syncEnd = Date.now()
const duration = (syncEnd - syncStart) / 1000
const count = await nodeStore.nodeCount()
console.log(`Node sync took ${duration}s`, count)
}
return duration
return (syncEnd - syncStart)
}//}}}
static async handleNode(backendNode) {//{{{
async handleNode(backendNode) {//{{{
try {
/* Retrieving the local copy of this node from IndexedDB.
* The backend node can be discarded if it is older than
@ -83,14 +130,16 @@ export class Sync {
})
} catch (e) {
console.error(e)
} finally {
this.pushMessage({ op: SYNC_HANDLED, count: 1 })
}
}//}}}
static async nodesToServer() {//{{{
while(true) {
async nodesToServer() {//{{{
const BATCH_SIZE = 32
while (true) {
try {
// Send nodes in batches until everything is sent, or an error has occured.
const nodesToSend = await nodeStore.sendQueue.retrieve(2)
const nodesToSend = await nodeStore.sendQueue.retrieve(BATCH_SIZE)
if (nodesToSend.length === 0)
break
console.debug(`Sending ${nodesToSend.length} node(s) to server`)
@ -109,6 +158,7 @@ export class Sync {
// Nodes are archived on server and can now be deleted from the send queue.
const keys = nodesToSend.map(node => node.ClientSequence)
console.log(await nodeStore.sendQueue.delete(keys))
this.pushMessage({ op: SYNC_HANDLED, count: nodesToSend.length })
} catch (e) {
console.trace(e)
@ -118,3 +168,48 @@ export class Sync {
}
}//}}}
}
export class SyncProgress extends Component {
constructor() {//{{{
super()
this.state = {
nodesToSync: 0,
nodesSynced: 0,
syncedDone: false,
}
}//}}}
componentDidMount() {//{{{
window._sync.addListener(msg => this.progressHandler(msg), true)
}//}}}
getSnapshotBeforeUpdate(_, prevState) {//{{{
if (!prevState.syncedDone && this.state.syncedDone)
setTimeout(() => document.getElementById('sync-progress')?.classList.add('hidden'), 750)
}//}}}
progressHandler(msg) {//{{{
switch (msg.op) {
case SYNC_COUNT:
this.setState({ nodesToSync: msg.count })
break
case SYNC_HANDLED:
this.setState({ nodesSynced: this.state.nodesSynced + msg.count })
break
case SYNC_DONE:
this.setState({ syncedDone: true })
break
}
}//}}}
render(_, { nodesToSync, nodesSynced }) {//{{{
if (nodesToSync === 0)
return html`<div id="sync-progress"></div>`
return html`
<div id="sync-progress">
<progress min=0 max=${nodesToSync} value=${nodesSynced}></progress>
<div class="count">${nodesSynced} / ${nodesToSync}</div>
</div>
`
}//}}}
}