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.
This commit is contained in:
Daniel Gasienica 2018-03-30 16:31:33 -04:00
parent 887bd83852
commit 805031ade8
6 changed files with 87 additions and 25 deletions

View File

@ -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();

View File

@ -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();
}());

View File

@ -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.');
},
}];
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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 = {};