diff --git a/package.json b/package.json index c81096dc..bb4235aa 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "network-address": "^1.1.0", "prettier-bytes": "^1.0.1", "run-parallel": "^1.1.6", + "semver": "^5.1.0", "simple-concat": "^1.0.0", "simple-get": "^2.0.0", "srt-to-vtt": "^1.1.1", diff --git a/renderer/lib/migrations.js b/renderer/lib/migrations.js new file mode 100644 index 00000000..c08f4a6b --- /dev/null +++ b/renderer/lib/migrations.js @@ -0,0 +1,90 @@ +/* eslint-disable camelcase */ + +module.exports = { + run +} + +var fs = require('fs-extra') +var path = require('path') +var semver = require('semver') + +var config = require('../../config') + +// Change `state.saved` (which will be saved back to config.json on exit) as +// needed, for example to deal with config.json format changes across versions +function run (state) { + // Replace "{ version: 1 }" with app version (semver) + if (!semver.valid(state.saved.version)) { + state.saved.version = '0.0.0' // Pre-0.7.0 version, so run all migrations + } + + var version = state.saved.version + + if (semver.lt(version, '0.7.0')) { + migrate_0_7_0(state) + } + + // Future migrations... + // if (semver.lt(version, '0.8.0')) { + // migrate_0_8_0(state) + // } + + // Config is now on the new version + state.saved.version = config.APP_VERSION +} + +function migrate_0_7_0 (state) { + console.log('migrate to 0.7.0') + + state.saved.torrents.forEach(function (ts) { + var infoHash = ts.infoHash + + // Replace torrentPath with torrentFileName + var src, dst + if (ts.torrentPath) { + // There are a number of cases to handle here: + // * Originally we used absolute paths + // * Then, relative paths for the default torrents, eg '../static/sintel.torrent' + // * Then, paths computed at runtime for default torrents, eg 'sintel.torrent' + // * Finally, now we're getting rid of torrentPath altogether + console.log('replacing torrentPath %s', ts.torrentPath) + if (path.isAbsolute(ts.torrentPath)) { + src = ts.torrentPath + } else if (ts.torrentPath.startsWith('..')) { + src = ts.torrentPath + } else { + src = path.join(config.STATIC_PATH, ts.torrentPath) + } + dst = path.join(config.CONFIG_TORRENT_PATH, infoHash + '.torrent') + // Synchronous FS calls aren't ideal, but probably OK in a migration + // that only runs once + if (src !== dst) fs.copySync(src, dst) + + delete ts.torrentPath + ts.torrentFileName = infoHash + '.torrent' + } + + // Replace posterURL with posterFileName + if (ts.posterURL) { + console.log('replacing posterURL %s', ts.posterURL) + var extension = path.extname(ts.posterURL) + src = path.isAbsolute(ts.posterURL) + ? ts.posterURL + : path.join(config.STATIC_PATH, ts.posterURL) + dst = path.join(config.CONFIG_POSTER_PATH, infoHash + extension) + // Synchronous FS calls aren't ideal, but probably OK in a migration + // that only runs once + if (src !== dst) fs.copySync(src, dst) + + delete ts.posterURL + ts.posterFileName = infoHash + extension + } + + // Fix exception caused by incorrect file ordering. + // https://github.com/feross/webtorrent-desktop/pull/604#issuecomment-222805214 + delete ts.defaultPlayFileIndex + delete ts.files + delete ts.selections + delete ts.fileModtimes + }) +} diff --git a/renderer/lib/state.js b/renderer/lib/state.js index 16c93a55..88c7b229 100644 --- a/renderer/lib/state.js +++ b/renderer/lib/state.js @@ -93,7 +93,7 @@ function getDefaultPlayState () { /* If the saved state file doesn't exist yet, here's what we use instead */ function getDefaultSavedState () { return { - version: 1, /* make sure we can upgrade gracefully later */ + version: config.APP_VERSION, /* make sure we can upgrade gracefully later */ torrents: [ { status: 'paused', diff --git a/renderer/main.js b/renderer/main.js index e0620efb..f272c257 100644 --- a/renderer/main.js +++ b/renderer/main.js @@ -27,6 +27,7 @@ var patch = require('virtual-dom/patch') var App = require('./views/app') var config = require('../config') var errors = require('./lib/errors') +var migrations = require('./lib/migrations') var sound = require('./lib/sound') var State = require('./lib/state') var TorrentPlayer = require('./lib/torrent-player') @@ -70,7 +71,7 @@ function loadState (cb) { */ function init () { // Clean up the freshly-loaded config file, which may be from an older version - cleanUpConfig() + migrations.run(state) // Restart everything we were torrenting last time the app ran resumeTorrents() @@ -116,60 +117,6 @@ function delayedInit () { sound.preload() } -// Change `state.saved` (which will be saved back to config.json on exit) as -// needed, for example to deal with config.json format changes across versions -function cleanUpConfig () { - state.saved.torrents.forEach(function (ts) { - var infoHash = ts.infoHash - - // Migration: replace torrentPath with torrentFileName - var src, dst - if (ts.torrentPath) { - // There are a number of cases to handle here: - // * Originally we used absolute paths - // * Then, relative paths for the default torrents, eg '../static/sintel.torrent' - // * Then, paths computed at runtime for default torrents, eg 'sintel.torrent' - // * Finally, now we're getting rid of torrentPath altogether - console.log('migration: replacing torrentPath %s', ts.torrentPath) - if (path.isAbsolute(ts.torrentPath)) { - src = ts.torrentPath - } else if (ts.torrentPath.startsWith('..')) { - src = ts.torrentPath - } else { - src = path.join(config.STATIC_PATH, ts.torrentPath) - } - dst = path.join(config.CONFIG_TORRENT_PATH, infoHash + '.torrent') - // Synchronous FS calls aren't ideal, but probably OK in a migration - // that only runs once - if (src !== dst) fs.copySync(src, dst) - - delete ts.torrentPath - ts.torrentFileName = infoHash + '.torrent' - } - - // Migration: replace posterURL with posterFileName - if (ts.posterURL) { - console.log('migration: replacing posterURL %s', ts.posterURL) - var extension = path.extname(ts.posterURL) - src = path.isAbsolute(ts.posterURL) - ? ts.posterURL - : path.join(config.STATIC_PATH, ts.posterURL) - dst = path.join(config.CONFIG_POSTER_PATH, infoHash + extension) - // Synchronous FS calls aren't ideal, but probably OK in a migration - // that only runs once - if (src !== dst) fs.copySync(src, dst) - - delete ts.posterURL - ts.posterFileName = infoHash + extension - } - - // Migration: add per-file selections - if (!ts.selections && ts.files) { - ts.selections = ts.files.map((x) => true) - } - }) -} - // Lazily loads Chromecast and Airplay support function lazyLoadCast () { if (!Cast) {