webtorrent.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import { logger } from './logger'
  2. import { generateVideoImportTmpPath } from './utils'
  3. import * as WebTorrent from 'webtorrent'
  4. import { createWriteStream, ensureDir, remove } from 'fs-extra'
  5. import { CONFIG } from '../initializers/config'
  6. import { dirname, join } from 'path'
  7. async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName?: string }, timeout: number) {
  8. const id = target.magnetUri || target.torrentName
  9. let timer
  10. const path = generateVideoImportTmpPath(id)
  11. logger.info('Importing torrent video %s', id)
  12. const directoryPath = join(CONFIG.STORAGE.TMP_DIR, 'webtorrent')
  13. await ensureDir(directoryPath)
  14. return new Promise<string>((res, rej) => {
  15. const webtorrent = new WebTorrent()
  16. let file: WebTorrent.TorrentFile
  17. const torrentId = target.magnetUri || join(CONFIG.STORAGE.TORRENTS_DIR, target.torrentName)
  18. const options = { path: directoryPath }
  19. const torrent = webtorrent.add(torrentId, options, torrent => {
  20. if (torrent.files.length !== 1) {
  21. if (timer) clearTimeout(timer)
  22. for (let file of torrent.files) {
  23. deleteDownloadedFile({ directoryPath, filepath: file.path })
  24. }
  25. return safeWebtorrentDestroy(webtorrent, torrentId, undefined, target.torrentName)
  26. .then(() => rej(new Error('Cannot import torrent ' + torrentId + ': there are multiple files in it')))
  27. }
  28. file = torrent.files[ 0 ]
  29. // FIXME: avoid creating another stream when https://github.com/webtorrent/webtorrent/issues/1517 is fixed
  30. const writeStream = createWriteStream(path)
  31. writeStream.on('finish', () => {
  32. if (timer) clearTimeout(timer)
  33. return safeWebtorrentDestroy(webtorrent, torrentId, { directoryPath, filepath: file.path }, target.torrentName)
  34. .then(() => res(path))
  35. })
  36. file.createReadStream().pipe(writeStream)
  37. })
  38. torrent.on('error', err => rej(err))
  39. timer = setTimeout(async () => {
  40. return safeWebtorrentDestroy(webtorrent, torrentId, file ? { directoryPath, filepath: file.path } : undefined, target.torrentName)
  41. .then(() => rej(new Error('Webtorrent download timeout.')))
  42. }, timeout)
  43. })
  44. }
  45. // ---------------------------------------------------------------------------
  46. export {
  47. downloadWebTorrentVideo
  48. }
  49. // ---------------------------------------------------------------------------
  50. function safeWebtorrentDestroy (
  51. webtorrent: WebTorrent.Instance,
  52. torrentId: string,
  53. downloadedFile?: { directoryPath: string, filepath: string },
  54. torrentName?: string
  55. ) {
  56. return new Promise(res => {
  57. webtorrent.destroy(err => {
  58. // Delete torrent file
  59. if (torrentName) {
  60. logger.debug('Removing %s torrent after webtorrent download.', torrentId)
  61. remove(torrentId)
  62. .catch(err => logger.error('Cannot remove torrent %s in webtorrent download.', torrentId, { err }))
  63. }
  64. // Delete downloaded file
  65. if (downloadedFile) deleteDownloadedFile(downloadedFile)
  66. if (err) logger.warn('Cannot destroy webtorrent in timeout.', { err })
  67. return res()
  68. })
  69. })
  70. }
  71. function deleteDownloadedFile (downloadedFile: { directoryPath: string, filepath: string }) {
  72. // We want to delete the base directory
  73. let pathToDelete = dirname(downloadedFile.filepath)
  74. if (pathToDelete === '.') pathToDelete = downloadedFile.filepath
  75. const toRemovePath = join(downloadedFile.directoryPath, pathToDelete)
  76. logger.debug('Removing %s after webtorrent download.', toRemovePath)
  77. remove(toRemovePath)
  78. .catch(err => logger.error('Cannot remove torrent file %s in webtorrent download.', toRemovePath, { err }))
  79. }