diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 02ad93d38..c89d62f92 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -157,6 +157,17 @@ "description": "Message shown on the loading screen while we are doing application optimizations" }, + "migratingToSQLCipher": { + "message": "Optimizing messages... $status$ complete.", + "description": + "Message shown on the loading screen while we are doing application optimizations", + "placeholders": { + "status": { + "content": "$1", + "example": "45/200" + } + } + }, "chooseDirectory": { "message": "Choose folder", "description": "Button to allow the user to find a folder on disk" diff --git a/js/background.js b/js/background.js index 723210bfa..b1a02af86 100644 --- a/js/background.js +++ b/js/background.js @@ -319,12 +319,34 @@ await upgradeMessages(); const db = await Whisper.Database.open(); - await window.Signal.migrateToSQL({ - db, - clearStores: Whisper.Database.clearStores, - handleDOMException: Whisper.Database.handleDOMException, + const totalMessages = await MessageDataMigrator.getNumMessages({ + connection: db, }); + function showMigrationStatus(current) { + const status = `${current}/${totalMessages}`; + Views.Initialization.setMessage( + window.i18n('migratingToSQLCipher', [status]) + ); + } + + if (totalMessages) { + window.log.info(`About to migrate ${totalMessages} messages`); + + showMigrationStatus(0); + await window.Signal.migrateToSQL({ + db, + clearStores: Whisper.Database.clearStores, + handleDOMException: Whisper.Database.handleDOMException, + countCallback: count => { + window.log.info(`Migration: ${count} messages complete`); + showMigrationStatus(count); + }, + }); + } + + Views.Initialization.setMessage(window.i18n('loading')); + // Note: We are not invoking the second set of IndexedDB migrations because it is // likely that any future migrations will simply extracting things from IndexedDB. diff --git a/js/models/conversations.js b/js/models/conversations.js index d0fb93012..75a4a1025 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -155,7 +155,6 @@ return; } - window.log.info('Merging updated message into collection'); existing.merge(message.attributes); }; diff --git a/js/modules/messages_data_migrator.js b/js/modules/messages_data_migrator.js index 6b9581ba7..77417126d 100644 --- a/js/modules/messages_data_migrator.js +++ b/js/modules/messages_data_migrator.js @@ -134,7 +134,7 @@ exports.dangerouslyProcessAllWithoutIndex = async ({ // NOTE: Even if we make this async using `then`, requesting `count` on an // IndexedDB store blocks all subsequent transactions, so we might as well // explicitly wait for it here: - const numTotalMessages = await _getNumMessages({ connection }); + const numTotalMessages = await exports.getNumMessages({ connection }); const migrationStartTime = Date.now(); let numCumulativeMessagesProcessed = 0; @@ -366,7 +366,7 @@ const _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex = ({ }); }; -const _getNumMessages = async ({ connection } = {}) => { +exports.getNumMessages = async ({ connection } = {}) => { if (!isObject(connection)) { throw new TypeError("'connection' is required"); } diff --git a/js/modules/migrate_to_sql.js b/js/modules/migrate_to_sql.js index 006be1554..36c8cb015 100644 --- a/js/modules/migrate_to_sql.js +++ b/js/modules/migrate_to_sql.js @@ -10,6 +10,8 @@ const { const { getMessageExportLastIndex, setMessageExportLastIndex, + getMessageExportCount, + setMessageExportCount, getUnprocessedExportLastIndex, setUnprocessedExportLastIndex, } = require('./settings'); @@ -18,7 +20,12 @@ module.exports = { migrateToSQL, }; -async function migrateToSQL({ db, clearStores, handleDOMException }) { +async function migrateToSQL({ + db, + clearStores, + handleDOMException, + countCallback, +}) { if (!db) { throw new Error('Need db for IndexedDB connection!'); } @@ -31,7 +38,10 @@ async function migrateToSQL({ db, clearStores, handleDOMException }) { window.log.info('migrateToSQL: start'); - let lastIndex = await getMessageExportLastIndex(db); + let [lastIndex, doneSoFar] = await Promise.all([ + getMessageExportLastIndex(db), + getMessageExportCount(db), + ]); let complete = false; while (!complete) { @@ -48,7 +58,16 @@ async function migrateToSQL({ db, clearStores, handleDOMException }) { ({ complete, lastIndex } = status); // eslint-disable-next-line no-await-in-loop - await setMessageExportLastIndex(db, lastIndex); + await Promise.all([ + setMessageExportCount(db, doneSoFar), + setMessageExportLastIndex(db, lastIndex), + ]); + + const { count } = status; + doneSoFar += count; + if (countCallback) { + countCallback(doneSoFar); + } } window.log.info('migrateToSQL: migrate of messages complete'); @@ -85,7 +104,7 @@ async function migrateStoreToSQLite({ storeName, handleDOMException, lastIndex = null, - batchSize = 20, + batchSize = 50, }) { if (!db) { throw new Error('Need db for IndexedDB connection!'); diff --git a/js/modules/settings.js b/js/modules/settings.js index 2fa2310a7..391b548d6 100644 --- a/js/modules/settings.js +++ b/js/modules/settings.js @@ -4,6 +4,7 @@ const ITEMS_STORE_NAME = 'items'; const LAST_PROCESSED_INDEX_KEY = 'attachmentMigration_lastProcessedIndex'; const IS_MIGRATION_COMPLETE_KEY = 'attachmentMigration_isComplete'; const MESSAGE_LAST_INDEX_KEY = 'sqlMigration_messageLastIndex'; +const MESSAGE_COUNT_KEY = 'sqlMigration_messageCount'; const UNPROCESSED_LAST_INDEX_KEY = 'sqlMigration_unprocessedLastIndex'; // Public API @@ -25,6 +26,10 @@ exports.getMessageExportLastIndex = connection => exports._getItem(connection, MESSAGE_LAST_INDEX_KEY); exports.setMessageExportLastIndex = (connection, lastIndex) => exports._setItem(connection, MESSAGE_LAST_INDEX_KEY, lastIndex); +exports.getMessageExportCount = connection => + exports._getItem(connection, MESSAGE_COUNT_KEY); +exports.setMessageExportCount = (connection, count) => + exports._setItem(connection, MESSAGE_COUNT_KEY, count); exports.getUnprocessedExportLastIndex = connection => exports._getItem(connection, UNPROCESSED_LAST_INDEX_KEY); diff --git a/js/modules/views/initialization.js b/js/modules/views/initialization.js index f543e81c9..1f70e32da 100644 --- a/js/modules/views/initialization.js +++ b/js/modules/views/initialization.js @@ -2,26 +2,44 @@ /* global i18n: false */ -const OPTIMIZATION_MESSAGE_DISPLAY_THRESHOLD = 1000; // milliseconds +const DISPLAY_THRESHOLD = 3000; // milliseconds +const SELECTOR = '.app-loading-screen .message'; -const setMessage = () => { - const message = document.querySelector('.app-loading-screen .message'); - if (!message) { - return () => {}; +let timeout; +let targetString; +let didTimeout = false; + +const clear = () => { + if (timeout) { + clearTimeout(timeout); + timeout = null; } - message.innerText = i18n('loading'); +}; - const optimizingMessageTimeoutId = setTimeout(() => { - const innerMessage = document.querySelector('.app-loading-screen .message'); +const setMessage = loadingText => { + const message = document.querySelector(SELECTOR); + if (!message) { + return clear; + } + + targetString = loadingText || i18n('optimizingApplication'); + + message.innerText = didTimeout ? targetString : i18n('loading'); + + if (timeout) { + return clear; + } + + timeout = setTimeout(() => { + didTimeout = true; + const innerMessage = document.querySelector(SELECTOR); if (!innerMessage) { return; } - innerMessage.innerText = i18n('optimizingApplication'); - }, OPTIMIZATION_MESSAGE_DISPLAY_THRESHOLD); + innerMessage.innerText = targetString; + }, DISPLAY_THRESHOLD); - return () => { - clearTimeout(optimizingMessageTimeoutId); - }; + return clear; }; module.exports = {