diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/checker.js | 45 | ||||
-rw-r--r-- | src/database.js | 49 | ||||
-rw-r--r-- | src/logger.js | 41 | ||||
-rw-r--r-- | src/pods.js | 177 | ||||
-rw-r--r-- | src/utils.js | 190 | ||||
-rw-r--r-- | src/videos.js | 245 | ||||
-rw-r--r-- | src/webTorrentNode.js | 145 | ||||
-rw-r--r-- | src/webtorrent.js | 87 |
8 files changed, 979 insertions, 0 deletions
diff --git a/src/checker.js b/src/checker.js new file mode 100644 index 000000000..fd828ca8c --- /dev/null +++ b/src/checker.js | |||
@@ -0,0 +1,45 @@ | |||
1 | ;(function () { | ||
2 | 'use strict' | ||
3 | |||
4 | var config = require('config') | ||
5 | var mkdirp = require('mkdirp') | ||
6 | |||
7 | var checker = {} | ||
8 | |||
9 | // Check the config files | ||
10 | checker.checkConfig = function () { | ||
11 | var required = [ 'listen.port', | ||
12 | 'webserver.https', 'webserver.host', 'webserver.port', | ||
13 | 'database.host', 'database.port', 'database.suffix', | ||
14 | 'storage.certs', 'storage.uploads', 'storage.logs', | ||
15 | 'network.friends' ] | ||
16 | var miss = [] | ||
17 | |||
18 | for (var key of required) { | ||
19 | if (!config.has(key)) { | ||
20 | miss.push(key) | ||
21 | } | ||
22 | } | ||
23 | |||
24 | return miss | ||
25 | } | ||
26 | |||
27 | // Create directories for the storage if it doesn't exist | ||
28 | checker.createDirectories = function () { | ||
29 | var storages = config.get('storage') | ||
30 | |||
31 | for (var key of Object.keys(storages)) { | ||
32 | var path = storages[key] | ||
33 | try { | ||
34 | mkdirp.sync(__dirname + '/../' + path) | ||
35 | } catch (error) { | ||
36 | // Do not use logger | ||
37 | console.error('Cannot create ' + path + ':' + error) | ||
38 | process.exit(0) | ||
39 | } | ||
40 | } | ||
41 | } | ||
42 | |||
43 | // ----------- Export ----------- | ||
44 | module.exports = checker | ||
45 | })() | ||
diff --git a/src/database.js b/src/database.js new file mode 100644 index 000000000..6fbd5dbc8 --- /dev/null +++ b/src/database.js | |||
@@ -0,0 +1,49 @@ | |||
1 | ;(function () { | ||
2 | 'use strict' | ||
3 | |||
4 | var config = require('config') | ||
5 | var mongoose = require('mongoose') | ||
6 | |||
7 | var logger = require('./logger') | ||
8 | |||
9 | var dbname = 'petube' + config.get('database.suffix') | ||
10 | var host = config.get('database.host') | ||
11 | var port = config.get('database.port') | ||
12 | |||
13 | // ----------- Videos ----------- | ||
14 | var videosSchema = mongoose.Schema({ | ||
15 | name: String, | ||
16 | namePath: String, | ||
17 | description: String, | ||
18 | magnetUri: String, | ||
19 | podUrl: String | ||
20 | }) | ||
21 | |||
22 | var VideosDB = mongoose.model('videos', videosSchema) | ||
23 | |||
24 | // ----------- Pods ----------- | ||
25 | var podsSchema = mongoose.Schema({ | ||
26 | url: String, | ||
27 | publicKey: String | ||
28 | }) | ||
29 | |||
30 | var PodsDB = mongoose.model('pods', podsSchema) | ||
31 | |||
32 | // ----------- Connection ----------- | ||
33 | |||
34 | mongoose.connect('mongodb://' + host + ':' + port + '/' + dbname) | ||
35 | mongoose.connection.on('error', function () { | ||
36 | logger.error('Mongodb connection error.') | ||
37 | process.exit(0) | ||
38 | }) | ||
39 | |||
40 | mongoose.connection.on('open', function () { | ||
41 | logger.info('Connected to mongodb.') | ||
42 | }) | ||
43 | |||
44 | // ----------- Export ----------- | ||
45 | module.exports = { | ||
46 | VideosDB: VideosDB, | ||
47 | PodsDB: PodsDB | ||
48 | } | ||
49 | })() | ||
diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 000000000..b1384e8b9 --- /dev/null +++ b/src/logger.js | |||
@@ -0,0 +1,41 @@ | |||
1 | ;(function () { | ||
2 | // Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/ | ||
3 | |||
4 | 'use strict' | ||
5 | |||
6 | var winston = require('winston') | ||
7 | var config = require('config') | ||
8 | |||
9 | var logDir = __dirname + '/../' + config.get('storage.logs') | ||
10 | |||
11 | winston.emitErrs = true | ||
12 | |||
13 | var logger = new winston.Logger({ | ||
14 | transports: [ | ||
15 | new winston.transports.File({ | ||
16 | level: 'debug', | ||
17 | filename: logDir + '/all-logs.log', | ||
18 | handleExceptions: true, | ||
19 | json: true, | ||
20 | maxsize: 5242880, | ||
21 | maxFiles: 5, | ||
22 | colorize: false | ||
23 | }), | ||
24 | new winston.transports.Console({ | ||
25 | level: 'debug', | ||
26 | handleExceptions: true, | ||
27 | humanReadableUnhandledException: true, | ||
28 | json: false, | ||
29 | colorize: true | ||
30 | }) | ||
31 | ], | ||
32 | exitOnError: true | ||
33 | }) | ||
34 | |||
35 | module.exports = logger | ||
36 | module.exports.stream = { | ||
37 | write: function (message, encoding) { | ||
38 | logger.info(message) | ||
39 | } | ||
40 | } | ||
41 | })() | ||
diff --git a/src/pods.js b/src/pods.js new file mode 100644 index 000000000..9d6539685 --- /dev/null +++ b/src/pods.js | |||
@@ -0,0 +1,177 @@ | |||
1 | ;(function () { | ||
2 | 'use strict' | ||
3 | |||
4 | var fs = require('fs') | ||
5 | var config = require('config') | ||
6 | var async = require('async') | ||
7 | var request = require('request') | ||
8 | |||
9 | var logger = require('./logger') | ||
10 | var utils = require('./utils') | ||
11 | var PodsDB = require('./database').PodsDB | ||
12 | |||
13 | var pods = {} | ||
14 | var http = config.get('webserver.https') ? 'https' : 'http' | ||
15 | var host = config.get('webserver.host') | ||
16 | var port = config.get('webserver.port') | ||
17 | |||
18 | // ----------- Private functions ----------- | ||
19 | |||
20 | function getForeignPodsList (url, callback) { | ||
21 | var path = '/api/pods' | ||
22 | |||
23 | request.get(url + path, function (err, response, body) { | ||
24 | if (err) throw err | ||
25 | callback(JSON.parse(body)) | ||
26 | }) | ||
27 | } | ||
28 | |||
29 | // ----------- Public functions ----------- | ||
30 | pods.list = function (callback) { | ||
31 | PodsDB.find(function (err, pods_list) { | ||
32 | if (err) { | ||
33 | logger.error('Cannot get the list of the pods.', { error: err }) | ||
34 | return callback(err) | ||
35 | } | ||
36 | |||
37 | return callback(null, pods_list) | ||
38 | }) | ||
39 | } | ||
40 | |||
41 | // { url } | ||
42 | pods.add = function (data, callback) { | ||
43 | logger.info('Adding pod: %s', data.url) | ||
44 | |||
45 | var params = { | ||
46 | url: data.url, | ||
47 | publicKey: data.publicKey | ||
48 | } | ||
49 | |||
50 | PodsDB.create(params, function (err, pod) { | ||
51 | if (err) { | ||
52 | logger.error('Cannot insert the pod.', { error: err }) | ||
53 | return callback(err) | ||
54 | } | ||
55 | |||
56 | fs.readFile(utils.certDir + 'petube.pub', 'utf8', function (err, cert) { | ||
57 | if (err) { | ||
58 | logger.error('Cannot read cert file.', { error: err }) | ||
59 | return callback(err) | ||
60 | } | ||
61 | |||
62 | return callback(null, { cert: cert }) | ||
63 | }) | ||
64 | }) | ||
65 | } | ||
66 | |||
67 | // { path, data } | ||
68 | pods.makeSecureRequest = function (data, callback) { | ||
69 | PodsDB.find({}, { url: 1, publicKey: 1 }).exec(function (err, urls) { | ||
70 | if (err) { | ||
71 | logger.error('Cannot get the list of the pods.', { error: err }) | ||
72 | return callback(err) | ||
73 | } | ||
74 | |||
75 | logger.debug('Make multiple requests.') | ||
76 | utils.makeMultipleRetryRequest( | ||
77 | { encrypt: true, sign: true, method: data.method, path: data.path, data: data.data }, | ||
78 | |||
79 | urls, | ||
80 | |||
81 | function (err, response, body, url) { | ||
82 | if (err || response.statusCode !== 200) { | ||
83 | logger.error('Error sending secure request to %s/%s pod.', url, data.path, { error: err }) | ||
84 | } | ||
85 | }, | ||
86 | |||
87 | function (err) { | ||
88 | if (err) { | ||
89 | logger.error('There was some errors when sending the video meta data.', { error: err }) | ||
90 | return callback(err) | ||
91 | } | ||
92 | |||
93 | logger.debug('Finished') | ||
94 | callback(null) | ||
95 | } | ||
96 | ) | ||
97 | }) | ||
98 | } | ||
99 | |||
100 | pods.makeFriends = function (callback) { | ||
101 | logger.debug('Read public key...') | ||
102 | fs.readFile(utils.certDir + 'petube.pub', 'utf8', function (err, cert) { | ||
103 | if (err) { | ||
104 | logger.error('Cannot read public cert.', { error: err }) | ||
105 | return callback(err) | ||
106 | } | ||
107 | |||
108 | var urls = config.get('network.friends') | ||
109 | var pods_score = {} | ||
110 | |||
111 | async.each(urls, function (url, callback) { | ||
112 | // Always add a trust pod | ||
113 | pods_score[url] = Infinity | ||
114 | |||
115 | getForeignPodsList(url, function (foreign_pods_list) { | ||
116 | if (foreign_pods_list.length === 0) return callback() | ||
117 | |||
118 | async.each(foreign_pods_list, function (foreign_pod, callback) { | ||
119 | var foreign_url = foreign_pod.url | ||
120 | if (pods_score[foreign_url]) pods_score[foreign_url]++ | ||
121 | else pods_score[foreign_url] = 1 | ||
122 | callback() | ||
123 | }, callback) | ||
124 | }) | ||
125 | }, function () { | ||
126 | logger.debug('Pods score', { pods_score: pods_score }) | ||
127 | |||
128 | // Build the list of pods to add | ||
129 | // Only add a pod if it exists in more than a half base pods | ||
130 | var pods_list = [] | ||
131 | var base_score = urls.length / 2 | ||
132 | Object.keys(pods_score).forEach(function (pod) { | ||
133 | if (pods_score[pod] > base_score) pods_list.push({ url: pod }) | ||
134 | }) | ||
135 | |||
136 | logger.debug('Pods that we keep', { pods: pods_list }) | ||
137 | |||
138 | var data = { | ||
139 | url: http + '://' + host + ':' + port, | ||
140 | publicKey: cert | ||
141 | } | ||
142 | |||
143 | logger.debug('Make requests...') | ||
144 | |||
145 | utils.makeMultipleRetryRequest( | ||
146 | { method: 'POST', path: '/api/pods/', data: data }, | ||
147 | |||
148 | pods_list, | ||
149 | |||
150 | function eachRequest (err, response, body, url) { | ||
151 | if (!err && response.statusCode === 200) { | ||
152 | pods.add({ url: url, publicKey: body.cert }, function (err) { | ||
153 | if (err) { | ||
154 | logger.error('Error with adding %s pod.', url, { error: err }) | ||
155 | } | ||
156 | }) | ||
157 | } else { | ||
158 | logger.error('Error with adding %s pod.', url) | ||
159 | } | ||
160 | }, | ||
161 | |||
162 | function endRequests (err) { | ||
163 | if (err) { | ||
164 | logger.error('There was some errors when we wanted to make friends.', { error: err }) | ||
165 | return callback(err) | ||
166 | } | ||
167 | |||
168 | logger.debug('Finished') | ||
169 | callback(null) | ||
170 | } | ||
171 | ) | ||
172 | }) | ||
173 | }) | ||
174 | } | ||
175 | |||
176 | module.exports = pods | ||
177 | })() | ||
diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 000000000..0b21565dd --- /dev/null +++ b/src/utils.js | |||
@@ -0,0 +1,190 @@ | |||
1 | ;(function () { | ||
2 | 'use strict' | ||
3 | |||
4 | var request = require('request') | ||
5 | var replay = require('request-replay') | ||
6 | var ursa = require('ursa') | ||
7 | var config = require('config') | ||
8 | var fs = require('fs') | ||
9 | var openssl = require('openssl-wrapper') | ||
10 | var crypto = require('crypto') | ||
11 | |||
12 | var logger = require('./logger') | ||
13 | |||
14 | var http = config.get('webserver.https') ? 'https' : 'http' | ||
15 | var host = config.get('webserver.host') | ||
16 | var port = config.get('webserver.port') | ||
17 | var algorithm = 'aes-256-ctr' | ||
18 | |||
19 | var utils = {} | ||
20 | |||
21 | // ----------- Private functions ---------- | ||
22 | |||
23 | function makeRetryRequest (params, from_url, to_pod, signature, callbackEach) { | ||
24 | // Append the signature | ||
25 | if (signature) { | ||
26 | params.json.signature = { | ||
27 | url: from_url, | ||
28 | signature: signature | ||
29 | } | ||
30 | } | ||
31 | |||
32 | logger.debug('Sending informations to %s', to_pod.url, { params: params }) | ||
33 | |||
34 | // Replay 15 times, with factor 3 | ||
35 | replay( | ||
36 | request.post(params, function (err, response, body) { | ||
37 | callbackEach(err, response, body, to_pod.url) | ||
38 | }), | ||
39 | { | ||
40 | retries: 10, | ||
41 | factor: 3, | ||
42 | maxTimeout: Infinity, | ||
43 | errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ] | ||
44 | } | ||
45 | ).on('replay', function (replay) { | ||
46 | logger.info('Replaying request to %s. Request failed: %d %s. Replay number: #%d. Will retry in: %d ms.', | ||
47 | params.url, replay.error.code, replay.error.message, replay.number, replay.delay) | ||
48 | }) | ||
49 | } | ||
50 | |||
51 | // ----------- Public attributes ---------- | ||
52 | utils.certDir = __dirname + '/../' + config.get('storage.certs') | ||
53 | |||
54 | // { path, data } | ||
55 | utils.makeMultipleRetryRequest = function (all, pods, callbackEach, callback) { | ||
56 | if (!callback) { | ||
57 | callback = callbackEach | ||
58 | callbackEach = function () {} | ||
59 | } | ||
60 | |||
61 | var url = http + '://' + host + ':' + port | ||
62 | var signature | ||
63 | |||
64 | // Signature ? | ||
65 | if (all.method === 'POST' && all.data && all.sign === true) { | ||
66 | var myKey = ursa.createPrivateKey(fs.readFileSync(utils.certDir + 'petube.key.pem')) | ||
67 | signature = myKey.hashAndSign('sha256', url, 'utf8', 'hex') | ||
68 | } | ||
69 | |||
70 | // Make a request for each pod | ||
71 | for (var pod of pods) { | ||
72 | var params = { | ||
73 | url: pod.url + all.path, | ||
74 | method: all.method | ||
75 | } | ||
76 | |||
77 | // Add data with POST requst ? | ||
78 | if (all.method === 'POST' && all.data) { | ||
79 | logger.debug('Make a POST request.') | ||
80 | |||
81 | // Encrypt data ? | ||
82 | if (all.encrypt === true) { | ||
83 | logger.debug(pod.publicKey) | ||
84 | var crt = ursa.createPublicKey(pod.publicKey) | ||
85 | |||
86 | // TODO: ES6 with let | ||
87 | ;(function (crt_copy, copy_params, copy_url, copy_pod, copy_signature) { | ||
88 | utils.symetricEncrypt(JSON.stringify(all.data), function (err, dataEncrypted) { | ||
89 | if (err) throw err | ||
90 | |||
91 | var passwordEncrypted = crt_copy.encrypt(dataEncrypted.password, 'utf8', 'hex') | ||
92 | copy_params.json = { | ||
93 | data: dataEncrypted.crypted, | ||
94 | key: passwordEncrypted | ||
95 | } | ||
96 | |||
97 | makeRetryRequest(copy_params, copy_url, copy_pod, copy_signature, callbackEach) | ||
98 | }) | ||
99 | })(crt, params, url, pod, signature) | ||
100 | } else { | ||
101 | params.json = { data: all.data } | ||
102 | makeRetryRequest(params, url, pod, signature, callbackEach) | ||
103 | } | ||
104 | } else { | ||
105 | logger.debug('Make a GET/DELETE request') | ||
106 | makeRetryRequest(params, url, pod, signature, callbackEach) | ||
107 | } | ||
108 | } | ||
109 | |||
110 | return callback() | ||
111 | } | ||
112 | |||
113 | utils.certsExist = function (callback) { | ||
114 | fs.exists(utils.certDir + 'petube.key.pem', function (exists) { | ||
115 | return callback(exists) | ||
116 | }) | ||
117 | } | ||
118 | |||
119 | utils.createCerts = function (callback) { | ||
120 | utils.certsExist(function (exist) { | ||
121 | if (exist === true) { | ||
122 | var string = 'Certs already exist.' | ||
123 | logger.warning(string) | ||
124 | return callback(new Error(string)) | ||
125 | } | ||
126 | |||
127 | logger.debug('Gen RSA keys...') | ||
128 | openssl.exec('genrsa', { 'out': utils.certDir + 'petube.key.pem' }, function (err) { | ||
129 | if (err) { | ||
130 | logger.error('Cannot create private key on this pod.', { error: err }) | ||
131 | return callback(err) | ||
132 | } | ||
133 | |||
134 | logger.debug('Manage public key...') | ||
135 | openssl.exec('rsa', { 'in': utils.certDir + 'petube.key.pem', 'pubout': true, 'out': utils.certDir + 'petube.pub' }, function (err) { | ||
136 | if (err) { | ||
137 | logger.error('Cannot create public key on this pod .', { error: err }) | ||
138 | return callback(err) | ||
139 | } | ||
140 | |||
141 | return callback(null) | ||
142 | }) | ||
143 | }) | ||
144 | }) | ||
145 | } | ||
146 | |||
147 | utils.createCertsIfNotExist = function (callback) { | ||
148 | utils.certsExist(function (exist) { | ||
149 | if (exist === true) { | ||
150 | return callback(null) | ||
151 | } | ||
152 | |||
153 | utils.createCerts(function (err) { | ||
154 | return callback(err) | ||
155 | }) | ||
156 | }) | ||
157 | } | ||
158 | |||
159 | utils.generatePassword = function (callback) { | ||
160 | crypto.randomBytes(32, function (err, buf) { | ||
161 | if (err) { | ||
162 | return callback(err) | ||
163 | } | ||
164 | |||
165 | callback(null, buf.toString('utf8')) | ||
166 | }) | ||
167 | } | ||
168 | |||
169 | utils.symetricEncrypt = function (text, callback) { | ||
170 | utils.generatePassword(function (err, password) { | ||
171 | if (err) { | ||
172 | return callback(err) | ||
173 | } | ||
174 | |||
175 | var cipher = crypto.createCipher(algorithm, password) | ||
176 | var crypted = cipher.update(text, 'utf8', 'hex') | ||
177 | crypted += cipher.final('hex') | ||
178 | callback(null, { crypted: crypted, password: password }) | ||
179 | }) | ||
180 | } | ||
181 | |||
182 | utils.symetricDecrypt = function (text, password) { | ||
183 | var decipher = crypto.createDecipher(algorithm, password) | ||
184 | var dec = decipher.update(text, 'hex', 'utf8') | ||
185 | dec += decipher.final('utf8') | ||
186 | return dec | ||
187 | } | ||
188 | |||
189 | module.exports = utils | ||
190 | })() | ||
diff --git a/src/videos.js b/src/videos.js new file mode 100644 index 000000000..605ce098f --- /dev/null +++ b/src/videos.js | |||
@@ -0,0 +1,245 @@ | |||
1 | ;(function () { | ||
2 | 'use strict' | ||
3 | |||
4 | var fs = require('fs') | ||
5 | var webtorrent = require('./webTorrentNode') | ||
6 | var config = require('config') | ||
7 | var async = require('async') | ||
8 | |||
9 | var logger = require('./logger') | ||
10 | var VideosDB = require('./database').VideosDB | ||
11 | var pods = require('./pods') | ||
12 | |||
13 | var videos = {} | ||
14 | // Public url | ||
15 | var http = config.get('webserver.https') === true ? 'https' : 'http' | ||
16 | var host = config.get('webserver.host') | ||
17 | var port = config.get('webserver.port') | ||
18 | |||
19 | // ----------- Private functions ----------- | ||
20 | function seedVideo (path, callback) { | ||
21 | logger.debug('Seeding : %s', path) | ||
22 | |||
23 | webtorrent.seed(path, function (torrent) { | ||
24 | logger.debug('Seeded : %s', torrent.magnetURI) | ||
25 | |||
26 | return callback(null, torrent) | ||
27 | }) | ||
28 | } | ||
29 | |||
30 | // ----------- Public attributes ---------- | ||
31 | videos.uploadDir = __dirname + '/../' + config.get('storage.uploads') | ||
32 | |||
33 | // ----------- Public functions ----------- | ||
34 | videos.list = function (callback) { | ||
35 | VideosDB.find(function (err, videos_list) { | ||
36 | if (err) { | ||
37 | logger.error('Cannot get list of the videos.', { error: err }) | ||
38 | return callback(err) | ||
39 | } | ||
40 | |||
41 | return callback(null, videos_list) | ||
42 | }) | ||
43 | } | ||
44 | |||
45 | videos.add = function (data, callback) { | ||
46 | var video_file = data.video | ||
47 | var video_data = data.data | ||
48 | |||
49 | logger.debug('Path: %s', video_file.path) | ||
50 | seedVideo(video_file.path, function (err, torrent) { | ||
51 | if (err) { | ||
52 | logger.error('Cannot seed this video.', { error: err }) | ||
53 | return callback(err) | ||
54 | } | ||
55 | |||
56 | var params = { | ||
57 | name: video_data.name, | ||
58 | namePath: video_file.name, | ||
59 | description: video_data.description, | ||
60 | magnetUri: torrent.magnetURI, | ||
61 | podUrl: http + '://' + host + ':' + port | ||
62 | } | ||
63 | |||
64 | VideosDB.create(params, function (err, video) { | ||
65 | if (err) { | ||
66 | logger.error('Cannot insert this video.', { error: err }) | ||
67 | return callback(err) | ||
68 | } | ||
69 | |||
70 | // Now we'll send the video's meta data | ||
71 | params.namePath = null | ||
72 | |||
73 | logger.debug('Sending this video Uri to friends...') | ||
74 | |||
75 | var data = { | ||
76 | path: '/api/remotevideos/add', | ||
77 | method: 'POST', | ||
78 | data: params | ||
79 | } | ||
80 | |||
81 | pods.makeSecureRequest(data, function (err) { | ||
82 | if (err) { | ||
83 | logger.error('Somes issues when sending this video to friends.', { error: err }) | ||
84 | return callback(err) | ||
85 | } | ||
86 | |||
87 | return callback(null) | ||
88 | }) | ||
89 | }) | ||
90 | }) | ||
91 | } | ||
92 | |||
93 | videos.remove = function (id, callback) { | ||
94 | // Maybe the torrent is not seeding, it doesn't have importance | ||
95 | function removeTorrent (magnetUri, callback) { | ||
96 | try { | ||
97 | webtorrent.remove(magnetUri, callback) | ||
98 | } catch (err) { | ||
99 | logger.warn('Cannot remove the torrent from WebTorrent', { err: err }) | ||
100 | return callback(null) | ||
101 | } | ||
102 | } | ||
103 | |||
104 | VideosDB.findById(id, function (err, video) { | ||
105 | if (err || !video) { | ||
106 | logger.error('Cannot find this video.', { error: err }) | ||
107 | return callback(err) | ||
108 | } | ||
109 | |||
110 | if (video.namePath === null) { | ||
111 | var error_string = 'Cannot remove the video of another pod.' | ||
112 | logger.error(error_string) | ||
113 | return callback(new Error(error_string)) | ||
114 | } | ||
115 | |||
116 | logger.debug('Removing video %s', video.magnetUri) | ||
117 | |||
118 | removeTorrent(video.magnetUri, function () { | ||
119 | VideosDB.findByIdAndRemove(id, function (err) { | ||
120 | if (err) { | ||
121 | logger.error('Cannot remove the torrent.', { error: err }) | ||
122 | return callback(err) | ||
123 | } | ||
124 | |||
125 | fs.unlink(videos.uploadDir + video.namePath, function (err) { | ||
126 | if (err) { | ||
127 | logger.error('Cannot remove this video file.', { error: err }) | ||
128 | return callback(err) | ||
129 | } | ||
130 | |||
131 | var data = { | ||
132 | path: '/api/remotevideos/remove', | ||
133 | method: 'POST', | ||
134 | data: { | ||
135 | magnetUri: video.magnetUri | ||
136 | } | ||
137 | } | ||
138 | |||
139 | // Yes this is a POST request because we add some informations in the body (signature, encrypt etc) | ||
140 | pods.makeSecureRequest(data, function (err) { | ||
141 | if (err) { | ||
142 | logger.error('Somes issues when sending we want to remove the video to friends.', { error: err }) | ||
143 | return callback(err) | ||
144 | } | ||
145 | |||
146 | callback(null) | ||
147 | }) | ||
148 | }) | ||
149 | }) | ||
150 | }) | ||
151 | }) | ||
152 | } | ||
153 | |||
154 | // Use the magnet Uri because the _id field is not the same on different servers | ||
155 | videos.removeRemote = function (fromUrl, magnetUri, callback) { | ||
156 | // TODO : check if the remote server has the rights to remove this video | ||
157 | |||
158 | VideosDB.findOne({ magnetUri: magnetUri }, function (err, video) { | ||
159 | if (err || !video) { | ||
160 | logger.error('Cannot find the torrent URI of this remote video.') | ||
161 | return callback(err) | ||
162 | } | ||
163 | |||
164 | if (video.podUrl !== fromUrl) { | ||
165 | logger.error('The pod has not rights on this video.') | ||
166 | return callback(err) | ||
167 | } | ||
168 | |||
169 | VideosDB.findByIdAndRemove(video._id, function (err) { | ||
170 | if (err) { | ||
171 | logger.error('Cannot remove the remote video.') | ||
172 | return callback(err) | ||
173 | } | ||
174 | |||
175 | callback(null) | ||
176 | }) | ||
177 | }) | ||
178 | } | ||
179 | |||
180 | // { name, magnetUri, podUrl } | ||
181 | videos.addRemote = function (data, callback) { | ||
182 | logger.debug('Add remote video from pod: %s', data.podUrl) | ||
183 | |||
184 | var params = { | ||
185 | name: data.name, | ||
186 | namePath: null, | ||
187 | description: data.description, | ||
188 | magnetUri: data.magnetUri, | ||
189 | podUrl: data.podUrl | ||
190 | } | ||
191 | |||
192 | VideosDB.create(params, function (err, video) { | ||
193 | if (err) { | ||
194 | logger.error('Cannot insert this remote video.', { error: err }) | ||
195 | return callback(err) | ||
196 | } | ||
197 | |||
198 | return callback(null, video) | ||
199 | }) | ||
200 | } | ||
201 | |||
202 | videos.get = function (id, callback) { | ||
203 | VideosDB.findById(id, function (err, video) { | ||
204 | if (err) { | ||
205 | logger.error('Cannot get this video.', { error: err }) | ||
206 | return callback(err) | ||
207 | } | ||
208 | |||
209 | return callback(null, video) | ||
210 | }) | ||
211 | } | ||
212 | |||
213 | videos.search = function (name, callback) { | ||
214 | VideosDB.find({ name: new RegExp(name) }, function (err, videos) { | ||
215 | if (err) { | ||
216 | logger.error('Cannot search the videos.', { error: err }) | ||
217 | return callback(err) | ||
218 | } | ||
219 | |||
220 | return callback(null, videos) | ||
221 | }) | ||
222 | } | ||
223 | |||
224 | videos.seedAll = function (final_callback) { | ||
225 | VideosDB.find({ namePath: { $ne: null } }, function (err, videos_list) { | ||
226 | if (err) { | ||
227 | logger.error('Cannot get list of the videos to seed.', { error: err }) | ||
228 | return final_callback(err) | ||
229 | } | ||
230 | |||
231 | async.each(videos_list, function (video, callback) { | ||
232 | seedVideo(videos.uploadDir + video.namePath, function (err) { | ||
233 | if (err) { | ||
234 | logger.error('Cannot seed this video.', { error: err }) | ||
235 | return callback(err) | ||
236 | } | ||
237 | |||
238 | callback(null) | ||
239 | }) | ||
240 | }, final_callback) | ||
241 | }) | ||
242 | } | ||
243 | |||
244 | module.exports = videos | ||
245 | })() | ||
diff --git a/src/webTorrentNode.js b/src/webTorrentNode.js new file mode 100644 index 000000000..03bff7803 --- /dev/null +++ b/src/webTorrentNode.js | |||
@@ -0,0 +1,145 @@ | |||
1 | ;(function () { | ||
2 | 'use strict' | ||
3 | |||
4 | var spawn = require('electron-spawn') | ||
5 | var config = require('config') | ||
6 | var ipc = require('node-ipc') | ||
7 | var pathUtils = require('path') | ||
8 | |||
9 | var logger = require('./logger') | ||
10 | |||
11 | var host | ||
12 | var port | ||
13 | |||
14 | try { | ||
15 | host = config.get('webserver.host') | ||
16 | port = config.get('webserver.port') | ||
17 | } catch (e) { | ||
18 | host = 'client' | ||
19 | port = 1 | ||
20 | } | ||
21 | |||
22 | var nodeKey = 'webtorrentnode' + port | ||
23 | var processKey = 'webtorrent' + port | ||
24 | |||
25 | ipc.config.silent = true | ||
26 | ipc.config.id = nodeKey | ||
27 | |||
28 | var webtorrentnode = {} | ||
29 | |||
30 | // Useful for beautiful tests | ||
31 | webtorrentnode.silent = false | ||
32 | |||
33 | // Useful to kill it | ||
34 | webtorrentnode.app = null | ||
35 | |||
36 | webtorrentnode.create = function (callback) { | ||
37 | ipc.serve(function () { | ||
38 | if (!webtorrentnode.silent) logger.info('IPC server ready.') | ||
39 | |||
40 | ipc.server.on(processKey + '.ready', function () { | ||
41 | if (!webtorrentnode.silent) logger.info('Webtorrent process ready.') | ||
42 | callback() | ||
43 | }) | ||
44 | |||
45 | var webtorrent_process = spawn(__dirname + '/webtorrent.js', host, port, { detached: true }) | ||
46 | webtorrent_process.stderr.on('data', function (data) { | ||
47 | // logger.debug('Webtorrent process stderr: ', data.toString()) | ||
48 | }) | ||
49 | |||
50 | webtorrent_process.stdout.on('data', function (data) { | ||
51 | // logger.debug('Webtorrent process:', data.toString()) | ||
52 | }) | ||
53 | |||
54 | function exitChildProcess () { | ||
55 | if (!webtorrentnode.silent) logger.info('Gracefully exit child') | ||
56 | process.kill(-webtorrent_process.pid) | ||
57 | process.exit(0) | ||
58 | } | ||
59 | |||
60 | process.on('SIGINT', exitChildProcess) | ||
61 | process.on('SIGTERM', exitChildProcess) | ||
62 | |||
63 | webtorrentnode.app = webtorrent_process | ||
64 | }) | ||
65 | |||
66 | ipc.server.start() | ||
67 | } | ||
68 | |||
69 | webtorrentnode.seed = function (path, callback) { | ||
70 | var extension = pathUtils.extname(path) | ||
71 | var basename = pathUtils.basename(path, extension) | ||
72 | var data = { | ||
73 | _id: basename, | ||
74 | args: { | ||
75 | path: path | ||
76 | } | ||
77 | } | ||
78 | |||
79 | if (!webtorrentnode.silent) logger.debug('Node wants to seed ' + data._id) | ||
80 | |||
81 | // Finish signal | ||
82 | ipc.server.on(nodeKey + '.seedDone.' + data._id, function (received) { | ||
83 | if (!webtorrentnode.silent) logger.debug('Process seeded torrent ' + received.magnetUri) | ||
84 | |||
85 | // This is a fake object, we just use the magnetUri in this project | ||
86 | var torrent = { | ||
87 | magnetURI: received.magnetUri | ||
88 | } | ||
89 | |||
90 | callback(torrent) | ||
91 | }) | ||
92 | |||
93 | ipc.server.broadcast(processKey + '.seed', data) | ||
94 | } | ||
95 | |||
96 | webtorrentnode.add = function (magnetUri, callback) { | ||
97 | var data = { | ||
98 | _id: magnetUri, | ||
99 | args: { | ||
100 | magnetUri: magnetUri | ||
101 | } | ||
102 | } | ||
103 | |||
104 | if (!webtorrentnode.silent) logger.debug('Node wants to add ' + data._id) | ||
105 | |||
106 | // Finish signal | ||
107 | ipc.server.on(nodeKey + '.addDone.' + data._id, function (received) { | ||
108 | if (!webtorrentnode.silent) logger.debug('Process added torrent') | ||
109 | |||
110 | // This is a fake object, we just use the magnetUri in this project | ||
111 | var torrent = { | ||
112 | files: received.files | ||
113 | } | ||
114 | |||
115 | callback(torrent) | ||
116 | }) | ||
117 | |||
118 | ipc.server.broadcast(processKey + '.add', data) | ||
119 | } | ||
120 | |||
121 | webtorrentnode.remove = function (magnetUri, callback) { | ||
122 | var data = { | ||
123 | _id: magnetUri, | ||
124 | args: { | ||
125 | magnetUri: magnetUri | ||
126 | } | ||
127 | } | ||
128 | |||
129 | if (!webtorrentnode.silent) logger.debug('Node wants to stop seeding ' + data._id) | ||
130 | |||
131 | // Finish signal | ||
132 | ipc.server.on(nodeKey + '.removeDone.' + data._id, function (received) { | ||
133 | if (!webtorrentnode.silent) logger.debug('Process removed torrent ' + data._id) | ||
134 | |||
135 | var err = null | ||
136 | if (received.err) err = received.err | ||
137 | |||
138 | callback(err) | ||
139 | }) | ||
140 | |||
141 | ipc.server.broadcast(processKey + '.remove', data) | ||
142 | } | ||
143 | |||
144 | module.exports = webtorrentnode | ||
145 | })() | ||
diff --git a/src/webtorrent.js b/src/webtorrent.js new file mode 100644 index 000000000..840f97671 --- /dev/null +++ b/src/webtorrent.js | |||
@@ -0,0 +1,87 @@ | |||
1 | ;(function () { | ||
2 | 'use strict' | ||
3 | |||
4 | module.exports = function (args) { | ||
5 | var WebTorrent = require('webtorrent') | ||
6 | var ipc = require('node-ipc') | ||
7 | |||
8 | if (args.length !== 3) { | ||
9 | console.log('Wrong arguments number: ' + args.length + '/3') | ||
10 | process.exit(-1) | ||
11 | } | ||
12 | |||
13 | var host = args[1] | ||
14 | var port = args[2] | ||
15 | var nodeKey = 'webtorrentnode' + port | ||
16 | var processKey = 'webtorrent' + port | ||
17 | |||
18 | ipc.config.silent = true | ||
19 | ipc.config.id = processKey | ||
20 | |||
21 | if (host === 'client' && port === '1') global.WEBTORRENT_ANNOUNCE = [] | ||
22 | else global.WEBTORRENT_ANNOUNCE = 'ws://' + host + ':' + port + '/tracker/socket' | ||
23 | var wt = new WebTorrent({ dht: false }) | ||
24 | |||
25 | function seed (data) { | ||
26 | var args = data.args | ||
27 | var path = args.path | ||
28 | var _id = data._id | ||
29 | |||
30 | wt.seed(path, function (torrent) { | ||
31 | var to_send = { | ||
32 | magnetUri: torrent.magnetURI | ||
33 | } | ||
34 | |||
35 | ipc.of[nodeKey].emit(nodeKey + '.seedDone.' + _id, to_send) | ||
36 | }) | ||
37 | } | ||
38 | |||
39 | function add (data) { | ||
40 | var args = data.args | ||
41 | var magnetUri = args.magnetUri | ||
42 | var _id = data._id | ||
43 | |||
44 | wt.add(magnetUri, function (torrent) { | ||
45 | var to_send = { | ||
46 | files: [] | ||
47 | } | ||
48 | |||
49 | torrent.files.forEach(function (file) { | ||
50 | to_send.files.push({ path: file.path }) | ||
51 | }) | ||
52 | |||
53 | ipc.of[nodeKey].emit(nodeKey + '.addDone.' + _id, to_send) | ||
54 | }) | ||
55 | } | ||
56 | |||
57 | function remove (data) { | ||
58 | var args = data.args | ||
59 | var magnetUri = args.magnetUri | ||
60 | var _id = data._id | ||
61 | |||
62 | try { | ||
63 | wt.remove(magnetUri, callback) | ||
64 | } catch (err) { | ||
65 | console.log('Cannot remove the torrent from WebTorrent', { err: err }) | ||
66 | return callback(null) | ||
67 | } | ||
68 | |||
69 | function callback () { | ||
70 | var to_send = {} | ||
71 | ipc.of[nodeKey].emit(nodeKey + '.removeDone.' + _id, to_send) | ||
72 | } | ||
73 | } | ||
74 | |||
75 | console.log('Configuration: ' + host + ':' + port) | ||
76 | console.log('Connecting to IPC...') | ||
77 | |||
78 | ipc.connectTo(nodeKey, function () { | ||
79 | ipc.of[nodeKey].on(processKey + '.seed', seed) | ||
80 | ipc.of[nodeKey].on(processKey + '.add', add) | ||
81 | ipc.of[nodeKey].on(processKey + '.remove', remove) | ||
82 | |||
83 | ipc.of[nodeKey].emit(processKey + '.ready') | ||
84 | console.log('Ready.') | ||
85 | }) | ||
86 | } | ||
87 | })() | ||