diff --git a/package-lock.json b/package-lock.json index fa0da15..5319bc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "osmtogeojson": "^3.0.0-beta.5", "patch-package": "^6.5.1", "pouchdb": "^8.0.1", + "pouchdb-adapter-indexeddb": "^8.0.1", "pouchdb-browser": "^8.0.1", "proj4": "^2.8.1", "solid-js": "^1.6.10", @@ -7095,6 +7096,40 @@ "vuvuzela": "1.0.3" } }, + "node_modules/pouchdb-adapter-indexeddb": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/pouchdb-adapter-indexeddb/-/pouchdb-adapter-indexeddb-8.0.1.tgz", + "integrity": "sha512-pX4FdL2isG4uhYXXnBUQJnkwcu+mBUjN/TW+B6aQhU+b/ZpLzH1XU/aui3/X604vhyOwy+DE4MsZEIsohUkR4g==", + "dependencies": { + "pouchdb-adapter-utils": "8.0.1", + "pouchdb-binary-utils": "8.0.1", + "pouchdb-errors": "8.0.1", + "pouchdb-md5": "8.0.1", + "pouchdb-merge": "8.0.1", + "pouchdb-utils": "8.0.1" + } + }, + "node_modules/pouchdb-adapter-utils": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/pouchdb-adapter-utils/-/pouchdb-adapter-utils-8.0.1.tgz", + "integrity": "sha512-2nTeYaImu958BU4e46SSdv0IdkXYS/PSy5CXyfb7jK9g0aBAp/JRi7qh9nsTjk4FewpT6OpaE/7evxMQa7UuMg==", + "dependencies": { + "pouchdb-binary-utils": "8.0.1", + "pouchdb-collections": "8.0.1", + "pouchdb-errors": "8.0.1", + "pouchdb-md5": "8.0.1", + "pouchdb-merge": "8.0.1", + "pouchdb-utils": "8.0.1" + } + }, + "node_modules/pouchdb-binary-utils": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/pouchdb-binary-utils/-/pouchdb-binary-utils-8.0.1.tgz", + "integrity": "sha512-WsuR/S0aoUlcA0Alt99czkXsfuXWcrYXAcvGiTW02zawVXOafCnb/qHjA09TUaV0oy5HeHmYaNnDckoOUqspeA==", + "dependencies": { + "buffer-from": "1.1.2" + } + }, "node_modules/pouchdb-browser": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/pouchdb-browser/-/pouchdb-browser-8.0.1.tgz", @@ -7106,6 +7141,46 @@ "vuvuzela": "1.0.3" } }, + "node_modules/pouchdb-collections": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/pouchdb-collections/-/pouchdb-collections-8.0.1.tgz", + "integrity": "sha512-TlkQ2GGHJApJgL0b7bJMQcwX6eMfVenLeoK9mqHfC2fJssui+HWJJ5LYKHOWan11SeB90BQVFbO6rHN6CJQeDg==" + }, + "node_modules/pouchdb-errors": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/pouchdb-errors/-/pouchdb-errors-8.0.1.tgz", + "integrity": "sha512-H+ZsQxcG/JV3Tn29gnM6c9+lRPCN91ZYOkoIICsLjVRYgOTzN1AvNUD/G5JCB+81aI/u3fxZec0LEaZh6g6NHA==" + }, + "node_modules/pouchdb-md5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/pouchdb-md5/-/pouchdb-md5-8.0.1.tgz", + "integrity": "sha512-shVcs/K/iilrcAhDEERpLIrGm/cnDVsXiocOzs7kycJEuBqYnLD9nj58VwWDcum26wfa8T9cznvEGE1jlYVNPQ==", + "dependencies": { + "pouchdb-binary-utils": "8.0.1", + "spark-md5": "3.0.2" + } + }, + "node_modules/pouchdb-merge": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/pouchdb-merge/-/pouchdb-merge-8.0.1.tgz", + "integrity": "sha512-79dw6+K7js2+/kt9u4hKOkGCnz+ov0+yft2k21n6M+ylFEQyMKuWHEZRoFWr72o1vxwjhIXhUM1PB2PIdxIh0Q==", + "dependencies": { + "pouchdb-utils": "8.0.1" + } + }, + "node_modules/pouchdb-utils": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/pouchdb-utils/-/pouchdb-utils-8.0.1.tgz", + "integrity": "sha512-pWgxdk9EHVWJmjQoEvTe+ZlPXyjcuQ/vgLITN+RjGwcYhoQYUE1M0PksQd2dUP3V8lGS4+wrg9lEM/qSJPYcpw==", + "dependencies": { + "clone-buffer": "1.0.0", + "immediate": "3.3.0", + "pouchdb-collections": "8.0.1", + "pouchdb-errors": "8.0.1", + "pouchdb-md5": "8.0.1", + "uuid": "8.3.2" + } + }, "node_modules/pouchdb/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", diff --git a/package.json b/package.json index f6ce0ef..f25017e 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "osmtogeojson": "^3.0.0-beta.5", "patch-package": "^6.5.1", "pouchdb": "^8.0.1", + "pouchdb-adapter-indexeddb": "^8.0.1", "pouchdb-browser": "^8.0.1", "proj4": "^2.8.1", "solid-js": "^1.6.10", diff --git a/src/db-admin/compact.ts b/src/db-admin/compact.ts new file mode 100644 index 0000000..c6e21cd --- /dev/null +++ b/src/db-admin/compact.ts @@ -0,0 +1,110 @@ +import { Signal } from 'solid-js'; +import PouchDB from 'pouchdb'; + +import { getSettings } from '../db/settings'; +import { delay } from '../lib/delay'; + +export const compactDb = async (p: { signal: Signal; params: any }) => { + const { signal, params } = p; + const [, setStatus] = signal; + console.log({ caller: 'compactDb', p }); + + const db = new PouchDB('dyomedea', { auto_compaction: true }); + const dbinfo = await db.info(); + const tasks = PouchDB.activeTasks.list(); + + setStatus({ status: 'starting', db: 'dyomedea', dbinfo, tasks }); + + return; + const timerId = setInterval(async () => { + const dbinfo = await db.info(); + const tasks = PouchDB.activeTasks.list(); + if (tasks.length > 0) { + setStatus({ status: 'ongoing', db: 'dyomedea', dbinfo, tasks }); + } else { + setStatus({ status: 'terminated', db: 'dyomedea', dbinfo, tasks }); + clearInterval(timerId); + } + }, 1000); + + const openIDb = (name: string) => + new Promise((resolve, reject) => { + const iDb = indexedDB.open(name); + iDb.onerror = (event: any) => { + console.error({ + caller: 'compactDb', + message: 'open db error', + target: event.target, + }); + reject(event.target.errorCode); + }; + + iDb.onsuccess = (event: any) => { + console.log({ + caller: 'compactDb', + message: 'open db', + target: event.target, + }); + resolve(event.target.result); + }; + }); + + const getAll = (store: any) => + new Promise((resolve, reject) => { + const request = store.getAll(); + request.onerror = (event: any) => { + console.error({ + caller: 'compactDb', + message: 'getAll error', + target: event.target, + }); + reject(event.target.errorCode); + }; + + request.onsuccess = (event: any) => { + console.log({ + caller: 'compactDb', + message: 'getAll', + target: event.target, + }); + resolve(event.target.result as any[]); + }; + }); + + const iDb = (await openIDb('_pouch_dyomedea')) as IDBDatabase; + + const bySequence = iDb.transaction('by-sequence', 'readonly'); + const store = bySequence.objectStore('by-sequence'); + console.log({ + caller: 'compactDb', + message: 'transaction opened', + bySequence, + store, + }); + + const sequences = (await getAll(store)) as any[]; + setStatus({ + status: 'revisions', + db: 'dyomedea', + sequences, + }); + + sequences.forEach(async (sequence) => { + const { _deleted, _doc_id_rev } = sequence; + if (_deleted) { + const [id, rev] = _doc_id_rev.split('::'); + // const purge = await db.purge(id, rev); + console.log({ + caller: 'compactDb', + message: 'purging', + id, + rev, + sequence, + purge, + }); + await delay(100); + } + }); + + // db.compact(); +}; diff --git a/src/db/account.ts b/src/db/account.ts index 92878e8..c402499 100644 --- a/src/db/account.ts +++ b/src/db/account.ts @@ -4,7 +4,7 @@ import getUri from '../lib/ids'; export const initialAccount = { id: 'initial', name: '???', - localDb: 'dyomedea', + localDb: '_dyomedea_', }; export const getAccounts = async () => { diff --git a/src/db/index.ts b/src/db/index.ts index b656d86..523d5a3 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import PouchDB from 'pouchdb'; +import indexeddb from 'pouchdb-adapter-indexeddb'; import uri from '../lib/ids'; import { toHex } from '../lib/to-hex'; import { @@ -11,6 +12,8 @@ import { import changeHandler from './change-handler'; import { getSettings, putSettings } from './settings'; +PouchDB.plugin(indexeddb); + const dbDefinitionId = uri('dbdef', {}); const currentDbDefinition = { @@ -29,7 +32,8 @@ export const initDb = async (params: any) => { console.log({ caller: 'initDb' }); if (globalThis.localDb === undefined) { - globalThis.localDb = new PouchDB('$local_dyomedea$', { + globalThis.localDb = new PouchDB('_local_dyomedea_', { + adapter: 'indexeddb', auto_compaction: true, revs_limit: 10, }); @@ -55,10 +59,20 @@ export const initDb = async (params: any) => { if (globalThis.db === undefined) { globalThis.db = new PouchDB(currentAccount.localDb, { - auto_compaction: false, + adapter: 'indexeddb', + auto_compaction: true, }); } const db = globalThis.db; + const dbinfo = await db.info(); + // db.compact(); + console.log({ + caller: 'initDb', + dbinfo, + activeTasks: PouchDB.activeTasks.list(), + storage: await navigator.storage.estimate(), + }); + // var previousDbDefinition = { _id: dbDefinitionId, type: dbDefinitionId, @@ -150,5 +164,11 @@ export const initDb = async (params: any) => { // changes.cancel(); + console.log({ + caller: 'initDb (end)', + dbinfo, + activeTasks: PouchDB.activeTasks.list(), + storage: await navigator.storage.estimate(), + }); globalThis.dbReady = true; }; diff --git a/src/db/rte.ts b/src/db/rte.ts index f148caf..55a8be1 100644 --- a/src/db/rte.ts +++ b/src/db/rte.ts @@ -1,7 +1,6 @@ import { cloneDeep, debounce, memoize, property, wrap } from 'lodash'; -import { delay } from '../lib/delay'; import getUri from '../lib/ids'; -import { appendToArray, putNewGpx } from './gpx'; +import { putNewGpx } from './gpx'; import { getFamily, put } from './lib'; export const emptyRte: Rte = { diff --git a/src/index.tsx b/src/index.tsx index a4154a4..8dd067a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -10,6 +10,9 @@ import dict from './i18n'; import App from './App'; import { Language } from '@suid/icons-material'; +import { createWorkerSignal } from './solid-workers/solid-worker-main'; +import { createEffect } from 'solid-js'; +import dispatch from './workers/dispatcher-main'; // See https://stackoverflow.com/questions/71538643/property-wakelock-does-not-exist-on-type-navigator const requestWakeLock = async () => { @@ -55,6 +58,20 @@ const getLanguage = () => { const i18nDict = createI18nContext(dict, getLanguage()); +const settings: any = await dispatch({ + action: 'getSettings', +}); +const accounts: any = await dispatch({ + action: 'getAccounts', +}); +const [compactDbStatus] = createWorkerSignal({ + provider: 'compactDb', + params: { settings, accounts }, +}); +createEffect(() => { + console.log({ caller: 'createEffect', compactDbStatus: compactDbStatus() }); +}); + render( () => ( diff --git a/src/lib/delay.ts b/src/lib/delay.ts new file mode 100644 index 0000000..5921663 --- /dev/null +++ b/src/lib/delay.ts @@ -0,0 +1,2 @@ +export const delay = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/missing-typedefs/pouchdb-adapter-indexeddb.d.ts b/src/missing-typedefs/pouchdb-adapter-indexeddb.d.ts new file mode 100644 index 0000000..586d41a --- /dev/null +++ b/src/missing-typedefs/pouchdb-adapter-indexeddb.d.ts @@ -0,0 +1 @@ +declare module 'pouchdb-adapter-indexeddb'; diff --git a/src/solid-workers/solid-worker-main.ts b/src/solid-workers/solid-worker-main.ts new file mode 100644 index 0000000..148fcb0 --- /dev/null +++ b/src/solid-workers/solid-worker-main.ts @@ -0,0 +1,73 @@ +import { createSignal, Signal } from 'solid-js'; + +declare global { + var exportQueue: Map; +} + +let worker: Worker; + +const init = () => { + console.log({ caller: 'solid-worker-main / init' }); + globalThis.exportQueue = new Map< + string, + { count: number; signal: Signal } + >(); + + worker = new Worker(new URL('./solid-worker', import.meta.url), { + type: 'module', + }); + + worker.onmessage = (event: any) => { + const { _id, _updatedValue } = event.data; + console.log({ + caller: 'solid-worker-main / message received', + _id, + _updatedValue, + }); + if (!globalThis.exportQueue.get(_id)) { + console.error({ + caller: 'solid-worker-main / message received', + message: 'not found', + _id, + _updatedValue, + }); + } + const signal = globalThis.exportQueue.get(_id).signal; + signal[1](_updatedValue); + }; +}; + +export const createWorkerSignal = (parameters: { + provider: string; + params?: any; + initialValue?: any; +}) => { + if (!worker) { + init(); + } + const { provider, params, initialValue } = parameters; + const id = JSON.stringify({ provider, params }); + if (globalThis.exportQueue.has(id)) { + globalThis.exportQueue.get(id).count++; + return globalThis.exportQueue.get(id).signal; + } + const signal = createSignal(initialValue); + globalThis.exportQueue.set(id, { signal, count: 1 }); + worker.postMessage({ ...parameters, _id: id }); + return signal; +}; + +export const releaseWorkerSignal = (parameters: { + provider: string; + params: any; +}) => { + const { provider, params } = parameters; + const id = JSON.stringify({ provider, params }); + if (globalThis.exportQueue.has(id)) { + globalThis.exportQueue.get(id).count--; + if (globalThis.exportQueue.get(id).count === 0) { + globalThis.exportQueue.delete(id); + worker.postMessage({ ...parameters, _id: id, _release: true }); + } + } +}; diff --git a/src/solid-workers/solid-worker.ts b/src/solid-workers/solid-worker.ts new file mode 100644 index 0000000..de0ff3d --- /dev/null +++ b/src/solid-workers/solid-worker.ts @@ -0,0 +1,59 @@ +import { createEffect, createRoot, createSignal, Signal } from 'solid-js'; +import { compactDb } from '../db-admin/compact'; + +declare global { + var importQueue: Map; +} + +globalThis.importQueue = new Map>(); + +const providers = { + compactDb, +}; + +onmessage = function (e) { + createRoot((dispose) => { + const { provider, params, initialValue, _release, _id } = e.data; + console.log({ + caller: 'solid-worker / onmessage', + e, + provider, + params, + initialValue, + _release, + _id, + }); + + if (_release) { + dispose(); + return; + } + + if (!Object.hasOwn(providers, provider)) { + console.error({ + caller: 'solid-worker', + message: 'Unknown provider', + provider, + }); + } + + const signal = createSignal(initialValue); + // globalThis.importQueue.set(_id, signal); // Would be needed for bidirectional signals... + + createEffect(() => { + console.log({ + caller: 'solid_worker / effect', + provider, + params, + initialValue, + _release, + currentValue: signal[0](), + _id, + }); + postMessage({ _id, _updatedValue: signal[0]() }); + }); + + const providerFunction = providers[provider as keyof typeof providers]; + providerFunction({ params, signal }); + }); +};