Allow selecting individual files to torrent
Saves bandwidth and disk space when a torrent contains extra files you don't need Fixes #360
This commit is contained in:
@@ -113,6 +113,10 @@ table {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
@@ -597,7 +601,7 @@ body.drag .app::after {
|
||||
}
|
||||
|
||||
.torrent-details {
|
||||
padding: 8em 20px 20px 20px;
|
||||
padding: 8em 12px 20px 20px;
|
||||
}
|
||||
|
||||
.torrent-details table {
|
||||
@@ -611,6 +615,10 @@ body.drag .app::after {
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.torrent-details td {
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
.torrent-details tr:hover {
|
||||
background-color: rgba(200, 200, 200, 0.3);
|
||||
}
|
||||
@@ -621,16 +629,16 @@ body.drag .app::after {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.torrent-details td.col-icon {
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.torrent-details td.col-icon .icon {
|
||||
.torrent-details td .icon {
|
||||
font-size: 18px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.torrent-details td.col-icon {
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.torrent-details td.col-name {
|
||||
width: auto;
|
||||
text-overflow: ellipsis;
|
||||
@@ -646,6 +654,11 @@ body.drag .app::after {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.torrent-details td.col-select {
|
||||
width: 2em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/*
|
||||
* PLAYER
|
||||
*/
|
||||
|
||||
@@ -153,6 +153,11 @@ function cleanUpConfig () {
|
||||
delete ts.posterURL
|
||||
ts.posterFileName = infoHash + extension
|
||||
}
|
||||
|
||||
// Migration: add per-file selections
|
||||
if (!ts.selections) {
|
||||
ts.selections = ts.files.map((x) => true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -231,6 +236,9 @@ function dispatch (action, ...args) {
|
||||
if (action === 'toggleSelectTorrent') {
|
||||
toggleSelectTorrent(args[0] /* infoHash */)
|
||||
}
|
||||
if (action === 'toggleTorrentFile') {
|
||||
toggleTorrentFile(args[0] /* infoHash */, args[1] /* index */)
|
||||
}
|
||||
if (action === 'openTorrentContextMenu') {
|
||||
openTorrentContextMenu(args[0] /* infoHash */)
|
||||
}
|
||||
@@ -709,7 +717,7 @@ function startTorrentingSummary (torrentSummary) {
|
||||
}
|
||||
|
||||
console.log('start torrenting %s %s', s.torrentKey, torrentID)
|
||||
ipcRenderer.send('wt-start-torrenting', s.torrentKey, torrentID, path, s.fileModtimes)
|
||||
ipcRenderer.send('wt-start-torrenting', s.torrentKey, torrentID, path, s.fileModtimes, s.selections)
|
||||
}
|
||||
|
||||
//
|
||||
@@ -818,6 +826,9 @@ function torrentMetadata (torrentKey, torrentInfo) {
|
||||
torrentSummary.path = torrentInfo.path
|
||||
torrentSummary.files = torrentInfo.files
|
||||
torrentSummary.magnetURI = torrentInfo.magnetURI
|
||||
if (!torrentSummary.selections) {
|
||||
torrentSummary.selections = torrentSummary.files.map((x) => true)
|
||||
}
|
||||
update()
|
||||
|
||||
// Save the .torrent file, if it hasn't been saved already
|
||||
@@ -1058,6 +1069,14 @@ function toggleSelectTorrent (infoHash) {
|
||||
update()
|
||||
}
|
||||
|
||||
function toggleTorrentFile (infoHash, index) {
|
||||
var torrentSummary = getTorrentSummary(infoHash)
|
||||
torrentSummary.selections[index] = !torrentSummary.selections[index]
|
||||
|
||||
// Let the WebTorrent process know to start or stop fetching that file
|
||||
ipcRenderer.send('wt-select-files', infoHash, torrentSummary.selections)
|
||||
}
|
||||
|
||||
function openTorrentContextMenu (infoHash) {
|
||||
var torrentSummary = getTorrentSummary(infoHash)
|
||||
var menu = new electron.remote.Menu()
|
||||
|
||||
@@ -208,7 +208,8 @@ function TorrentList (state) {
|
||||
// Show a single torrentSummary file in the details view for a single torrent
|
||||
function renderFileRow (torrentSummary, file, index) {
|
||||
// First, find out how much of the file we've downloaded
|
||||
var isDone = false
|
||||
var isSelected = torrentSummary.selections[index] // Are we even torrenting it?
|
||||
var isDone = false // Are we finished torrenting it?
|
||||
var progress = ''
|
||||
if (torrentSummary.progress && torrentSummary.progress.files) {
|
||||
var fileProg = torrentSummary.progress.files[index]
|
||||
@@ -217,26 +218,38 @@ function TorrentList (state) {
|
||||
}
|
||||
|
||||
// Second, render the file as a table row
|
||||
var isPlayable = TorrentPlayer.isPlayable(file)
|
||||
var infoHash = torrentSummary.infoHash
|
||||
var icon
|
||||
var rowClass = ''
|
||||
var handleClick
|
||||
if (TorrentPlayer.isPlayable(file)) {
|
||||
if (isPlayable) {
|
||||
icon = 'play_arrow' /* playable? add option to play */
|
||||
handleClick = dispatcher('play', infoHash, index)
|
||||
} else {
|
||||
icon = 'description' /* file icon, opens in OS default app */
|
||||
rowClass = isDone ? '' : 'disabled'
|
||||
handleClick = dispatcher('openFile', infoHash, index)
|
||||
}
|
||||
var rowClass = 'clickable'
|
||||
if (!isSelected) rowClass = 'disabled' // File deselected, not being torrented
|
||||
if (!isDone && !isPlayable) rowClass = 'disabled' // Can't open yet, can't stream
|
||||
return hx`
|
||||
<tr onclick=${handleClick} class='${rowClass}'>
|
||||
<td class='col-icon'>
|
||||
<tr>
|
||||
<td class='col-icon ${rowClass}' onclick=${handleClick}>
|
||||
<i class='icon'>${icon}</i>
|
||||
</td>
|
||||
<td class='col-name'>${file.name}</td>
|
||||
<td class='col-progress'>${progress}</td>
|
||||
<td class='col-size'>${prettyBytes(file.length)}</td>
|
||||
<td class='col-name ${rowClass}' onclick=${handleClick}>
|
||||
${file.name}
|
||||
</td>
|
||||
<td class='col-progress ${rowClass}' onclick=${handleClick}>
|
||||
${isSelected ? progress : ''}
|
||||
</td>
|
||||
<td class='col-size ${rowClass}' onclick=${handleClick}>
|
||||
${prettyBytes(file.length)}
|
||||
</td>
|
||||
<td class='col-select'
|
||||
onclick=${dispatcher('toggleTorrentFile', infoHash, index)}>
|
||||
<i class='icon'>${isSelected ? 'close' : 'add'}</i>
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ function init () {
|
||||
client.on('warning', (err) => ipc.send('wt-warning', null, err.message))
|
||||
client.on('error', (err) => ipc.send('wt-error', null, err.message))
|
||||
|
||||
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes) =>
|
||||
startTorrenting(torrentKey, torrentID, path, fileModtimes))
|
||||
ipc.on('wt-start-torrenting', (e, torrentKey, torrentID, path, fileModtimes, selections) =>
|
||||
startTorrenting(torrentKey, torrentID, path, fileModtimes, selections))
|
||||
ipc.on('wt-stop-torrenting', (e, infoHash) =>
|
||||
stopTorrenting(infoHash))
|
||||
ipc.on('wt-create-torrent', (e, torrentKey, options) =>
|
||||
@@ -65,6 +65,8 @@ function init () {
|
||||
startServer(infoHash, index))
|
||||
ipc.on('wt-stop-server', (e) =>
|
||||
stopServer())
|
||||
ipc.on('wt-select-files', (e, infoHash, selections) =>
|
||||
selectFiles(infoHash, selections))
|
||||
|
||||
ipc.send('ipcReadyWebTorrent')
|
||||
|
||||
@@ -73,7 +75,7 @@ function init () {
|
||||
|
||||
// Starts a given TorrentID, which can be an infohash, magnet URI, etc. Returns WebTorrent object
|
||||
// See https://github.com/feross/webtorrent/blob/master/docs/api.md#clientaddtorrentid-opts-function-ontorrent-torrent-
|
||||
function startTorrenting (torrentKey, torrentID, path, fileModtimes) {
|
||||
function startTorrenting (torrentKey, torrentID, path, fileModtimes, selections) {
|
||||
console.log('starting torrent %s: %s', torrentKey, torrentID)
|
||||
|
||||
var torrent = client.add(torrentID, {
|
||||
@@ -81,8 +83,13 @@ function startTorrenting (torrentKey, torrentID, path, fileModtimes) {
|
||||
fileModtimes: fileModtimes
|
||||
})
|
||||
torrent.key = torrentKey
|
||||
|
||||
// Listen for ready event, progress notifications, etc
|
||||
addTorrentEvents(torrent)
|
||||
|
||||
// Only download the files the user wants, not necessarily all files
|
||||
torrent.once('ready', () => selectFiles(torrent, selections))
|
||||
|
||||
return torrent
|
||||
}
|
||||
|
||||
@@ -308,6 +315,48 @@ function getAudioMetadata (infoHash, index) {
|
||||
})
|
||||
}
|
||||
|
||||
function selectFiles (torrentOrInfoHash, selections) {
|
||||
// Get the torrent object
|
||||
var torrent
|
||||
if (typeof torrentOrInfoHash === 'string') {
|
||||
torrent = client.get(torrentOrInfoHash)
|
||||
} else {
|
||||
torrent = torrentOrInfoHash
|
||||
}
|
||||
|
||||
// Selections not specified?
|
||||
// Load all files. We still need to replace the default whole-torrent
|
||||
// selection with individual selections for each file, so we can
|
||||
// select/deselect files later on
|
||||
if (!selections) {
|
||||
selections = torrent.files.map((x) => true)
|
||||
}
|
||||
|
||||
// Selections specified incorrectly?
|
||||
if (selections.length !== torrent.files.length) {
|
||||
throw new Error('got ' + selections.length + ' file selections, ' +
|
||||
'but the torrent contains ' + torrent.files.length + ' files')
|
||||
}
|
||||
|
||||
// Remove default selection (whole torrent)
|
||||
torrent.deselect(0, torrent.pieces.length - 1, false)
|
||||
|
||||
// Add selections (individual files)
|
||||
for (var i = 0; i < selections.length; i++) {
|
||||
var file = torrent.files[i]
|
||||
if (selections[i]) {
|
||||
file.select()
|
||||
} else {
|
||||
console.log('deselecting file ' + i + ' of torrent ' + torrent.name)
|
||||
file.deselect()
|
||||
|
||||
// If we deselected a file, try to nuke it to save disk space
|
||||
var filePath = path.join(torrent.path, file.path)
|
||||
fs.unlink(filePath) // Ignore errors for now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gets a WebTorrent handle by torrentKey
|
||||
// Throws an Error if we're not currently torrenting anything w/ that key
|
||||
function getTorrent (torrentKey) {
|
||||
|
||||
Reference in New Issue
Block a user