From 88f7338d7c84ac2a774b483ccff25faf6ceeb879 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 30 Aug 2023 15:54:44 +0200 Subject: wallet-core,wallet-cli: properly serialize manual DB export --- packages/taler-wallet-core/src/db.ts | 135 +++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 29 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index c550ab675..2dbf5dade 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -23,6 +23,7 @@ import { IDBFactory, IDBObjectStore, IDBTransaction, + structuredEncapsulate, } from "@gnu-taler/idb-bridge"; import { AgeCommitmentProof, @@ -566,10 +567,31 @@ export interface ExchangeDetailsPointer { updateClock: TalerPreciseTimestamp; } +export enum ExchangeEntryDbRecordStatus { + Preset = 1, + Ephemeral = 2, + Used = 3, +} + +export enum ExchangeEntryDbUpdateStatus { + Initial = 1, + InitialUpdate = 2, + Suspended = 3, + Failed = 4, + OutdatedUpdate = 5, + Ready = 6, + ReadyUpdate = 7, +} + +/** + * Timestamp stored as a IEEE 754 double, in milliseconds. + */ +export type DbIndexableTimestampMs = number; + /** * Exchange record as stored in the wallet's database. */ -export interface ExchangeRecord { +export interface ExchangeEntryRecord { /** * Base url of the exchange. */ @@ -594,13 +616,12 @@ export interface ExchangeRecord { */ detailsPointer: ExchangeDetailsPointer | undefined; - /** - * Is this a permanent or temporary exchange record? - */ - permanent: boolean; + entryStatus: ExchangeEntryDbRecordStatus; + + updateStatus: ExchangeEntryDbUpdateStatus; /** - * Last time when the exchange was updated (both /keys and /wire). + * Last time when the exchange /keys info was updated. */ lastUpdate: TalerPreciseTimestamp | undefined; @@ -608,20 +629,21 @@ export interface ExchangeRecord { * Next scheduled update for the exchange. * * (This field must always be present, so we can index on the timestamp.) + * + * FIXME: To index on the timestamp, this needs to be a number of + * binary timestamp! */ - nextUpdate: TalerPreciseTimestamp; + nextUpdateStampMs: DbIndexableTimestampMs; lastKeysEtag: string | undefined; - lastWireEtag: string | undefined; - /** * Next time that we should check if coins need to be refreshed. * * Updated whenever the exchange's denominations are updated or when * the refresh check has been done. */ - nextRefreshCheck: TalerPreciseTimestamp; + nextRefreshCheckStampMs: DbIndexableTimestampMs; /** * Public key of the reserve that we're currently using for @@ -2424,7 +2446,7 @@ export const WalletStoresV1 = { ), exchanges: describeStore( "exchanges", - describeContents({ + describeContents({ keyPath: "baseUrl", }), {}, @@ -2713,11 +2735,10 @@ export type WalletDbReadOnlyTransaction< Stores extends StoreNames & string, > = DbReadOnlyTransaction; -export type WalletReadWriteTransaction< +export type WalletDbReadWriteTransaction< Stores extends StoreNames & string, > = DbReadWriteTransaction; - /** * An applied migration. */ @@ -2748,32 +2769,88 @@ export const walletMetadataStore = { ), }; -export function exportDb(db: IDBDatabase): Promise { - const dump = { - name: db.name, - stores: {} as { [s: string]: any }, +export interface DbDumpRecord { + /** + * Key, serialized with structuredEncapsulated. + */ + key: any; + /** + * Value, serialized with structuredEncapsulated. + */ + value: any; +} + +export interface DbIndexDump { + keyPath: string | string[]; + multiEntry: boolean; + unique: boolean; +} + +export interface DbStoreDump { + keyPath?: string | string[]; + autoIncrement: boolean; + indexes: { [indexName: string]: DbIndexDump }; + records: DbDumpRecord[]; +} + +export interface DbDumpDatabase { + version: number; + stores: { [storeName: string]: DbStoreDump }; +} + +export interface DbDump { + databases: { + [name: string]: DbDumpDatabase; + }; +} + +export function exportDb(db: IDBDatabase): Promise { + const dbDump: DbDump = { + databases: {}, + }; + + const walletDb: DbDumpDatabase = { version: db.version, + stores: {}, }; + dbDump.databases[db.name] = walletDb; return new Promise((resolve, reject) => { const tx = db.transaction(Array.from(db.objectStoreNames)); tx.addEventListener("complete", () => { - resolve(dump); + resolve(dbDump); }); // tslint:disable-next-line:prefer-for-of for (let i = 0; i < db.objectStoreNames.length; i++) { const name = db.objectStoreNames[i]; - const storeDump = {} as { [s: string]: any }; - dump.stores[name] = storeDump; - tx.objectStore(name) - .openCursor() - .addEventListener("success", (e: Event) => { - const cursor = (e.target as any).result; - if (cursor) { - storeDump[cursor.key] = cursor.value; - cursor.continue(); - } - }); + const store = tx.objectStore(name); + const storeDump: DbStoreDump = { + autoIncrement: store.autoIncrement, + keyPath: store.keyPath, + indexes: {}, + records: [], + }; + const indexNames = store.indexNames; + for (let j = 0; j < indexNames.length; j++) { + const idxName = indexNames[j]; + const index = store.index(idxName); + storeDump.indexes[idxName] = { + keyPath: index.keyPath, + multiEntry: index.multiEntry, + unique: index.unique, + }; + } + walletDb.stores[name] = storeDump; + store.openCursor().addEventListener("success", (e: Event) => { + const cursor = (e.target as any).result; + if (cursor) { + storeDump.records.push({ + key: structuredEncapsulate(cursor.key), + value: structuredEncapsulate(cursor.value), + }); + cursor.continue(); + } + }); } }); } -- cgit v1.2.3