diff --git a/src/client.js b/src/client.js index 339333319..9d1a1077d 100644 --- a/src/client.js +++ b/src/client.js @@ -448,6 +448,7 @@ MatrixClient.prototype.initCrypto = async function() { this.reEmitter.reEmit(crypto, [ "crypto.keyBackupFailed", + "crypto.keyBackupSessionsRemaining", "crypto.roomKeyRequest", "crypto.roomKeyRequestCancellation", "crypto.warning", diff --git a/src/crypto/index.js b/src/crypto/index.js index e920f28b5..2bbd1f241 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -1052,6 +1052,9 @@ Crypto.prototype._backupPendingKeys = async function(limit) { return 0; } + let remaining = await this._cryptoStore.countSessionsNeedingBackup(); + this.emit("crypto.keyBackupSessionsRemaining", remaining); + const data = {}; for (const session of sessions) { const roomId = session.sessionData.room_id; @@ -1088,7 +1091,10 @@ Crypto.prototype._backupPendingKeys = async function(limit) { undefined, undefined, this.backupInfo.version, {rooms: data}, ); + await this._cryptoStore.unmarkSessionsNeedingBackup(sessions); + remaining = await this._cryptoStore.countSessionsNeedingBackup(); + this.emit("crypto.keyBackupSessionsRemaining", remaining); return sessions.length; }; @@ -1128,6 +1134,9 @@ Crypto.prototype.backupAllGroupSessions = async function(version) { }, ); + const remaining = await this._cryptoStore.countSessionsNeedingBackup(); + this.emit("crypto.keyBackupSessionsRemaining", remaining); + let numKeysBackedUp; do { numKeysBackedUp = await this._backupPendingKeys(KEY_BACKUP_KEYS_PER_REQUEST); diff --git a/src/crypto/store/indexeddb-crypto-store-backend.js b/src/crypto/store/indexeddb-crypto-store-backend.js index 5378efd78..f4b07ed5a 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.js +++ b/src/crypto/store/indexeddb-crypto-store-backend.js @@ -563,8 +563,22 @@ export class Backend { }); } - unmarkSessionsNeedingBackup(sessions) { - const txn = this._db.transaction("sessions_needing_backup", "readwrite"); + countSessionsNeedingBackup(txn) { + if (!txn) { + txn = this._db.transaction("sessions_needing_backup", "readonly"); + } + const objectStore = txn.objectStore("sessions_needing_backup"); + return new Promise((resolve, reject) => { + const req = objectStore.count(); + req.onerror = reject; + req.onsuccess = () => resolve(req.result); + }); + } + + unmarkSessionsNeedingBackup(sessions, txn) { + if (!txn) { + txn = this._db.transaction("sessions_needing_backup", "readwrite"); + } const objectStore = txn.objectStore("sessions_needing_backup"); return Promise.all(sessions.map((session) => { return new Promise((resolve, reject) => { diff --git a/src/crypto/store/indexeddb-crypto-store.js b/src/crypto/store/indexeddb-crypto-store.js index fecc16789..5f9defd02 100644 --- a/src/crypto/store/indexeddb-crypto-store.js +++ b/src/crypto/store/indexeddb-crypto-store.js @@ -480,14 +480,26 @@ export default class IndexedDBCryptoStore { }); } + /** + * Count the inbound group sessions that need to be backed up. + * @param {*} txn An active transaction. See doTxn(). (optional) + * @returns {Promise} resolves to the number of sessions + */ + countSessionsNeedingBackup(txn) { + return this._connect().then((backend) => { + return backend.countSessionsNeedingBackup(txn); + }); + } + /** * Unmark sessions as needing to be backed up. * @param {Array} sessions The sessions that need to be backed up. + * @param {*} txn An active transaction. See doTxn(). (optional) * @returns {Promise} resolves when the sessions are unmarked */ - unmarkSessionsNeedingBackup(sessions) { + unmarkSessionsNeedingBackup(sessions, txn) { return this._connect().then((backend) => { - return backend.unmarkSessionsNeedingBackup(sessions); + return backend.unmarkSessionsNeedingBackup(sessions, txn); }); } diff --git a/src/crypto/store/localStorage-crypto-store.js b/src/crypto/store/localStorage-crypto-store.js index 45c4baff0..4c30b6199 100644 --- a/src/crypto/store/localStorage-crypto-store.js +++ b/src/crypto/store/localStorage-crypto-store.js @@ -221,6 +221,12 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore { return Promise.resolve(sessions); } + countSessionsNeedingBackup() { + const sessionsNeedingBackup + = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; + return Promise.resolve(Object.keys(sessionsNeedingBackup).length); + } + unmarkSessionsNeedingBackup(sessions) { const sessionsNeedingBackup = getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {}; diff --git a/src/crypto/store/memory-crypto-store.js b/src/crypto/store/memory-crypto-store.js index 0b6e0b6fe..49df5f238 100644 --- a/src/crypto/store/memory-crypto-store.js +++ b/src/crypto/store/memory-crypto-store.js @@ -336,6 +336,10 @@ export default class MemoryCryptoStore { return Promise.resolve(sessions); } + countSessionsNeedingBackup() { + return Promise.resolve(Object.keys(this._sessionsNeedingBackup).length); + } + unmarkSessionsNeedingBackup(sessions) { for (const session of sessions) { const sessionKey = session.senderKey + '/' + session.sessionId;