From 00e91f3ffc3a831b569a6acf9141bfc4f1737c5a Mon Sep 17 00:00:00 2001 From: chtrinh Date: Fri, 10 Feb 2017 00:36:57 -0800 Subject: [PATCH 1/6] Auto seed torrents after player is closed by using cache torrent files 'autoSeed' - starts up a forked process with a WebTorrent client which enables this feature. Torrent files are create and saved in tmp folder to be used later for seeding. Randomly selects files for join swarm. 'seedLimit' - starting limit for number of torrents connected. --- src/app/index.html | 3 + src/app/lib/seeder.js | 67 +++++++++++++ src/app/lib/streamer.js | 7 ++ src/app/lib/views/main_window.js | 9 +- src/app/lib/views/settings_container.js | 8 ++ src/app/lib/workers/seederTask.js | 114 +++++++++++++++++++++++ src/app/settings.js | 2 + src/app/templates/settings-container.tpl | 4 + 8 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 src/app/lib/seeder.js create mode 100644 src/app/lib/workers/seederTask.js diff --git a/src/app/index.html b/src/app/index.html index 9d0a66b2c4..caa54af8fb 100644 --- a/src/app/index.html +++ b/src/app/index.html @@ -69,6 +69,7 @@ + @@ -145,5 +146,7 @@ + + diff --git a/src/app/lib/seeder.js b/src/app/lib/seeder.js new file mode 100644 index 0000000000..5d161fef2e --- /dev/null +++ b/src/app/lib/seeder.js @@ -0,0 +1,67 @@ +(function (App) { + + var Seeder = function () { + this.worker = null; + }; + + Seeder.prototype = { + start: function() { + this.getWorkerInstance().send({action: 'start'}); + }, + + stop: function() { + this.getWorkerInstance().send({action: 'stop'}); + this.worker.kill(); + this.worker = null; + }, + + append: function(torrent) { + this.getWorkerInstance().send({action: 'append', payload: torrent.name}); + }, + + save: function(torrent) { + var targetFile = path.join(App.settings.tmpLocation, 'TorrentCache', Common.md5(torrent.name) + '.torrent'); + var wstream = fs.createWriteStream(targetFile); + + wstream.write(torrent.torrentFile); + wstream.end(); + }, + + getWorkerInstance: function () { + if (this.worker === null) { + var taskFile = 'src/app/lib/workers/seederTask.js'; + // TODO: AdvSettings here should be trigger creation of worker instance + var args = JSON.stringify({ + connectionLimit: Settings.connectionLimit, + trackerAnnouncement: Settings.trackers.forced, + tmpLocation: App.settings.tmpLocation, + seedLimit: Settings.seedLimit + }); + + this.worker = child.fork(taskFile, [args], {silent: true, execPath:'node'}); + + this.worker.on('disconnect', function(msg) { + win.info('Seeder worker: disconnect'); + }); + + this.worker.on('exit', function(msg) { + win.info('Seeder worker: exit'); + }); + + this.worker.on('message', function(msg) { + win.info(msg); + }); + } + + return this.worker; + } + }; + + var seeder = new Seeder(); + + App.vent.on('seed:start', seeder.start.bind(seeder)); + App.vent.on('seed:stop', seeder.stop.bind(seeder)); + App.vent.on('seed:save', seeder.save.bind(seeder)); + App.vent.on('seed:append', seeder.append.bind(seeder)); + +})(window.App); diff --git a/src/app/lib/streamer.js b/src/app/lib/streamer.js index 6db44ea9f3..6f5c837ec3 100644 --- a/src/app/lib/streamer.js +++ b/src/app/lib/streamer.js @@ -44,6 +44,9 @@ this.setModels(model); this.fetchTorrent(this.torrentModel.get('torrent')).then(function (torrent) { + if (AdvSettings.get('autoSeed')) { + App.vent.trigger('seed:save', torrent); + } this.handleTorrent(torrent); this.watchState(); this.handleStreamInfo(); @@ -63,6 +66,10 @@ // kill the streamer stop: function() { + if (AdvSettings.get('autoSeed')) { + App.vent.trigger('seed:append', this.torrentModel.get('torrent')); + } + if (this.webtorrent) { // update ratio AdvSettings.set('totalDownloaded', Settings.totalDownloaded + this.torrentModel.get('torrent').downloaded); diff --git a/src/app/lib/views/main_window.js b/src/app/lib/views/main_window.js index 3774545073..8c43965bde 100644 --- a/src/app/lib/views/main_window.js +++ b/src/app/lib/views/main_window.js @@ -230,6 +230,10 @@ $('.events').css('display', 'block'); } + if (AdvSettings.get('autoSeed')) { + App.vent.trigger('seed:start'); + } + // set player from settings var players = App.Device.Collection.models; for (var i in players) { @@ -237,10 +241,6 @@ App.Device.Collection.setDevice(AdvSettings.get('chosenPlayer')); } } - - // Focus the window when the app opens - win.focus(); - }); // Cancel all new windows (Middle clicks / New Tab) @@ -250,7 +250,6 @@ App.vent.trigger('updatePostersSizeStylesheet'); App.vent.trigger('main:ready'); - }, movieTabShow: function (e) { diff --git a/src/app/lib/views/settings_container.js b/src/app/lib/views/settings_container.js index bf5834a9c4..3a40ab8c2f 100644 --- a/src/app/lib/views/settings_container.js +++ b/src/app/lib/views/settings_container.js @@ -242,6 +242,7 @@ case 'opensubtitlesAutoUpload': case 'subtitles_bold': case 'rememberFilters': + case 'autoSeed': value = field.is(':checked'); break; case 'httpApiUsername': @@ -292,6 +293,13 @@ syncSetting: function (setting, value) { switch (setting) { + case 'autoSeed': + if (value) { + App.vent.trigger('seed:start'); + } else { + App.vent.trigger('seed:stop'); + } + break; case 'coversShowRating': if (value) { $('.rating').show(); diff --git a/src/app/lib/workers/seederTask.js b/src/app/lib/workers/seederTask.js new file mode 100644 index 0000000000..ca5c716475 --- /dev/null +++ b/src/app/lib/workers/seederTask.js @@ -0,0 +1,114 @@ +'use strict'; + +var _ = require('underscore'); +var fs = require('fs'); +var path = require('path'); +var WebTorrent = require('webtorrent'); +var child = require('child_process'); +var crypt = require('crypto'); + +var SeederTask = function (opt) { + this.webtorrent = null; + this.torrentFiles = null; + this.tmpLocation = opt.tmpLocation; + this.seedLimit = opt.seedLimit; + this.connectionLimit = opt.connectionLimit; + this.trackerAnnouncement = opt.trackerAnnouncement; + this.torrentDir = path.join(this.tmpLocation, 'TorrentCache'); +}; + +SeederTask.prototype = { + start: function() { + if (this.webtorrent) { + this.stop(); + } + + process.send('Seeding started'); + + var seedTorrentFiles = this.seedLimit ? + _.sample(this.getTorrentFiles(), this.seedLimit) : this.getTorrentFiles(); + + seedTorrentFiles.forEach(this.joinSwarm.bind(this)); + }, + + stop: function() { + if (this.webtorrent) { + this.webtorrent.destroy(); + } + + this.webtorrent = null; + this.torrentFiles = []; + + process.send('Seeding stopped'); + }, + + getTorrentFiles: function() { + if (this.torrentFiles === null) { + var regexp = /\.torrent$/i; + var files = fs.readdirSync(this.torrentDir); + + this.torrentFiles = files.filter(function(val) { + return regexp.test(val); + }).map(function(filename) { + return path.join(this.torrentDir, filename); + }, this); + } + + return this.torrentFiles; + }, + + joinSwarm: function (torrentId) { + var client = this.getWebTorrentInstance(); + + if (!client.get(torrentId)) { + var torrent = client.add(torrentId, { + path: this.tmpLocation + }); + + torrent.on('ready', function () { + process.send(`Seeding ${torrent.name} --- ${torrentId}`); + }); + } + }, + + append: function(name) { + var targetFile = path.join(this.torrentDir, this._md5(name) + '.torrent'); + + this.torrentFiles.push(targetFile); + this.seedLimit += 1; + this.joinSwarm(targetFile); + }, + + _md5: function (arg) { + return crypt.createHash('md5').update(arg).digest('hex'); + }, + + getWebTorrentInstance: function() { + if (this.webtorrent === null) { + this.webtorrent = new WebTorrent({ + maxConns: parseInt(this.connectionLimit, 10) || 55, + tracker: { + wrtc: false, + announce: this.trackerAnnouncement + } + }); + + this.webtorrent.on('error', function (error) { + process.send('WebTorrent fatal error', error); + }); + } + + return this.webtorrent; + } +}; + +var args = JSON.parse(process.argv[2]); +var seederTask = new SeederTask(args); + +process.on('message', function (params) { + if (params.action && seederTask[params.action]) { + seederTask[params.action].call(seederTask, params.payload); + } else { + process.send(`Invalid seederTask function`); + } +}); diff --git a/src/app/settings.js b/src/app/settings.js index 3b90992669..e7feceedc7 100644 --- a/src/app/settings.js +++ b/src/app/settings.js @@ -169,6 +169,8 @@ Settings.automaticUpdating = true; Settings.events = true; Settings.minimizeToTray = false; Settings.bigPicture = false; +Settings.autoSeed = false; +Settings.seedLimit = 2; // Features Settings.activateTorrentCollection = false; diff --git a/src/app/templates/settings-container.tpl b/src/app/templates/settings-container.tpl index df53d35030..457d2ce8a9 100644 --- a/src/app/templates/settings-container.tpl +++ b/src/app/templates/settings-container.tpl @@ -505,6 +505,10 @@ > + + > + + From 196198ac942fa13a249f4c8156544b226e237377 Mon Sep 17 00:00:00 2001 From: chtrinh Date: Fri, 10 Feb 2017 01:16:48 -0800 Subject: [PATCH 2/6] Kill forked child process --- src/app/lib/seeder.js | 15 ++++----------- src/app/lib/workers/seederTask.js | 1 + 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/app/lib/seeder.js b/src/app/lib/seeder.js index 5d161fef2e..a437d79dc4 100644 --- a/src/app/lib/seeder.js +++ b/src/app/lib/seeder.js @@ -10,9 +10,10 @@ }, stop: function() { - this.getWorkerInstance().send({action: 'stop'}); - this.worker.kill(); - this.worker = null; + if (this.getWorkerInstance()) { + this.getWorkerInstance().send({action: 'stop'}); + this.worker = null; + } }, append: function(torrent) { @@ -40,14 +41,6 @@ this.worker = child.fork(taskFile, [args], {silent: true, execPath:'node'}); - this.worker.on('disconnect', function(msg) { - win.info('Seeder worker: disconnect'); - }); - - this.worker.on('exit', function(msg) { - win.info('Seeder worker: exit'); - }); - this.worker.on('message', function(msg) { win.info(msg); }); diff --git a/src/app/lib/workers/seederTask.js b/src/app/lib/workers/seederTask.js index ca5c716475..3248896b0f 100644 --- a/src/app/lib/workers/seederTask.js +++ b/src/app/lib/workers/seederTask.js @@ -40,6 +40,7 @@ SeederTask.prototype = { this.torrentFiles = []; process.send('Seeding stopped'); + process.kill(); }, getTorrentFiles: function() { From 035bf0295253c372155245df8c63acabbe782525 Mon Sep 17 00:00:00 2001 From: chtrinh Date: Fri, 10 Feb 2017 01:21:35 -0800 Subject: [PATCH 3/6] Remove non-existent file from index --- src/app/index.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/index.html b/src/app/index.html index caa54af8fb..d598ed031d 100644 --- a/src/app/index.html +++ b/src/app/index.html @@ -132,21 +132,20 @@ - + - + - + - From 1d04cb51d43f655ebd94e5b751b4c1997749ed80 Mon Sep 17 00:00:00 2001 From: chtrinh Date: Tue, 14 Feb 2017 00:56:13 -0800 Subject: [PATCH 4/6] Fix odd whitespace --- src/app/index.html | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/app/index.html b/src/app/index.html index d598ed031d..808c5d02cb 100644 --- a/src/app/index.html +++ b/src/app/index.html @@ -37,8 +37,8 @@ - - + + @@ -85,32 +85,32 @@ - + - + - + - - + --> From f29a031626fa99354ab22ce1c2f03387d1bc5ec4 Mon Sep 17 00:00:00 2001 From: chtrinh Date: Tue, 14 Feb 2017 01:46:04 -0800 Subject: [PATCH 5/6] Add back missing window focus --- src/app/index.html | 8 ++++---- src/app/lib/views/main_window.js | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/app/index.html b/src/app/index.html index 808c5d02cb..858888d5d4 100644 --- a/src/app/index.html +++ b/src/app/index.html @@ -107,7 +107,7 @@ - + --> @@ -132,18 +132,18 @@ - + - + - + diff --git a/src/app/lib/views/main_window.js b/src/app/lib/views/main_window.js index 8c43965bde..ddcd1c1771 100644 --- a/src/app/lib/views/main_window.js +++ b/src/app/lib/views/main_window.js @@ -241,6 +241,9 @@ App.Device.Collection.setDevice(AdvSettings.get('chosenPlayer')); } } + + // Focus the window when the app opens + win.focus(); }); // Cancel all new windows (Middle clicks / New Tab) From c58e6ec1e7fa751723114595fa6cceb2374dd5e4 Mon Sep 17 00:00:00 2001 From: chtrinh Date: Tue, 14 Feb 2017 01:50:49 -0800 Subject: [PATCH 6/6] Add name attribute to seeder task --- src/app/lib/seeder.js | 1 + src/app/lib/workers/seederTask.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/app/lib/seeder.js b/src/app/lib/seeder.js index a437d79dc4..d47cb9c820 100644 --- a/src/app/lib/seeder.js +++ b/src/app/lib/seeder.js @@ -33,6 +33,7 @@ var taskFile = 'src/app/lib/workers/seederTask.js'; // TODO: AdvSettings here should be trigger creation of worker instance var args = JSON.stringify({ + name: 'BackgroundSeeder', connectionLimit: Settings.connectionLimit, trackerAnnouncement: Settings.trackers.forced, tmpLocation: App.settings.tmpLocation, diff --git a/src/app/lib/workers/seederTask.js b/src/app/lib/workers/seederTask.js index 3248896b0f..4ab154e61c 100644 --- a/src/app/lib/workers/seederTask.js +++ b/src/app/lib/workers/seederTask.js @@ -10,6 +10,7 @@ var crypt = require('crypto'); var SeederTask = function (opt) { this.webtorrent = null; this.torrentFiles = null; + this.name = opt.name; this.tmpLocation = opt.tmpLocation; this.seedLimit = opt.seedLimit; this.connectionLimit = opt.connectionLimit;