From 2693075f9fcf954d6db1d22a3e2c0424628844f4 Mon Sep 17 00:00:00 2001 From: DC Date: Sat, 16 Apr 2016 04:56:43 -0700 Subject: [PATCH] Keep all torrent files and poster images in app config folder Fixes #402 --- package.json | 1 + renderer/index.js | 67 +++++++++++++++++++++++++++------ renderer/lib/torrent-poster.js | 2 +- renderer/lib/torrent-summary.js | 24 ++++++++++++ renderer/state.js | 1 + renderer/util.js | 9 ----- renderer/views/player.js | 9 ++--- renderer/views/torrent-list.js | 12 ++---- renderer/webtorrent.js | 10 +++-- 9 files changed, 96 insertions(+), 39 deletions(-) create mode 100644 renderer/lib/torrent-summary.js delete mode 100644 renderer/util.js diff --git a/package.json b/package.json index 778275cc..bca9b38d 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "drag-drop": "^2.11.0", "electron-localshortcut": "^0.6.0", "electron-prebuilt": "0.37.6", + "fs-extra": "^0.27.0", "hyperx": "^2.0.2", "languagedetect": "^1.1.1", "main-loop": "^3.2.0", diff --git a/renderer/index.js b/renderer/index.js index 68d3b70d..24b9145d 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -4,7 +4,7 @@ var appConfig = require('application-config')('WebTorrent') var concat = require('concat-stream') var dragDrop = require('drag-drop') var electron = require('electron') -var fs = require('fs') +var fs = require('fs-extra') var mainLoop = require('main-loop') var path = require('path') var srtToVtt = require('srt-to-vtt') @@ -21,7 +21,7 @@ var errors = require('./lib/errors') var sound = require('./lib/sound') var State = require('./state') var TorrentPlayer = require('./lib/torrent-player') -var util = require('./util') +var TorrentSummary = require('./lib/torrent-summary') var {setDispatch} = require('./lib/dispatcher') setDispatch(dispatch) @@ -61,6 +61,9 @@ loadState(init) * the dock icon and drag+drop. */ function init () { + // Clean up the freshly-loaded config file, which may be from an older version + cleanUpConfig() + // Push the first page into the location history state.location.go({ url: 'home' }) @@ -114,6 +117,46 @@ 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) { + console.log('migration: replacing torrentPath %s', ts.torrentPath) + src = path.isAbsolute(ts.torrentPath) + ? ts.torrentPath + : 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 + } + }) +} + // Lazily loads Chromecast and Airplay support function lazyLoadCast () { if (!Cast) { @@ -574,8 +617,8 @@ function startTorrentingSummary (torrentSummary) { var path = s.path || state.saved.downloadPath var torrentID - if (s.torrentPath) { // Load torrent file from disk - torrentID = util.getAbsoluteStaticPath(s.torrentPath) + if (s.torrentFileName) { // Load torrent file from disk + torrentID = TorrentSummary.getTorrentPath(torrentSummary) } else { // Load torrent from DHT torrentID = s.magnetURI || s.infoHash } @@ -696,10 +739,10 @@ function torrentMetadata (torrentKey, torrentInfo) { update() // Save the .torrent file, if it hasn't been saved already - if (!torrentSummary.torrentPath) ipcRenderer.send('wt-save-torrent-file', torrentKey) + if (!torrentSummary.torrentFileName) ipcRenderer.send('wt-save-torrent-file', torrentKey) // Auto-generate a poster image, if it hasn't been generated already - if (!torrentSummary.posterURL) ipcRenderer.send('wt-generate-torrent-poster', torrentKey) + if (!torrentSummary.posterFileName) ipcRenderer.send('wt-generate-torrent-poster', torrentKey) } function torrentDone (torrentKey, torrentInfo) { @@ -752,16 +795,16 @@ function torrentFileModtimes (torrentKey, fileModtimes) { saveStateThrottled() } -function torrentFileSaved (torrentKey, torrentPath) { - console.log('torrent file saved %s: %s', torrentKey, torrentPath) +function torrentFileSaved (torrentKey, torrentFileName) { + console.log('torrent file saved %s: %s', torrentKey, torrentFileName) var torrentSummary = getTorrentSummary(torrentKey) - torrentSummary.torrentPath = torrentPath + torrentSummary.torrentFileName = torrentFileName saveStateThrottled() } -function torrentPosterSaved (torrentKey, posterPath) { +function torrentPosterSaved (torrentKey, posterFileName) { var torrentSummary = getTorrentSummary(torrentKey) - torrentSummary.posterURL = posterPath + torrentSummary.posterFileName = posterFileName saveStateThrottled() } @@ -967,7 +1010,7 @@ function saveTorrentFileAs (torrentSummary) { ] } dialog.showSaveDialog(remote.getCurrentWindow(), opts, function (savePath) { - var torrentPath = util.getAbsoluteStaticPath(torrentSummary.torrentPath) + var torrentPath = TorrentSummary.getTorrentPath(torrentSummary) fs.readFile(torrentPath, function (err, torrentFile) { if (err) return onError(err) fs.writeFile(savePath, torrentFile, function (err) { diff --git a/renderer/lib/torrent-poster.js b/renderer/lib/torrent-poster.js index 4c8aa658..7f1a2e8a 100644 --- a/renderer/lib/torrent-poster.js +++ b/renderer/lib/torrent-poster.js @@ -20,7 +20,7 @@ function torrentPoster (torrent, cb) { function getLargestFileByExtension (torrent, extensions) { var files = torrent.files.filter(function (file) { - var extname = path.extname(file.name) + var extname = path.extname(file.name).toLowerCase() return extensions.indexOf(extname) !== -1 }) if (files.length === 0) return undefined diff --git a/renderer/lib/torrent-summary.js b/renderer/lib/torrent-summary.js new file mode 100644 index 00000000..23eb5932 --- /dev/null +++ b/renderer/lib/torrent-summary.js @@ -0,0 +1,24 @@ +module.exports = { + getPosterPath, + getTorrentPath +} + +var path = require('path') +var config = require('../../config') + +// Expects a torrentSummary +// Returns an absolute path to the torrent file, or null if unavailable +function getTorrentPath (torrentSummary) { + if (!torrentSummary || !torrentSummary.torrentFileName) return null + return path.join(config.CONFIG_TORRENT_PATH, torrentSummary.torrentFileName) +} + +// Expects a torrentSummary +// Returns an absolute path to the poster image, or null if unavailable +function getPosterPath (torrentSummary) { + if (!torrentSummary || !torrentSummary.posterFileName) return null + var posterPath = path.join(config.CONFIG_POSTER_PATH, torrentSummary.posterFileName) + // Work around a Chrome bug (reproduced in vanilla Chrome, not just Electron): + // Backslashes in URLS in CSS cause bizarre string encoding issues + return posterPath.replace(/\\/g, '/') +} diff --git a/renderer/state.js b/renderer/state.js index 010d091c..70edab78 100644 --- a/renderer/state.js +++ b/renderer/state.js @@ -45,6 +45,7 @@ function getInitialState () { /* * Saved state is read from and written to a file every time the app runs. * It should be simple and minimal and must be JSON. + * It must never contain absolute paths since we have a portable app. * * Config path: * diff --git a/renderer/util.js b/renderer/util.js deleted file mode 100644 index ea24cb06..00000000 --- a/renderer/util.js +++ /dev/null @@ -1,9 +0,0 @@ -var path = require('path') - -var config = require('../config') - -exports.getAbsoluteStaticPath = function (filePath) { - return path.isAbsolute(filePath) - ? filePath - : path.join(config.STATIC_PATH, filePath) -} diff --git a/renderer/views/player.js b/renderer/views/player.js index e1590e34..c6cad1ae 100644 --- a/renderer/views/player.js +++ b/renderer/views/player.js @@ -7,7 +7,7 @@ var hx = hyperx(h) var prettyBytes = require('prettier-bytes') var Bitfield = require('bitfield') -var util = require('../util') +var TorrentSummary = require('../lib/torrent-summary') var {dispatch, dispatcher} = require('../lib/dispatcher') // Shows a streaming video player. Standard features + Chromecast + Airplay @@ -499,10 +499,9 @@ function renderLoadingBar (state) { // Returns the CSS background-image string for a poster image + dark vignette function cssBackgroundImagePoster (state) { var torrentSummary = getPlayingTorrentSummary(state) - if (!torrentSummary || !torrentSummary.posterURL) return '' - var posterURL = util.getAbsoluteStaticPath(torrentSummary.posterURL) - var cleanURL = posterURL.replace(/\\/g, '/') - return cssBackgroundImageDarkGradient() + `, url(${cleanURL})` + var posterPath = TorrentSummary.getPosterPath(torrentSummary) + if (!posterPath) return '' + return cssBackgroundImageDarkGradient() + `, url(${posterPath})` } function cssBackgroundImageDarkGradient () { diff --git a/renderer/views/torrent-list.js b/renderer/views/torrent-list.js index ef2c43bb..28fed5de 100644 --- a/renderer/views/torrent-list.js +++ b/renderer/views/torrent-list.js @@ -5,8 +5,7 @@ var hyperx = require('hyperx') var hx = hyperx(h) var prettyBytes = require('prettier-bytes') -var util = require('../util') - +var TorrentSummary = require('../lib/torrent-summary') var TorrentPlayer = require('../lib/torrent-player') var {dispatcher} = require('../lib/dispatcher') @@ -31,15 +30,12 @@ function TorrentList (state) { // Background image: show some nice visuals, like a frame from the movie, if possible var style = {} - if (torrentSummary.posterURL) { + if (torrentSummary.posterFileName) { var gradient = isSelected ? 'linear-gradient(to bottom, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 100%)' : 'linear-gradient(to bottom, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%)' - var posterURL = util.getAbsoluteStaticPath(torrentSummary.posterURL) - // Work around a Chrome bug (reproduced in vanilla Chrome, not just Electron): - // Backslashes in URLS in CSS cause bizarre string encoding issues - var cleanURL = posterURL.replace(/\\/g, '/') - style.backgroundImage = gradient + `, url('${cleanURL}')` + var posterPath = TorrentSummary.getPosterPath(torrentSummary) + style.backgroundImage = gradient + `, url('${posterPath}')` } // Foreground: name of the torrent, basic info like size, play button, diff --git a/renderer/webtorrent.js b/renderer/webtorrent.js index c8f99e4b..314db03d 100644 --- a/renderer/webtorrent.js +++ b/renderer/webtorrent.js @@ -159,9 +159,10 @@ function getTorrentFileInfo (file) { function saveTorrentFile (torrentKey) { var torrent = getTorrent(torrentKey) checkIfTorrentFileExists(torrent.infoHash, function (torrentPath, exists) { + var fileName = torrent.infoHash + '.torrent' if (exists) { // We've already saved the file - return ipc.send('wt-file-saved', torrentKey, torrentPath) + return ipc.send('wt-file-saved', torrentKey, fileName) } // Otherwise, save the .torrent file, under the app config folder @@ -169,7 +170,7 @@ function saveTorrentFile (torrentKey) { fs.writeFile(torrentPath, torrent.torrentFile, function (err) { if (err) return console.log('error saving torrent file %s: %o', torrentPath, err) console.log('saved torrent file %s', torrentPath) - return ipc.send('wt-file-saved', torrentKey, torrentPath) + return ipc.send('wt-file-saved', torrentKey, fileName) }) }) }) @@ -193,11 +194,12 @@ function generateTorrentPoster (torrentKey) { // save it for next time mkdirp(config.CONFIG_POSTER_PATH, function (err) { if (err) return console.log('error creating poster dir: %o', err) - var posterFilePath = path.join(config.CONFIG_POSTER_PATH, torrent.infoHash + extension) + var posterFileName = torrent.infoHash + extension + var posterFilePath = path.join(config.CONFIG_POSTER_PATH, posterFileName) fs.writeFile(posterFilePath, buf, function (err) { if (err) return console.log('error saving poster: %o', err) // show the poster - ipc.send('wt-poster', torrentKey, posterFilePath) + ipc.send('wt-poster', torrentKey, posterFileName) }) }) })