]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Import torrents with webtorrent
authorChocobozzz <me@florianbigard.com>
Tue, 7 Aug 2018 07:54:36 +0000 (09:54 +0200)
committerChocobozzz <me@florianbigard.com>
Wed, 8 Aug 2018 07:30:31 +0000 (09:30 +0200)
16 files changed:
client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html
client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html
client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.scss
client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts
client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts
client/src/app/videos/+video-edit/video-add.component.scss
server/controllers/api/videos/import.ts
server/helpers/core-utils.ts
server/helpers/custom-validators/video-imports.ts
server/helpers/utils.ts
server/helpers/webtorrent.ts
server/initializers/constants.ts
server/lib/job-queue/handlers/video-import.ts
server/middlewares/validators/video-imports.ts
server/models/video/video-import.ts
shared/models/videos/video-import.model.ts

index 00b2d7cb0a810c60193d894bd591030f09ef4c47..b2b6c3d609ba58c410bfb036ec87c84c9d18dfbe 100644 (file)
@@ -5,7 +5,7 @@
   <ng-template pTemplate="header">
     <tr>
       <th style="width: 40px;"></th>
-      <th i18n>URL</th>
+      <th i18n>Target</th>
       <th i18n>Video</th>
       <th i18n style="width: 150px">State</th>
       <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
       </td>
 
       <td>
-        <a [href]="videoImport.targetUrl" target="_blank" rel="noopener noreferrer">{{ videoImport.targetUrl }}</a>
+        <a *ngIf="videoImport.targetUrl; else torrent" [href]="videoImport.targetUrl" target="_blank" rel="noopener noreferrer">{{ videoImport.targetUrl }}</a>
+        <ng-template #torrent>
+          <span [title]="videoImport.torrentName || videoImport.magnetUri">{{ videoImport.torrentName || videoImport.magnetUri }}</span>
+        </ng-template>
       </td>
 
       <td *ngIf="isVideoImportPending(videoImport)">
index 409e4de5eae43182ee115a76060e0bbbbab8e8a8..2f0c9abb58933272d2eeaca05b5903a25b0d024f 100644 (file)
@@ -2,8 +2,16 @@
   <div class="import-video-torrent">
     <div class="icon icon-upload"></div>
 
-    <div class="form-group">
-      <label i18n for="magnetUri">Magnet URI</label>
+    <div class="button-file">
+      <span i18n>Select the torrent to import</span>
+      <input #torrentfileInput type="file" name="torrentfile" id="torrentfile" accept=".torrent" (change)="fileChange()" />
+    </div>
+    <span class="button-file-extension">(.torrent)</span>
+
+    <div class="torrent-or-magnet">Or</div>
+
+    <div class="form-group form-group-magnet-uri">
+      <label i18n for="magnetUri">Paste magnet URI</label>
       <my-help
         helpType="custom" i18n-customHtml
         customHtml="You can import any torrent file that points to a mp4 file. You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance."
index 1ef5adc25844a88b8c27aad6cee7496f5466c199..262b0b68e513c17cc999c23ca939924593f6019d 100644 (file)
@@ -20,6 +20,26 @@ $width-size: 190px;
     background-image: url('../../../../assets/images/video/upload.svg');
   }
 
+  .button-file {
+    @include peertube-button-file(auto);
+
+    min-width: 190px;
+  }
+
+  .button-file-extension {
+    display: block;
+    font-size: 12px;
+    margin-top: 5px;
+  }
+
+  .torrent-or-magnet {
+    margin: 10px 0;
+  }
+
+  .form-group-magnet-uri {
+    margin-bottom: 40px;
+  }
+
   input[type=text] {
     @include peertube-input-text($width-size);
     display: block;
index 330c37718bbe0b48ff69b14e2306065b44696a04..9623c2bf4f19a65aed8e9641c8f252a3239e3e02 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, EventEmitter, OnInit, Output } from '@angular/core'
+import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
 import { Router } from '@angular/router'
 import { NotificationsService } from 'angular2-notifications'
 import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos'
@@ -23,6 +23,7 @@ import { VideoImportService } from '@app/shared/video-import'
 })
 export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate {
   @Output() firstStepDone = new EventEmitter<string>()
+  @ViewChild('torrentfileInput') torrentfileInput
 
   videoFileName: string
   magnetUri = ''
@@ -33,7 +34,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca
 
   video: VideoEdit
 
-  protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PRIVATE
+  protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC
 
   constructor (
     protected formValidatorService: FormValidatorService,
@@ -62,7 +63,14 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca
     return !!this.magnetUri
   }
 
-  importVideo () {
+  fileChange () {
+    const torrentfile = this.torrentfileInput.nativeElement.files[0] as File
+    if (!torrentfile) return
+
+    this.importVideo(torrentfile)
+  }
+
+  importVideo (torrentfile?: Blob) {
     this.isImportingVideo = true
 
     const videoUpdate: VideoUpdate = {
@@ -74,7 +82,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca
 
     this.loadingBar.start()
 
-    this.videoImportService.importVideoTorrent(this.magnetUri, videoUpdate).subscribe(
+    this.videoImportService.importVideoTorrent(torrentfile || this.magnetUri, videoUpdate).subscribe(
       res => {
         this.loadingBar.complete()
         this.firstStepDone.emit(res.video.name)
index 842ede7322ff6cbc43dea78a743080ca8a4f559d..97b402bfe4cebf305a251726f88de9142a970fea 100644 (file)
@@ -33,7 +33,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom
 
   video: VideoEdit
 
-  protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PRIVATE
+  protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC
 
   constructor (
     protected formValidatorService: FormValidatorService,
index 02ee295f951562933d0e05640fe695203bbf0e4b..443361f502d6febced7c90ce3184487953bb62e6 100644 (file)
@@ -49,7 +49,7 @@ $background-color:  #F7F7F7;
     background-color: $background-color;
     border-radius: 3px;
     width: 100%;
-    height: 440px;
+    min-height: 440px;
     display: flex;
     justify-content: center;
     align-items: center;
index c16a254d2505a82659abda0f8710a555f5912fc5..df151e79d7026abda921b1d57ceb97baa9490b2d 100644 (file)
@@ -1,8 +1,16 @@
-import * as magnetUtil from 'magnet-uri'
 import * as express from 'express'
+import * as magnetUtil from 'magnet-uri'
+import 'multer'
 import { auditLoggerFactory, VideoImportAuditView } from '../../../helpers/audit-logger'
 import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
-import { CONFIG, IMAGE_MIMETYPE_EXT, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers'
+import {
+  CONFIG,
+  IMAGE_MIMETYPE_EXT,
+  PREVIEWS_SIZE,
+  sequelizeTypescript,
+  THUMBNAILS_SIZE,
+  TORRENT_MIMETYPE_EXT
+} from '../../../initializers'
 import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl'
 import { createReqFiles } from '../../../helpers/express-utils'
 import { logger } from '../../../helpers/logger'
@@ -18,16 +26,20 @@ import { isArray } from '../../../helpers/custom-validators/misc'
 import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
 import { VideoChannelModel } from '../../../models/video/video-channel'
 import * as Bluebird from 'bluebird'
+import * as parseTorrent from 'parse-torrent'
+import { readFileBufferPromise, renamePromise } from '../../../helpers/core-utils'
+import { getSecureTorrentName } from '../../../helpers/utils'
 
 const auditLogger = auditLoggerFactory('video-imports')
 const videoImportsRouter = express.Router()
 
 const reqVideoFileImport = createReqFiles(
-  [ 'thumbnailfile', 'previewfile' ],
-  IMAGE_MIMETYPE_EXT,
+  [ 'thumbnailfile', 'previewfile', 'torrentfile' ],
+  Object.assign({}, TORRENT_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT),
   {
     thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
-    previewfile: CONFIG.STORAGE.PREVIEWS_DIR
+    previewfile: CONFIG.STORAGE.PREVIEWS_DIR,
+    torrentfile: CONFIG.STORAGE.TORRENTS_DIR
   }
 )
 
@@ -49,17 +61,37 @@ export {
 function addVideoImport (req: express.Request, res: express.Response) {
   if (req.body.targetUrl) return addYoutubeDLImport(req, res)
 
-  if (req.body.magnetUri) return addTorrentImport(req, res)
+  const file = req.files['torrentfile'][0]
+  if (req.body.magnetUri || file) return addTorrentImport(req, res, file)
 }
 
-async function addTorrentImport (req: express.Request, res: express.Response) {
+async function addTorrentImport (req: express.Request, res: express.Response, torrentfile: Express.Multer.File) {
   const body: VideoImportCreate = req.body
-  const magnetUri = body.magnetUri
 
-  const parsed = magnetUtil.decode(magnetUri)
-  const magnetName = isArray(parsed.name) ? parsed.name[0] : parsed.name as string
+  let videoName: string
+  let torrentName: string
+  let magnetUri: string
+
+  if (torrentfile) {
+    torrentName = torrentfile.originalname
+
+    // Rename the torrent to a secured name
+    const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, getSecureTorrentName(torrentName))
+    await renamePromise(torrentfile.path, newTorrentPath)
+    torrentfile.path = newTorrentPath
+
+    const buf = await readFileBufferPromise(torrentfile.path)
+    const parsedTorrent = parseTorrent(buf)
+
+    videoName = isArray(parsedTorrent.name) ? parsedTorrent.name[ 0 ] : parsedTorrent.name as string
+  } else {
+    magnetUri = body.magnetUri
+
+    const parsed = magnetUtil.decode(magnetUri)
+    videoName = isArray(parsed.name) ? parsed.name[ 0 ] : parsed.name as string
+  }
 
-  const video = buildVideo(res.locals.videoChannel.id, body, { name: magnetName })
+  const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName })
 
   await processThumbnail(req, video)
   await processPreview(req, video)
@@ -67,13 +99,14 @@ async function addTorrentImport (req: express.Request, res: express.Response) {
   const tags = null
   const videoImportAttributes = {
     magnetUri,
+    torrentName,
     state: VideoImportState.PENDING
   }
   const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes)
 
   // Create job to import the video
   const payload = {
-    type: 'magnet-uri' as 'magnet-uri',
+    type: torrentfile ? 'torrent-file' as 'torrent-file' : 'magnet-uri' as 'magnet-uri',
     videoImportId: videoImport.id,
     magnetUri
   }
index 884206aad0c4043830bf4e168346519e9bb97994..25eb6454ae52f1dbef6694decc8daa9cf1f75368 100644 (file)
@@ -13,6 +13,7 @@ import * as pem from 'pem'
 import * as rimraf from 'rimraf'
 import { URL } from 'url'
 import { truncate } from 'lodash'
+import * as crypto from 'crypto'
 
 function sanitizeUrl (url: string) {
   const urlObject = new URL(url)
@@ -95,6 +96,10 @@ function peertubeTruncate (str: string, maxLength: number) {
   return truncate(str, options)
 }
 
+function sha256 (str: string) {
+  return crypto.createHash('sha256').update(str).digest('hex')
+}
+
 function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
   return function promisified (): Promise<A> {
     return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
@@ -165,6 +170,7 @@ export {
   sanitizeHost,
   buildPath,
   peertubeTruncate,
+  sha256,
 
   promisify0,
   promisify1,
index d8b9bfaff09ba6274e00e79975a0ba6536192671..4d6ab1fa41cf6578300bcb45292808f56ce281cf 100644 (file)
@@ -1,10 +1,9 @@
 import 'express-validator'
 import 'multer'
 import * as validator from 'validator'
-import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers'
-import { exists } from './misc'
+import { CONSTRAINTS_FIELDS, TORRENT_MIMETYPE_EXT, VIDEO_IMPORT_STATES } from '../../initializers'
+import { exists, isFileValid } from './misc'
 import * as express from 'express'
-import { VideoChannelModel } from '../../models/video/video-channel'
 import { VideoImportModel } from '../../models/video/video-import'
 
 function isVideoImportTargetUrlValid (url: string) {
@@ -25,6 +24,12 @@ function isVideoImportStateValid (value: any) {
   return exists(value) && VIDEO_IMPORT_STATES[ value ] !== undefined
 }
 
+const videoTorrentImportTypes = Object.keys(TORRENT_MIMETYPE_EXT).map(m => `(${m})`)
+const videoTorrentImportRegex = videoTorrentImportTypes.join('|')
+function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
+  return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true)
+}
+
 async function isVideoImportExist (id: number, res: express.Response) {
   const videoImport = await VideoImportModel.loadAndPopulateVideo(id)
 
@@ -45,5 +50,6 @@ async function isVideoImportExist (id: number, res: express.Response) {
 export {
   isVideoImportStateValid,
   isVideoImportTargetUrlValid,
-  isVideoImportExist
+  isVideoImportExist,
+  isVideoImportTorrentFile
 }
index f4cc5547d49ddacbb1b57f4d528c13a6b584fabf..2ad87951e7b53dbcad6fdabe8edeabf458551508 100644 (file)
@@ -6,11 +6,12 @@ import { CONFIG } from '../initializers'
 import { UserModel } from '../models/account/user'
 import { ActorModel } from '../models/activitypub/actor'
 import { ApplicationModel } from '../models/application/application'
-import { pseudoRandomBytesPromise, unlinkPromise } from './core-utils'
+import { pseudoRandomBytesPromise, sha256, unlinkPromise } from './core-utils'
 import { logger } from './logger'
 import { isArray } from './custom-validators/misc'
 import * as crypto from "crypto"
 import { join } from "path"
+import { Instance as ParseTorrent } from 'parse-torrent'
 
 const isCidr = require('is-cidr')
 
@@ -183,13 +184,18 @@ async function getServerActor () {
   return Promise.resolve(serverActor)
 }
 
-function generateVideoTmpPath (id: string) {
-  const hash = crypto.createHash('sha256').update(id).digest('hex')
+function generateVideoTmpPath (target: string | ParseTorrent) {
+  const id = typeof target === 'string' ? target : target.infoHash
+
+  const hash = sha256(id)
   return join(CONFIG.STORAGE.VIDEOS_DIR, hash + '-import.mp4')
 }
 
-type SortType = { sortModel: any, sortValue: string }
+function getSecureTorrentName (originalName: string) {
+  return sha256(originalName) + '.torrent'
+}
 
+type SortType = { sortModel: any, sortValue: string }
 
 // ---------------------------------------------------------------------------
 
@@ -199,6 +205,7 @@ export {
   generateRandomString,
   getFormattedObjects,
   isSignupAllowed,
+  getSecureTorrentName,
   isSignupAllowedForCurrentIP,
   computeResolutionsToTranscode,
   resetSequelizeInstance,
index fce88a1f64f5356e736516d4fa13a7ed7225ae95..04b3ac71b57f4cc4ec51444ed555ad9ccad4850e 100644 (file)
@@ -2,17 +2,22 @@ import { logger } from './logger'
 import { generateVideoTmpPath } from './utils'
 import * as WebTorrent from 'webtorrent'
 import { createWriteStream } from 'fs'
+import { Instance as ParseTorrent } from 'parse-torrent'
+import { CONFIG } from '../initializers'
+import { join } from 'path'
 
-function downloadWebTorrentVideo (target: string) {
-  const path = generateVideoTmpPath(target)
+function downloadWebTorrentVideo (target: { magnetUri: string, torrentName: string }) {
+  const id = target.magnetUri || target.torrentName
 
-  logger.info('Importing torrent video %s', target)
+  const path = generateVideoTmpPath(id)
+  logger.info('Importing torrent video %s', id)
 
   return new Promise<string>((res, rej) => {
     const webtorrent = new WebTorrent()
 
-    const torrent = webtorrent.add(target, torrent => {
-      if (torrent.files.length !== 1) throw new Error('The number of files is not equal to 1 for ' + target)
+    const torrentId = target.magnetUri || join(CONFIG.STORAGE.TORRENTS_DIR, target.torrentName)
+    const torrent = webtorrent.add(torrentId, torrent => {
+      if (torrent.files.length !== 1) return rej(new Error('The number of files is not equal to 1 for ' + torrentId))
 
       const file = torrent.files[ 0 ]
       file.createReadStream().pipe(createWriteStream(path))
index 243d544eab47544ab7120c3f4893c39f85bd80bb..cf7cd3d74ee661c99269a6e92558e13c64922362 100644 (file)
@@ -273,6 +273,12 @@ const CONSTRAINTS_FIELDS = {
   VIDEO_IMPORTS: {
     URL: { min: 3, max: 2000 }, // Length
     TORRENT_NAME: { min: 3, max: 255 }, // Length
+    TORRENT_FILE: {
+      EXTNAME: [ '.torrent' ],
+      FILE_SIZE: {
+        max: 1024 * 200 // 200 KB
+      }
+    }
   },
   VIDEOS: {
     NAME: { min: 3, max: 120 }, // Length
@@ -417,6 +423,10 @@ const VIDEO_CAPTIONS_MIMETYPE_EXT = {
   'application/x-subrip': '.srt'
 }
 
+const TORRENT_MIMETYPE_EXT = {
+  'application/x-bittorrent': '.torrent'
+}
+
 // ---------------------------------------------------------------------------
 
 const SERVER_ACTOR_NAME = 'peertube'
@@ -596,6 +606,7 @@ export {
   FEEDS,
   JOB_TTL,
   NSFW_POLICY_TYPES,
+  TORRENT_MIMETYPE_EXT,
   STATIC_MAX_AGE,
   STATIC_PATHS,
   ACTIVITY_PUB,
index c457b71fc8d21294f06839d4ffdeebf175d26176..fd61aecad6bc1e2242fb7aa8c48f754acd211cee 100644 (file)
@@ -14,6 +14,7 @@ import { JobQueue } from '../index'
 import { federateVideoIfNeeded } from '../../activitypub'
 import { VideoModel } from '../../../models/video/video'
 import { downloadWebTorrentVideo } from '../../../helpers/webtorrent'
+import { getSecureTorrentName } from '../../../helpers/utils'
 
 type VideoImportYoutubeDLPayload = {
   type: 'youtube-dl'
@@ -25,7 +26,7 @@ type VideoImportYoutubeDLPayload = {
 }
 
 type VideoImportTorrentPayload = {
-  type: 'magnet-uri'
+  type: 'magnet-uri' | 'torrent-file'
   videoImportId: number
 }
 
@@ -35,7 +36,7 @@ async function processVideoImport (job: Bull.Job) {
   const payload = job.data as VideoImportPayload
 
   if (payload.type === 'youtube-dl') return processYoutubeDLImport(job, payload)
-  if (payload.type === 'magnet-uri') return processTorrentImport(job, payload)
+  if (payload.type === 'magnet-uri' || payload.type === 'torrent-file') return processTorrentImport(job, payload)
 }
 
 // ---------------------------------------------------------------------------
@@ -50,6 +51,7 @@ async function processTorrentImport (job: Bull.Job, payload: VideoImportTorrentP
   logger.info('Processing torrent video import in job %d.', job.id)
 
   const videoImport = await getVideoImportOrDie(payload.videoImportId)
+
   const options = {
     videoImportId: payload.videoImportId,
 
@@ -59,7 +61,11 @@ async function processTorrentImport (job: Bull.Job, payload: VideoImportTorrentP
     generateThumbnail: true,
     generatePreview: true
   }
-  return processFile(() => downloadWebTorrentVideo(videoImport.magnetUri), videoImport, options)
+  const target = {
+    torrentName: videoImport.torrentName ? getSecureTorrentName(videoImport.torrentName) : undefined,
+    magnetUri: videoImport.magnetUri
+  }
+  return processFile(() => downloadWebTorrentVideo(target), videoImport, options)
 }
 
 async function processYoutubeDLImport (job: Bull.Job, payload: VideoImportYoutubeDLPayload) {
index 8ec9373fb71e3a897b35741a299316e8745c321f..c03cf2e4d6c4146de56c797452941f02627ccbfc 100644 (file)
@@ -4,10 +4,11 @@ import { isIdValid } from '../../helpers/custom-validators/misc'
 import { logger } from '../../helpers/logger'
 import { areValidationErrors } from './utils'
 import { getCommonVideoAttributes } from './videos'
-import { isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports'
+import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../helpers/custom-validators/video-imports'
 import { cleanUpReqFiles } from '../../helpers/utils'
 import { isVideoChannelOfAccountExist, isVideoMagnetUriValid, isVideoNameValid } from '../../helpers/custom-validators/videos'
 import { CONFIG } from '../../initializers/constants'
+import { CONSTRAINTS_FIELDS } from '../../initializers'
 
 const videoImportAddValidator = getCommonVideoAttributes().concat([
   body('channelId')
@@ -19,6 +20,11 @@ const videoImportAddValidator = getCommonVideoAttributes().concat([
   body('magnetUri')
     .optional()
     .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'),
+  body('torrentfile')
+    .custom((value, { req }) => isVideoImportTorrentFile(req.files)).withMessage(
+    'This torrent file is not supported or too large. Please, make sure it is of the following type: '
+    + CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ')
+  ),
   body('name')
     .optional()
     .custom(isVideoNameValid).withMessage('Should have a valid name'),
@@ -40,11 +46,12 @@ const videoImportAddValidator = getCommonVideoAttributes().concat([
     if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
 
     // Check we have at least 1 required param
-    if (!req.body.targetUrl && !req.body.magnetUri) {
+    const file = req.files['torrentfile'][0]
+    if (!req.body.targetUrl && !req.body.magnetUri && !file) {
       cleanUpReqFiles(req)
 
       return res.status(400)
-        .json({ error: 'Should have a magnetUri or a targetUrl.' })
+        .json({ error: 'Should have a magnetUri or a targetUrl or a torrent file.' })
         .end()
     }
 
index 55fca28b8c99c57bacac6aac05bfe05b57a2af37..d6c02e5ac9af65dfded8a7b91dd2eb1e324e2a6d 100644 (file)
@@ -171,7 +171,11 @@ export class VideoImportModel extends Model<VideoImportModel> {
 
     return {
       id: this.id,
+
       targetUrl: this.targetUrl,
+      magnetUri: this.magnetUri,
+      torrentName: this.torrentName,
+
       state: {
         id: this.state,
         label: VideoImportModel.getStateLabel(this.state)
index a5c582c678d5dacc06af5d2f203a71e5da3b193c..29385400630e42e38d5ddd25821c54e122938c87 100644 (file)
@@ -4,7 +4,11 @@ import { VideoImportState } from './video-import-state.enum'
 
 export interface VideoImport {
   id: number
+
   targetUrl: string
+  magnetUri: string
+  torrentName: string
+
   createdAt: string
   updatedAt: string
   state: VideoConstant<VideoImportState>