diff --git a/src/app/index.html b/src/app/index.html index 9d0a66b2c4..858888d5d4 100644 --- a/src/app/index.html +++ b/src/app/index.html @@ -37,8 +37,8 @@ - - + + @@ -69,6 +69,7 @@ + @@ -84,32 +85,32 @@ - + - + - + - - + --> @@ -131,19 +132,20 @@ - + - + - + + diff --git a/src/app/lib/seeder.js b/src/app/lib/seeder.js new file mode 100644 index 0000000000..d47cb9c820 --- /dev/null +++ b/src/app/lib/seeder.js @@ -0,0 +1,61 @@ +(function (App) { + + var Seeder = function () { + this.worker = null; + }; + + Seeder.prototype = { + start: function() { + this.getWorkerInstance().send({action: 'start'}); + }, + + stop: function() { + if (this.getWorkerInstance()) { + this.getWorkerInstance().send({action: 'stop'}); + 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({ + name: 'BackgroundSeeder', + 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('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..ddcd1c1771 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) { @@ -240,7 +244,6 @@ // Focus the window when the app opens win.focus(); - }); // Cancel all new windows (Middle clicks / New Tab) @@ -250,7 +253,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..4ab154e61c --- /dev/null +++ b/src/app/lib/workers/seederTask.js @@ -0,0 +1,116 @@ +'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.name = opt.name; + 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'); + process.kill(); + }, + + 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 @@ > + + > + +