git ssb

5+

Matt McKegg / ferment



Tree: 13868e1aa6a7204af20406c760dac884fd24fdf1

Files: 13868e1aa6a7204af20406c760dac884fd24fdf1 / background-window.js

12638 bytesRaw
1var WebTorrent = require('webtorrent')
2var electron = require('electron')
3var parseTorrent = require('parse-torrent')
4var Path = require('path')
5var getExt = require('path').extname
6var fs = require('fs')
7var ipc = electron.ipcRenderer
8var watchEvent = require('./lib/watch-event')
9var rimraf = require('rimraf')
10var MutantDict = require('@mmckegg/mutant/dict')
11var MutantStruct = require('@mmckegg/mutant/struct')
12var convert = require('./lib/convert')
13var TorrentStatus = require('./models/torrent-status')
14var Tracker = require('bittorrent-tracker')
15var magnet = require('magnet-uri')
16var pull = require('pull-stream')
17
18console.log = electron.remote.getGlobal('console').log
19process.exit = electron.remote.app.quit
20// redirect errors to stderr
21window.addEventListener('error', function (e) {
22 e.preventDefault()
23 console.error(e.error.stack || 'Uncaught ' + e.error)
24})
25
26module.exports = function (client, config) {
27 var seedWhiteList = new Set(config.seedWhiteList ? [].concat(config.seedWhiteList) : [client.id])
28 var maxSeed = config.maxSeed == null ? 15 : parseInt(config.maxSeed, 10)
29 var seedInterval = config.seedInterval == null ? 15 : parseInt(config.seedInterval, 10)
30
31 var announce = config.webtorrent.announceList
32 var torrentClient = new WebTorrent()
33 var mediaPath = config.mediaPath
34 var releases = {}
35 var prioritizeReleases = []
36 var paused = []
37
38 var allTorrentStats = MutantStruct({
39 downloadSpeed: 0,
40 uploadSpeed: 0
41 }, {nextTick: true})
42
43 var torrentState = MutantDict()
44
45 setInterval(pollStats, 0.5 * 1000)
46 setInterval(scrapeInfo, 30 * 1000)
47 setInterval(seedRarest, 30 * 60 * 1000)
48
49 seedRarest()
50 startAutoSeed()
51
52 torrentClient.on('torrent', function (torrent) {
53 watchTorrent(torrent.infoHash)
54 })
55
56 ipc.on('bg-release', function (ev, id) {
57 if (releases[id]) {
58 var release = releases[id]
59 releases[id] = null
60 release()
61 }
62 })
63
64 ipc.on('bg-stream-torrent', (ev, id, torrentId) => {
65 unprioritize(true, () => {
66 var torrent = torrentClient.get(torrentId)
67 if (torrent) {
68 streamTorrent(id, torrentId)
69 } else {
70 addTorrent(torrentId, () => {
71 streamTorrent(id, torrentId)
72 })
73 }
74 })
75
76 function streamTorrent (id, torrentId) {
77 var torrent = torrentClient.get(torrentId)
78 var server = torrent.createServer()
79 prioritize(torrentId)
80 server.listen(0, function (err) {
81 if (err) return ipc.send('bg-response', id, err)
82 var port = server.address().port
83 var url = 'http://localhost:' + port + '/0'
84 ipc.send('bg-response', id, null, url)
85 })
86 releases[id] = () => {
87 server.close()
88 }
89 }
90 })
91
92 ipc.on('bg-export-torrent', (ev, id, torrentId, filePath) => {
93 unprioritize(true, () => {
94 var torrent = torrentClient.get(torrentId)
95 if (torrent) {
96 saveFile(id, torrentId, filePath)
97 } else {
98 addTorrent(torrentId, () => {
99 saveFile(id, torrentId, filePath)
100 })
101 }
102 })
103
104 function saveFile (id, torrentId, exportPath) {
105 var torrent = torrentClient.get(torrentId)
106 torrentState.get(torrent.infoHash).saving.set(true)
107 if (torrent.progress === 1) {
108 done()
109 } else {
110 torrent.once('done', done)
111 }
112
113 function done () {
114 var originalPath = Path.join(getTorrentDataPath(torrent.infoHash), torrent.files[0].path)
115 convert.export(originalPath, exportPath, (err, info) => {
116 torrentState.get(torrent.infoHash).saving.set(false)
117 ipc.send('bg-response', id, err, info)
118 console.log(info.toString())
119 })
120 }
121 }
122 })
123
124 ipc.on('bg-check-torrent', (ev, id, torrentId) => {
125 var torrent = torrentClient.get(torrentId)
126 if (torrent) {
127 ipc.send('bg-response', id, null)
128 } else {
129 addTorrent(torrentId, (err) => {
130 ipc.send('bg-response', id, err)
131 })
132 }
133 })
134
135 ipc.on('bg-get-all-torrent-state', (ev, id) => {
136 ipc.send('bg-response', id, torrentState())
137 })
138
139 ipc.on('bg-delete-torrent', (ev, id, torrentId) => {
140 var torrentInfo = parseTorrent(torrentId)
141 var torrent = torrentClient.get(torrentInfo.infoHash)
142 if (torrent) {
143 torrent.destroy()
144 }
145
146 fs.unlink(getTorrentPath(torrentInfo.infoHash), function () {
147 rimraf(getTorrentDataPath(torrentInfo.infoHash), function () {
148 console.log('Deleted torrent', torrentInfo.infoHash)
149 ipc.send('bg-response', id)
150 })
151 })
152 })
153
154 ipc.on('bg-seed-torrent', (ev, id, infoHash) => {
155 var torrent = torrentClient.get(infoHash)
156 if (torrent) {
157 ipc.send('bg-response', id, null, torrent.magnetURI)
158 } else {
159 fs.readFile(getTorrentPath(infoHash), function (err, buffer) {
160 if (err) return ipc.send('bg-response', id, err)
161 var torrent = parseTorrent(buffer)
162 torrent.announce = announce.slice()
163 torrentClient.add(torrent, {
164 path: getTorrentDataPath(infoHash)
165 }, function (torrent) {
166 ipc.send('bg-response', id, null, torrent.magnetURI)
167 })
168 })
169 }
170 })
171
172 ipc.send('ipcBackgroundReady', true)
173
174 // scoped
175
176 function watchTorrent (infoHash) {
177 if (!torrentState.has(infoHash)) {
178 var state = TorrentStatus(infoHash)
179 torrentState.put(infoHash, state)
180 state(function (value) {
181 ipc.send('bg-torrent-status', infoHash, value)
182 })
183 }
184 }
185
186 function scrapeInfo () {
187 var keys = torrentState.keys()
188 getTorrentInfo(keys, (err, info) => {
189 if (err) return console.log(err)
190 Object.keys(info).forEach((key) => {
191 var state = torrentState.get(key)
192 if (state) {
193 state.complete.set(info[key].complete)
194 }
195 })
196 })
197 }
198
199 function scrapeInfoFor (infoHash) {
200 getTorrentInfo(infoHash, (err, info) => {
201 if (err) return console.log(err)
202 var state = torrentState.get(infoHash)
203 if (state && info) {
204 state.complete.set(info.complete)
205 }
206 })
207 }
208
209 function pollStats () {
210 torrentState.keys().forEach(refreshTorrentState)
211 allTorrentStats.downloadSpeed.set(torrentClient.downloadSpeed)
212 allTorrentStats.uploadSpeed.set(torrentClient.uploadSpeed)
213 }
214
215 function refreshTorrentState (infoHash) {
216 var torrent = torrentClient.get(infoHash)
217 var state = torrentState.get(infoHash)
218 if (torrent) {
219 state.progress.set(torrent.progress)
220 state.downloadSpeed.set(torrent.downloadSpeed)
221 state.uploadSpeed.set(torrent.uploadSpeed)
222 state.uploaded.set(torrent.uploaded)
223 state.downloaded.set(torrent.downloaded)
224 state.numPeers.set(torrent.numPeers)
225 state.seeding.set(true)
226 state.loading.set(false)
227 } else {
228 state.seeding.set(false)
229 }
230 }
231
232 function getTorrentPath (infoHash) {
233 return `${getTorrentDataPath(infoHash)}.torrent`
234 }
235
236 function getTorrentDataPath (infoHash) {
237 return Path.join(mediaPath, `${infoHash}`)
238 }
239
240 function addTorrent (torrentId, cb) {
241 var torrent = parseTorrent(torrentId)
242 var torrentPath = getTorrentPath(torrent.infoHash)
243 torrent.announce = announce.slice()
244
245 watchTorrent(torrent.infoHash)
246
247 if (torrentClient.get(torrent.infoHash)) {
248 cb()
249 } else {
250 torrentState.get(torrent.infoHash).loading.set(true)
251 fs.exists(torrentPath, (exists) => {
252 torrentClient.add(exists ? torrentPath : torrent, {
253 path: getTorrentDataPath(torrent.infoHash),
254 announce
255 }, function (torrent) {
256 scrapeInfoFor(torrent.infoHash)
257 console.log('add torrent', torrent.infoHash)
258 if (!exists) fs.writeFile(torrentPath, torrent.torrentFile, cb)
259 else cb()
260 })
261 })
262 }
263 }
264
265 function getTorrentInfo (infoHashes, cb) {
266 if (infoHashes && infoHashes.length) {
267 Tracker.scrape({
268 announce: announce[0],
269 infoHash: infoHashes
270 }, function (err, info) {
271 if (err) return cb(err)
272 if (Array.isArray(infoHashes) && infoHashes.length === 1) info = {[info.infoHash]: info}
273 cb(null, info)
274 })
275 } else {
276 cb(null, {})
277 }
278 }
279
280 function startAutoSeed () {
281 client.friends.all((err, graph) => {
282 if (err) throw err
283
284 var extendedList = new Set(seedWhiteList)
285 Array.from(seedWhiteList).forEach((id) => {
286 var moreIds = graph[id]
287 if (moreIds) {
288 Object.keys(moreIds).forEach(x => extendedList.add(x))
289 }
290 })
291
292 console.log(`Seeding torrents from: \n - ${Array.from(extendedList).join('\n - ')}`)
293
294 pull(
295 client.createLogStream({ live: true, gt: Date.now() }),
296 ofType(['ferment/audio', 'ferment/update']),
297 pull.drain((item) => {
298 if (item.value && typeof item.value.content.audioSrc === 'string') {
299 var torrent = magnet.decode(item.value.content.audioSrc)
300 if (torrent.infoHash) {
301 if (extendedList.has(item.value.author)) {
302 fs.exists(Path.join(config.mediaPath, torrent.infoHash + '.torrent'), (exists) => {
303 if (!exists) {
304 if (!torrentClient.get(torrent.infoHash)) {
305 addTorrent(torrent)
306 console.log(`Auto seeding torrent ${torrent.infoHash}`)
307 }
308 }
309 })
310 }
311 }
312 }
313 })
314 )
315 })
316 }
317
318 function seedRarest () {
319 var i = 0
320 var items = []
321 var localTorrents = []
322 fs.readdir(mediaPath, function (err, entries) {
323 if (err) throw err
324 entries.forEach((name) => {
325 if (getExt(name) === '.torrent') {
326 localTorrents.push(Path.basename(name, '.torrent'))
327 }
328 })
329
330 // seed rarest torrents first
331 getTorrentInfo(localTorrents, (err, info) => {
332 if (err) return console.log(err)
333 localTorrents.map(infoHash => [infoHash, info[infoHash].complete]).sort((a, b) => {
334 return (a[1] + Math.random()) - (b[1] + Math.random())
335 }).slice(0, maxSeed).forEach((item) => {
336 watchTorrent(item[0])
337 torrentState.get(item[0]).complete.set(item[1])
338 items.push(Path.join(mediaPath, `${item[0]}.torrent`))
339 })
340 if (items.length) {
341 next()
342 }
343 })
344 })
345
346 function next () {
347 // don't seed all of the torrents at once, roll out slowly to avoid cpu spike
348 var item = items[i]
349 setTimeout(function () {
350 fs.readFile(item, function (err, buffer) {
351 if (!err) {
352 var torrent = parseTorrent(buffer)
353 if (!torrentClient.get(torrent.infoHash)) {
354 torrent.announce = announce.slice()
355 torrentClient.add(torrent, {
356 path: getTorrentDataPath(Path.basename(item, '.torrent'))
357 }, function (torrent) {
358 console.log('seeding', torrent.infoHash)
359 i += 1
360 if (i < items.length) next()
361 })
362 } else {
363 next()
364 }
365 }
366 })
367 // wait before seeding next file
368 }, seedInterval * 1000)
369 }
370 }
371
372 function unprioritize (restart, cb) {
373 while (prioritizeReleases.length) {
374 prioritizeReleases.pop()()
375 }
376
377 if (paused.length && restart) {
378 var remaining = paused.length
379 console.log(`restarting ${paused.length} torrent(s)`)
380 while (paused.length) {
381 var torrentFile = paused.pop()
382 var torrent = parseTorrent(torrentFile)
383 torrentClient.add(torrent, { path: getTorrentDataPath(torrent.infoHash), announce }, (torrent) => {
384 remaining -= 1
385 if (remaining === 0) {
386 cb && cb()
387 }
388 })
389 }
390 } else {
391 cb && cb()
392 }
393 }
394
395 function prioritize (torrentId) {
396 var torrent = torrentClient.get(torrentId)
397 torrent.critical(0, Math.floor(torrent.pieces.length / 8))
398 if (torrent.progress < 0.5) {
399 torrentClient.torrents.forEach(function (t) {
400 if (t !== torrent && t.progress < 0.9) {
401 paused.push(t.torrentFile)
402 t.destroy()
403 }
404 })
405
406 console.log(`pausing ${paused.length} torrent(s)`)
407
408 prioritizeReleases.push(watchEvent(torrent, 'download', () => {
409 if (torrent.progress > 0.8) {
410 unprioritize(true)
411 }
412 }))
413 }
414 }
415}
416
417function ofType (types) {
418 types = Array.isArray(types) ? types : [types]
419 return pull.filter((item) => {
420 if (item.value) {
421 return types.includes(item.value.content.type)
422 } else {
423 return true
424 }
425 })
426}
427

Built with git-ssb-web