From de65019ed6db789a12ee4b654b1de5890daa2186 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 20 Oct 2016 01:37:00 +0200 Subject: make queries then-able --- lib/decl/lib.es6.d.ts | 76 +++++++++++++++++++++++++-------------------------- lib/wallet/query.ts | 50 +++++++++++++++++++++++++-------- lib/wallet/types.ts | 9 ++++-- lib/wallet/wallet.ts | 63 ++++++++++++++++++++++++++++++++---------- popup/popup.tsx | 2 +- tsconfig.json | 1 - 6 files changed, 132 insertions(+), 69 deletions(-) diff --git a/lib/decl/lib.es6.d.ts b/lib/decl/lib.es6.d.ts index fc08339de..084ed4e8a 100644 --- a/lib/decl/lib.es6.d.ts +++ b/lib/decl/lib.es6.d.ts @@ -20288,44 +20288,44 @@ declare function addEventListener(type: "mouseenter", listener: (this: Window, e declare function addEventListener(type: "mouseleave", listener: (this: Window, ev: MouseEvent) => any, useCapture?: boolean): void; declare function addEventListener(type: "mousemove", listener: (this: Window, ev: MouseEvent) => any, useCapture?: boolean): void; declare function addEventListener(type: "mouseout", listener: (this: Window, ev: MouseEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "mouseover", listener: (this: Window, MouseEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "mouseup", listener: (this: Window, MouseEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "mousewheel", listener: (this: Window, WheelEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "offline", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "online", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "orientationchange", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pagehide", listener: (this: Window, PageTransitionEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pageshow", listener: (this: Window, PageTransitionEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pause", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "play", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "playing", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pointercancel", listener: (this: Window, PointerEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pointerdown", listener: (this: Window, PointerEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pointerenter", listener: (this: Window, PointerEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pointerleave", listener: (this: Window, PointerEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pointermove", listener: (this: Window, PointerEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pointerout", listener: (this: Window, PointerEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pointerover", listener: (this: Window, PointerEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "pointerup", listener: (this: Window, PointerEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "popstate", listener: (this: Window, PopStateEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "progress", listener: (this: Window, ProgressEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "ratechange", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "readystatechange", listener: (this: Window, ProgressEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "reset", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "resize", listener: (this: Window, UIEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "scroll", listener: (this: Window, UIEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "seeked", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "seeking", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "select", listener: (this: Window, UIEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "stalled", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "storage", listener: (this: Window, StorageEvent) => any, useCapture?: boolean): void; -declare function addEventListener(type: "submit", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "suspend", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "timeupdate", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "unload", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "volumechange", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "waiting", listener: (this: Window, Event) => any, useCapture?: boolean): void; -declare function addEventListener(type: "wheel", listener: (this: Window, WheelEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "mouseover", listener: (this: Window, ev: MouseEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "mouseup", listener: (this: Window, ev: MouseEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "mousewheel", listener: (this: Window, ev: WheelEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "offline", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "online", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "orientationchange", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pagehide", listener: (this: Window, ev: PageTransitionEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pageshow", listener: (this: Window, ev: PageTransitionEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pause", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "play", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "playing", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pointercancel", listener: (this: Window, ev: PointerEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pointerdown", listener: (this: Window, ev: PointerEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pointerenter", listener: (this: Window, ev: PointerEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pointerleave", listener: (this: Window, ev: PointerEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pointermove", listener: (this: Window, ev: PointerEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pointerout", listener: (this: Window, ev: PointerEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pointerover", listener: (this: Window, ev: PointerEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "pointerup", listener: (this: Window, ev: PointerEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "popstate", listener: (this: Window, ev: PopStateEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "progress", listener: (this: Window, ev: ProgressEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "ratechange", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "readystatechange", listener: (this: Window, ev: ProgressEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "reset", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "resize", listener: (this: Window, ev: UIEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "scroll", listener: (this: Window, ev: UIEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "seeked", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "seeking", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "select", listener: (this: Window, ev: UIEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "stalled", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "storage", listener: (this: Window, ev: StorageEvent) => any, useCapture?: boolean): void; +declare function addEventListener(type: "submit", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "suspend", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "timeupdate", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "unload", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "volumechange", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "waiting", listener: (this: Window, ev: Event) => any, useCapture?: boolean): void; +declare function addEventListener(type: "wheel", listener: (this: Window, ev: WheelEvent) => any, useCapture?: boolean): void; declare function addEventListener(type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): void; type AAGUID = string; type AlgorithmIdentifier = string | Algorithm; diff --git a/lib/wallet/query.ts b/lib/wallet/query.ts index acf9aa44d..c172bbeb7 100644 --- a/lib/wallet/query.ts +++ b/lib/wallet/query.ts @@ -69,6 +69,8 @@ export interface QueryStream { map(f: (x:T) => S): QueryStream; flatMap(f: (x: T) => S[]): QueryStream; toArray(): Promise; + + then(onfulfill: any, onreject: any): any; } export let AbortTransaction = Symbol("abort_transaction"); @@ -92,7 +94,7 @@ function openPromise() { } -abstract class QueryStreamBase implements QueryStream { +abstract class QueryStreamBase implements QueryStream, PromiseLike { abstract subscribe(f: (isDone: boolean, value: any, tx: IDBTransaction) => void): void; @@ -103,11 +105,15 @@ abstract class QueryStreamBase implements QueryStream { this.root = root; } - flatMap(f: (x: T) => T[]): QueryStream { - return new QueryStreamFlatMap(this, f); + then(onfulfilled: (value: void) => R | PromiseLike, onrejected: (reason: any) => R | PromiseLike): PromiseLike { + return this.root.then(onfulfilled, onrejected); + } + + flatMap(f: (x: T) => S[]): QueryStream { + return new QueryStreamFlatMap(this, f); } - map(f: (x: T) => S): QueryStream { + map(f: (x: T) => S): QueryStream { return new QueryStreamMap(this, f); } @@ -193,11 +199,11 @@ class QueryStreamFilter extends QueryStreamBase { } -class QueryStreamFlatMap extends QueryStreamBase { +class QueryStreamFlatMap extends QueryStreamBase { s: QueryStreamBase; - flatMapFn: (v: T) => T[]; + flatMapFn: (v: T) => S[]; - constructor(s: QueryStreamBase, flatMapFn: (v: T) => T[]) { + constructor(s: QueryStreamBase, flatMapFn: (v: T) => S[]) { super(s.root); this.s = s; this.flatMapFn = flatMapFn; @@ -218,11 +224,11 @@ class QueryStreamFlatMap extends QueryStreamBase { } -class QueryStreamMap extends QueryStreamBase { - s: QueryStreamBase; - mapFn: (v: T) => T[]; +class QueryStreamMap extends QueryStreamBase { + s: QueryStreamBase; + mapFn: (v: S) => T; - constructor(s: QueryStreamBase, mapFn: (v: T) => T[]) { + constructor(s: QueryStreamBase, mapFn: (v: S) => T) { super(s.root); this.s = s; this.mapFn = mapFn; @@ -364,7 +370,7 @@ class IterQueryStream extends QueryStreamBase { } -export class QueryRoot { +export class QueryRoot implements PromiseLike { private work: ((t: IDBTransaction) => void)[] = []; private db: IDBDatabase; private stores = new Set(); @@ -376,18 +382,26 @@ export class QueryRoot { */ private hasWrite: boolean; + private finishScheduled: boolean; + constructor(db: IDBDatabase) { this.db = db; } + then(onfulfilled: (value: void) => R | PromiseLike, onrejected: (reason: any) => R | PromiseLike): PromiseLike { + return this.finish().then(onfulfilled, onrejected); + } + iter(store: Store): QueryStream { this.stores.add(store.name); + this.scheduleFinish(); return new IterQueryStream(this, store.name, {}); } iterIndex(index: Index, only?: S): QueryStream { this.stores.add(index.storeName); + this.scheduleFinish(); return new IterQueryStream(this, index.storeName, { only, indexName: index.indexName @@ -403,6 +417,7 @@ export class QueryRoot { let doPut = (tx: IDBTransaction) => { tx.objectStore(store.name).put(val); }; + this.scheduleFinish(); this.addWork(doPut, store.name, true); return this; } @@ -427,6 +442,7 @@ export class QueryRoot { tx.objectStore(store.name).put(m); } }; + this.scheduleFinish(); this.addWork(doPut, store.name, true); return this; } @@ -443,6 +459,7 @@ export class QueryRoot { tx.objectStore(store.name).put(obj); } }; + this.scheduleFinish(); this.addWork(doPutAll, store.name, true); return this; } @@ -456,6 +473,7 @@ export class QueryRoot { const doAdd = (tx: IDBTransaction) => { tx.objectStore(store.name).add(val); }; + this.scheduleFinish(); this.addWork(doAdd, store.name, true); return this; } @@ -509,6 +527,13 @@ export class QueryRoot { .then(() => promise); } + private scheduleFinish() { + if (!this.finishScheduled) { + Promise.resolve().then(() => this.finish()); + this.finishScheduled = true; + } + } + /** * Finish the query, and start the query in the first place if necessary. */ @@ -543,6 +568,7 @@ export class QueryRoot { const doDelete = (tx: IDBTransaction) => { tx.objectStore(storeName).delete(key); }; + this.scheduleFinish(); this.addWork(doDelete, storeName, true); return this; } diff --git a/lib/wallet/types.ts b/lib/wallet/types.ts index 2df8094a7..3d21f8229 100644 --- a/lib/wallet/types.ts +++ b/lib/wallet/types.ts @@ -64,10 +64,15 @@ export interface ReserveRecord { * be higher than the requested_amount */ requested_amount: AmountJson, + + /** - * Amount we've already withdrawn from the reserve. + * What's the current amount that sits + * in precoins? */ - withdrawn_amount: AmountJson; + precoin_amount: AmountJson; + + confirmed: boolean, } diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts index 53508cf59..d4c95a6f0 100644 --- a/lib/wallet/wallet.ts +++ b/lib/wallet/wallet.ts @@ -33,7 +33,7 @@ import { import {HttpResponse, RequestException} from "./http"; import {QueryRoot, Store, Index, JoinResult, AbortTransaction} from "./query"; import {Checkable} from "./checkable"; -import {canonicalizeBaseUrl} from "./helpers"; +import {canonicalizeBaseUrl, amountToPretty} from "./helpers"; import {ReserveCreationInfo, Amounts} from "./types"; import {PreCoin} from "./types"; import {CryptoApi} from "./cryptoApi"; @@ -403,10 +403,13 @@ export class Wallet { } } - updateExchanges(): void { + async updateExchanges(): Promise { console.log("updating exchanges"); - let exchangesUrls = this.q().iter(Stores.exchanges).map((e) => e.baseUrl); + let exchangesUrls = await this.q() + .iter(Stores.exchanges) + .map((e) => e.baseUrl) + .toArray(); for (let url of exchangesUrls) { this.updateExchangeFromUrl(url) @@ -766,18 +769,18 @@ export class Wallet { const coin = await this.withdrawExecute(preCoin); const mutateReserve = (r: ReserveRecord) => { - let currentAmount = r.current_amount; - if (!currentAmount) { - throw Error("can't withdraw from reserve when current amount is" + - " unknown"); - } - let x = Amounts.sub(currentAmount, + + console.log(`before committing coin: current ${amountToPretty(r.current_amount!)}, precoin: ${amountToPretty( + r.precoin_amount)})}`); + + let x = Amounts.sub(r.precoin_amount, preCoin.coinValue, denom!.fee_withdraw); if (x.saturated) { + console.error("database inconsistent"); throw AbortTransaction; } - r.current_amount = x.amount; + r.precoin_amount = x.amount; return r; }; @@ -791,7 +794,6 @@ export class Wallet { }; await this.q() - .put(Stores.precoins, preCoin) .mutate(Stores.reserves, preCoin.reservePub, mutateReserve) .delete("precoins", coin.coinPub) .add(Stores.coins, coin) @@ -828,7 +830,7 @@ export class Wallet { current_amount: null, requested_amount: req.amount, confirmed: false, - withdrawn_amount: Amounts.getZero(req.amount.currency) + precoin_amount: Amounts.getZero(req.amount.currency), }; const historyEntry = { @@ -940,17 +942,47 @@ export class Wallet { /** * Withdraw coins from a reserve until it is empty. */ - private async depleteReserve(reserve: any, + private async depleteReserve(reserve: ReserveRecord, exchange: IExchangeInfo): Promise { + if (!reserve.current_amount) { + throw Error("can't withdraw when amount is unknown"); + } let denomsAvailable: Denomination[] = copy(exchange.active_denoms); - let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount, + let denomsForWithdraw = getWithdrawDenomList(reserve.current_amount!, denomsAvailable); let ps = denomsForWithdraw.map(async(denom) => { + function mutateReserve(r: ReserveRecord): ReserveRecord { + let currentAmount = r.current_amount; + if (!currentAmount) { + throw Error("can't withdraw when amount is unknown"); + } + r.precoin_amount = Amounts.add(r.precoin_amount, + denom.value, + denom.fee_withdraw).amount; + let result = Amounts.sub(currentAmount, + denom.value, + denom.fee_withdraw); + if (result.saturated) { + console.error("can't create precoin, saturated"); + throw AbortTransaction; + } + r.current_amount = result.amount; + + console.log(`after creating precoin: current ${amountToPretty(r.current_amount)}, precoin: ${amountToPretty( + r.precoin_amount)})}`); + + return r; + } + let preCoin = await this.cryptoApi .createPreCoin(denom, reserve); + await this.q() + .put(Stores.precoins, preCoin) + .mutate(Stores.reserves, reserve.reserve_pub, mutateReserve); await this.processPreCoin(preCoin); }); + await Promise.all(ps); return ps.length; } @@ -1219,6 +1251,7 @@ export class Wallet { if (!amount) { amount = r.requested_amount; } + amount = Amounts.add(amount, r.precoin_amount).amount; if (Amounts.cmp(smallestWithdraw[r.exchange_base_url], amount) < 0) { entry.pendingIncoming = Amounts.add(entry.pendingIncoming, amount).amount; @@ -1333,7 +1366,7 @@ export class Wallet { oldDenom.fee_refresh)); function mutateCoin(c: Coin): Coin { - let r = Amounts.sub(coin.currentAmount, + let r = Amounts.sub(c.currentAmount, refreshSession.valueWithFee); if (r.saturated) { // Something else must have written the coin value diff --git a/popup/popup.tsx b/popup/popup.tsx index d58aa1dd3..5e439417a 100644 --- a/popup/popup.tsx +++ b/popup/popup.tsx @@ -270,7 +270,7 @@ class WalletBalanceView extends preact.Component { return i18n`Error: could not retrieve balance information.`; } if (!wallet) { - return this.renderEmpty(); + return ; } console.log(wallet); let listing = Object.keys(wallet).map((key) => { diff --git a/tsconfig.json b/tsconfig.json index e9efcb50c..de73f283d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,6 @@ "test/tests/taler.ts", "lib/refs.d.ts", "lib/i18n.ts", - "lib/shopApi.ts", "lib/taler-wallet-lib.ts", "lib/wallet/checkable.ts", "lib/wallet/chromeBadge.ts", -- cgit v1.2.3