From 805031ade8b0e9fc80a9a30468da502f6eb75013 Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Fri, 30 Mar 2018 16:31:33 -0400 Subject: [PATCH] Conditionally run post-attachment migrations Introduce placeholder migrations for Backbone models so they never implicitly run migrations whenever they are `fetch`ed. We prefer to run our migrations explicitly upon app startup and then let Backbone models be (slightly) dumb(er) models, without inadvertently triggering migrations. --- js/background.js | 22 +++------ js/database.js | 4 +- .../migrations/get_placeholder_migrations.js | 23 +++++++++ ...rations_0_database_with_attachment_data.js | 9 ++++ ...ions_1_database_without_attachment_data.js | 47 ++++++++++++++++--- preload.js | 7 ++- 6 files changed, 87 insertions(+), 25 deletions(-) create mode 100644 js/modules/migrations/get_placeholder_migrations.js diff --git a/js/background.js b/js/background.js index 5c72a636f..b990cfd8b 100644 --- a/js/background.js +++ b/js/background.js @@ -83,23 +83,15 @@ const cancelInitializationMessage = Views.Initialization.setMessage(); console.log('Start IndexedDB migrations'); - console.log('Migrate database with attachments'); + console.log('Run migrations on database with attachment data'); await Migrations0DatabaseWithAttachmentData.run({ Backbone }); - // console.log('Migrate attachments to disk'); - // const database = Migrations0DatabaseWithAttachmentData.getDatabase(); - // await MessageDataMigrator.processAll({ - // Backbone, - // databaseName: database.name, - // minDatabaseVersion: database.version, - // upgradeMessageSchema, - // }); - - // console.log('Migrate database without attachments'); - // await Migrations1DatabaseWithoutAttachmentData.run({ - // Backbone, - // database: Whisper.Database, - // }); + const database = Whisper.Database; + const status = await Migrations1DatabaseWithoutAttachmentData.getStatus({ database }); + console.log('Run migrations on database without attachment data:', status); + if (status.canRun) { + await Migrations1DatabaseWithoutAttachmentData.run({ Backbone, database }); + } console.log('Storage fetch'); storage.fetch(); diff --git a/js/database.js b/js/database.js index 242e91a7b..a5688e8c4 100644 --- a/js/database.js +++ b/js/database.js @@ -6,7 +6,7 @@ (function () { 'use strict'; - const { Migrations0DatabaseWithAttachmentData } = window.Signal.Migrations; + const { getPlaceholderMigrations } = window.Signal.Migrations; window.Whisper = window.Whisper || {}; window.Whisper.Database = window.Whisper.Database || {}; @@ -123,5 +123,5 @@ request.onsuccess = resolve; })); - Whisper.Database.migrations = Migrations0DatabaseWithAttachmentData.migrations; + Whisper.Database.migrations = getPlaceholderMigrations(); }()); diff --git a/js/modules/migrations/get_placeholder_migrations.js b/js/modules/migrations/get_placeholder_migrations.js new file mode 100644 index 000000000..2cd563e4b --- /dev/null +++ b/js/modules/migrations/get_placeholder_migrations.js @@ -0,0 +1,23 @@ +const Migrations0DatabaseWithAttachmentData = + require('./migrations_0_database_with_attachment_data'); +const Migrations1DatabaseWithoutAttachmentData = + require('./migrations_1_database_without_attachment_data'); + + +exports.getPlaceholderMigrations = () => { + const last0MigrationVersion = + Migrations0DatabaseWithAttachmentData.getLatestVersion(); + const last1MigrationVersion = + Migrations1DatabaseWithoutAttachmentData.getLatestVersion(); + + const lastMigrationVersion = last1MigrationVersion || last0MigrationVersion; + + return [{ + version: lastMigrationVersion, + migrate() { + throw new Error('Unexpected invocation of placeholder migration!' + + '\n\nMigrations must explicitly be run upon application startup instead' + + ' of implicitly via Backbone IndexedDB adapter at any time.'); + }, + }]; +}; diff --git a/js/modules/migrations/migrations_0_database_with_attachment_data.js b/js/modules/migrations/migrations_0_database_with_attachment_data.js index a12e788ff..61dffb033 100644 --- a/js/modules/migrations/migrations_0_database_with_attachment_data.js +++ b/js/modules/migrations/migrations_0_database_with_attachment_data.js @@ -154,3 +154,12 @@ exports.getDatabase = () => ({ name: database.id, version: last(exports.migrations).version, }); + +exports.getLatestVersion = () => { + const lastMigration = last(migrations); + if (!lastMigration) { + return null; + } + + return lastMigration.version; +}; diff --git a/js/modules/migrations/migrations_1_database_without_attachment_data.js b/js/modules/migrations/migrations_1_database_without_attachment_data.js index a4fb3e870..9eb53c874 100644 --- a/js/modules/migrations/migrations_1_database_without_attachment_data.js +++ b/js/modules/migrations/migrations_1_database_without_attachment_data.js @@ -1,15 +1,50 @@ +const last = require('lodash/last'); + +const db = require('../database'); +const settings = require('../settings'); const { runMigrations } = require('./run_migrations'); -exports.migrations = [ +// NOTE: Add new migrations that need to traverse entire database, e.g. messages +// store, here. These will only run after attachment migration has completed in +// the background: +const migrations = [ // { - // version: 18, - // async migrate(transaction, next) { - // console.log('Migration 18'); - // console.log('Attachments stored on disk'); + // version: 0, + // migrate(transaction, next) { // next(); // }, // }, ]; -exports.run = runMigrations; +exports.run = async ({ Backbone, database } = {}) => { + const { canRun } = await exports.getStatus({ database }); + if (!canRun) { + throw new Error('Cannot run migrations on database without attachment data'); + } + + await runMigrations({ Backbone, database }); +}; + +exports.getStatus = async ({ database } = {}) => { + const connection = await db.open(database.id, database.version); + const isAttachmentMigrationComplete = + await settings.isAttachmentMigrationComplete(connection); + const hasMigrations = migrations.length > 0; + + const canRun = isAttachmentMigrationComplete && hasMigrations; + return { + isAttachmentMigrationComplete, + hasMigrations, + canRun, + }; +}; + +exports.getLatestVersion = () => { + const lastMigration = last(migrations); + if (!lastMigration) { + return null; + } + + return lastMigration.version; +}; diff --git a/preload.js b/preload.js index 99e8d5f88..2caa365f5 100644 --- a/preload.js +++ b/preload.js @@ -119,6 +119,8 @@ const upgradeMessageSchema = message => Message.upgradeSchema(message, upgradeSchemaContext); + const { getPlaceholderMigrations } = + require('./js/modules/migrations/get_placeholder_migrations'); const { IdleDetector} = require('./js/modules/idle_detector'); window.Signal = {}; @@ -128,13 +130,14 @@ window.Signal.Debug = require('./js/modules/debug'); window.Signal.Logs = require('./js/modules/logs'); window.Signal.Migrations = {}; - window.Signal.Migrations.loadAttachmentData = Attachment.loadData(readAttachmentData); window.Signal.Migrations.deleteAttachmentData = Attachment.deleteData(deleteAttachmentData); - window.Signal.Migrations.upgradeMessageSchema = upgradeMessageSchema; + window.Signal.Migrations.getPlaceholderMigrations =getPlaceholderMigrations; + window.Signal.Migrations.loadAttachmentData = Attachment.loadData(readAttachmentData); window.Signal.Migrations.Migrations0DatabaseWithAttachmentData = require('./js/modules/migrations/migrations_0_database_with_attachment_data'); window.Signal.Migrations.Migrations1DatabaseWithoutAttachmentData = require('./js/modules/migrations/migrations_1_database_without_attachment_data'); + window.Signal.Migrations.upgradeMessageSchema = upgradeMessageSchema; window.Signal.OS = require('./js/modules/os'); window.Signal.Settings = require('./js/modules/settings'); window.Signal.Types = {};