Add ability to list video imports
authorChocobozzz <me@florianbigard.com>
Thu, 2 Aug 2018 15:48:50 +0000 (17:48 +0200)
committerChocobozzz <me@florianbigard.com>
Mon, 6 Aug 2018 09:19:16 +0000 (11:19 +0200)
17 files changed:
client/src/app/+my-account/my-account-routing.module.ts
client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html [new file with mode: 0644]
client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss [new file with mode: 0644]
client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts [new file with mode: 0644]
client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
client/src/app/+my-account/my-account.component.html
client/src/app/+my-account/my-account.module.ts
client/src/app/shared/video-import/video-import.service.ts
server.ts
server/controllers/api/users.ts
server/helpers/youtube-dl.ts
server/initializers/constants.ts
server/lib/job-queue/handlers/video-import.ts
server/middlewares/validators/sort.ts
server/models/video/video-import.ts
server/models/video/video.ts
shared/models/videos/video-import.model.ts

index 91b464f75e9adca112b48a2abe9ebcd27a76ae26..6f0806e8a0f16af9995e9071ea22da5a5f88d480 100644 (file)
@@ -8,6 +8,7 @@ import { MyAccountVideosComponent } from './my-account-videos/my-account-videos.
 import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component'
 import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
 import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
+import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
 
 const myAccountRoutes: Routes = [
   {
@@ -64,6 +65,15 @@ const myAccountRoutes: Routes = [
             title: 'Account videos'
           }
         }
+      },
+      {
+        path: 'video-imports',
+        component: MyAccountVideoImportsComponent,
+        data: {
+          meta: {
+            title: 'Account video imports'
+          }
+        }
       }
     ]
   }
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html
new file mode 100644 (file)
index 0000000..74ca33f
--- /dev/null
@@ -0,0 +1,37 @@
+<p-table
+  [value]="videoImports" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+  [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
+>
+  <ng-template pTemplate="header">
+    <tr>
+      <th i18n>URL</th>
+      <th i18n>Video</th>
+      <th i18n style="width: 150px">State</th>
+      <th i18n pSortableColumn="createdAt">Created <p-sortIcon field="createdAt"></p-sortIcon></th>
+      <th></th>
+    </tr>
+  </ng-template>
+
+  <ng-template pTemplate="body" let-videoImport>
+    <tr>
+      <td>
+        <a [href]="videoImport.targetUrl" target="_blank" rel="noopener noreferrer">{{ videoImport.targetUrl }}</a>
+      </td>
+
+      <td *ngIf="isVideoImportPending(videoImport)">
+        {{ videoImport.video.name }}
+      </td>
+      <td *ngIf="isVideoImportSuccess(videoImport)">
+        <a [href]="getVideoUrl(videoImport.video)" target="_blank" rel="noopener noreferrer">{{ videoImport.video.name }}</a>
+      </td>
+      <td *ngIf="isVideoImportFailed(videoImport)"></td>
+
+      <td>{{ videoImport.state.label }}</td>
+      <td>{{ videoImport.createdAt }}</td>
+
+      <td class="action-cell">
+        <my-edit-button *ngIf="isVideoImportSuccess(videoImport)" [routerLink]="getEditVideoUrl(videoImport.video)"></my-edit-button>
+      </td>
+    </tr>
+  </ng-template>
+</p-table>
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.scss
new file mode 100644 (file)
index 0000000..5e67747
--- /dev/null
@@ -0,0 +1,2 @@
+@import '_variables';
+@import '_mixins';
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
new file mode 100644 (file)
index 0000000..31ccb0b
--- /dev/null
@@ -0,0 +1,66 @@
+import { Component, OnInit } from '@angular/core'
+import { RestPagination, RestTable } from '@app/shared'
+import { SortMeta } from 'primeng/components/common/sortmeta'
+import { NotificationsService } from 'angular2-notifications'
+import { ConfirmService } from '@app/core'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { VideoImport, VideoImportState } from '../../../../../shared/models/videos'
+import { VideoImportService } from '@app/shared/video-import'
+
+@Component({
+  selector: 'my-account-video-imports',
+  templateUrl: './my-account-video-imports.component.html',
+  styleUrls: [ './my-account-video-imports.component.scss' ]
+})
+export class MyAccountVideoImportsComponent extends RestTable implements OnInit {
+  videoImports: VideoImport[] = []
+  totalRecords = 0
+  rowsPerPage = 10
+  sort: SortMeta = { field: 'createdAt', order: 1 }
+  pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
+
+  constructor (
+    private notificationsService: NotificationsService,
+    private confirmService: ConfirmService,
+    private videoImportService: VideoImportService,
+    private i18n: I18n
+  ) {
+    super()
+  }
+
+  ngOnInit () {
+    this.loadSort()
+  }
+
+  isVideoImportSuccess (videoImport: VideoImport) {
+    return videoImport.state.id === VideoImportState.SUCCESS
+  }
+
+  isVideoImportPending (videoImport: VideoImport) {
+    return videoImport.state.id === VideoImportState.PENDING
+  }
+
+  isVideoImportFailed (videoImport: VideoImport) {
+    return videoImport.state.id === VideoImportState.FAILED
+  }
+
+  getVideoUrl (video: { uuid: string }) {
+    return '/videos/watch/' + video.uuid
+  }
+
+  getEditVideoUrl (video: { uuid: string }) {
+    return '/videos/update/' + video.uuid
+  }
+
+  protected loadData () {
+    this.videoImportService.getMyVideoImports(this.pagination, this.sort)
+        .subscribe(
+          resultList => {
+            this.videoImports = resultList.data
+            this.totalRecords = resultList.total
+          },
+
+          err => this.notificationsService.error(this.i18n('Error'), err.message)
+        )
+  }
+}
index 54830c75e76ee7252bede67d6c2bff1612e9910d..01e1ef1da28780877338322926d55f217c77d406 100644 (file)
@@ -145,6 +145,8 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
       suffix = this.i18n('Waiting transcoding')
     } else if (video.state.id === VideoState.TO_TRANSCODE) {
       suffix = this.i18n('To transcode')
+    } else if (video.state.id === VideoState.TO_IMPORT) {
+      suffix = this.i18n('To import')
     } else {
       return ''
     }
index 48db55ad3eb2e37e21a2133d39c92c7e51b73c7b..f67245d857fb37b67304a24f7168dbe85e0fb219 100644 (file)
@@ -5,6 +5,8 @@
     <a i18n routerLink="/my-account/video-channels" routerLinkActive="active" class="title-page">My video channels</a>
 
     <a i18n routerLink="/my-account/videos" routerLinkActive="active" class="title-page">My videos</a>
+
+    <a i18n routerLink="/my-account/video-imports" routerLinkActive="active" class="title-page">My video imports</a>
   </div>
 
   <div class="margin-content">
index 2088273e6d6e907b673c6806aad0558214f2fdb4..5403ab64945c0aa21d5a15fab002fa5087ac7608 100644 (file)
@@ -1,3 +1,4 @@
+import { TableModule } from 'primeng/table'
 import { NgModule } from '@angular/core'
 import { SharedModule } from '../shared'
 import { MyAccountRoutingModule } from './my-account-routing.module'
@@ -11,11 +12,13 @@ import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-vid
 import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
 import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
 import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
+import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
 
 @NgModule({
   imports: [
     MyAccountRoutingModule,
-    SharedModule
+    SharedModule,
+    TableModule
   ],
 
   declarations: [
@@ -28,7 +31,8 @@ import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-i
     MyAccountVideoChannelsComponent,
     MyAccountVideoChannelCreateComponent,
     MyAccountVideoChannelUpdateComponent,
-    ActorAvatarInfoComponent
+    ActorAvatarInfoComponent,
+    MyAccountVideoImportsComponent
   ],
 
   exports: [
index b4709866a811d89bcf8d70a2e99237a87f09c075..59b58ab38dffa5c727f639d85ca84212897abf85 100644 (file)
@@ -1,5 +1,5 @@
-import { catchError } from 'rxjs/operators'
-import { HttpClient } from '@angular/common/http'
+import { catchError, map, switchMap } from 'rxjs/operators'
+import { HttpClient, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { Observable } from 'rxjs'
 import { VideoImport } from '../../../../../shared'
@@ -8,6 +8,12 @@ import { RestExtractor, RestService } from '../rest'
 import { VideoImportCreate } from '../../../../../shared/models/videos/video-import-create.model'
 import { objectToFormData } from '@app/shared/misc/utils'
 import { VideoUpdate } from '../../../../../shared/models/videos'
+import { ResultList } from '../../../../../shared/models/result-list.model'
+import { UserService } from '@app/shared/users/user.service'
+import { SortMeta } from 'primeng/components/common/sortmeta'
+import { RestPagination } from '@app/shared/rest'
+import { ServerService } from '@app/core'
+import { peertubeTranslate } from '@app/shared/i18n/i18n-utils'
 
 @Injectable()
 export class VideoImportService {
@@ -16,7 +22,8 @@ export class VideoImportService {
   constructor (
     private authHttp: HttpClient,
     private restService: RestService,
-    private restExtractor: RestExtractor
+    private restExtractor: RestExtractor,
+    private serverService: ServerService
   ) {}
 
   importVideo (targetUrl: string, video: VideoUpdate): Observable<VideoImport> {
@@ -53,4 +60,29 @@ export class VideoImportService {
                .pipe(catchError(res => this.restExtractor.handleError(res)))
   }
 
+  getMyVideoImports (pagination: RestPagination, sort: SortMeta): Observable<ResultList<VideoImport>> {
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination, sort)
+
+    return this.authHttp
+               .get<ResultList<VideoImport>>(UserService.BASE_USERS_URL + '/me/videos/imports', { params })
+               .pipe(
+                 switchMap(res => this.extractVideoImports(res)),
+                 map(res => this.restExtractor.convertResultListDateToHuman(res)),
+                 catchError(err => this.restExtractor.handleError(err))
+               )
+  }
+
+  private extractVideoImports (result: ResultList<VideoImport>): Observable<ResultList<VideoImport>> {
+    return this.serverService.localeObservable
+               .pipe(
+                 map(translations => {
+                   result.data.forEach(d =>
+                     d.state.label = peertubeTranslate(d.state.label, translations)
+                   )
+
+                   return result
+                 })
+               )
+  }
 }
index 9aaa64dbf2d3f067ddaadadf48e5f2d073125fdb..9094ac943c24d0e2ad0f383993d7d67dc8733866 100644 (file)
--- a/server.ts
+++ b/server.ts
@@ -152,7 +152,7 @@ app.use(function (err, req, res, next) {
     error = err.stack || err.message || err
   }
 
-  logger.error('Error in controller.', { error })
+  logger.error('Error in controller.', { err: error })
   return res.status(err.status || 500).end()
 })
 
index dbe736bff9a6cb57eba32e02ad5846ea826ba8cc..6e5f9913e9915a625718260c76fea16b1db4272b 100644 (file)
@@ -29,7 +29,12 @@ import {
   usersUpdateValidator,
   usersVideoRatingValidator
 } from '../../middlewares'
-import { usersAskResetPasswordValidator, usersResetPasswordValidator, videosSortValidator } from '../../middlewares/validators'
+import {
+  usersAskResetPasswordValidator,
+  usersResetPasswordValidator,
+  videoImportsSortValidator,
+  videosSortValidator
+} from '../../middlewares/validators'
 import { AccountVideoRateModel } from '../../models/account/account-video-rate'
 import { UserModel } from '../../models/account/user'
 import { OAuthTokenModel } from '../../models/oauth/oauth-token'
@@ -40,6 +45,7 @@ import { UserVideoQuota } from '../../../shared/models/users/user-video-quota.mo
 import { updateAvatarValidator } from '../../middlewares/validators/avatar'
 import { updateActorAvatarFile } from '../../lib/avatar'
 import { auditLoggerFactory, UserAuditView } from '../../helpers/audit-logger'
+import { VideoImportModel } from '../../models/video/video-import'
 
 const auditLogger = auditLoggerFactory('users')
 
@@ -62,6 +68,16 @@ usersRouter.get('/me/video-quota-used',
   asyncMiddleware(getUserVideoQuotaUsed)
 )
 
+
+usersRouter.get('/me/videos/imports',
+  authenticate,
+  paginationValidator,
+  videoImportsSortValidator,
+  setDefaultSort,
+  setDefaultPagination,
+  asyncMiddleware(getUserVideoImports)
+)
+
 usersRouter.get('/me/videos',
   authenticate,
   paginationValidator,
@@ -178,6 +194,18 @@ async function getUserVideos (req: express.Request, res: express.Response, next:
   return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
 }
 
+async function getUserVideoImports (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const user = res.locals.oauth.token.User as UserModel
+  const resultList = await VideoImportModel.listUserVideoImportsForApi(
+    user.Account.id,
+    req.query.start as number,
+    req.query.count as number,
+    req.query.sort
+  )
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
+
 async function createUser (req: express.Request, res: express.Response) {
   const body: UserCreate = req.body
   const userToCreate = new UserModel({
index 74d3e213bc84ee5e00fbc02d56714dec128c2b5c..43156bb227172e4c958a6b8679095997a05a811d 100644 (file)
@@ -95,7 +95,7 @@ function titleTruncation (title: string) {
 }
 
 function descriptionTruncation (description: string) {
-  if (!description) return undefined
+  if (!description || description.length < CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.min) return undefined
 
   return truncate(description, {
     'length': CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max,
index cc363d4f2699a0497e9ddd7f289c64010f2a8dac..feb45e4d08cab936b16261780949d4287e2bb567 100644 (file)
@@ -37,6 +37,7 @@ const SORTABLE_COLUMNS = {
   VIDEO_ABUSES: [ 'id', 'createdAt' ],
   VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
   VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes' ],
+  VIDEO_IMPORTS: [ 'createdAt' ],
   VIDEO_COMMENT_THREADS: [ 'createdAt' ],
   BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ],
   FOLLOWERS: [ 'createdAt' ],
index 2f219e986be12aa9b468a656dd6d42a5f173bd83..5a7722153d321476bdaafa865b88ffaa1aeebb8c 100644 (file)
@@ -35,7 +35,7 @@ async function processVideoImport (job: Bull.Job) {
 
     // Get information about this video
     const { videoFileResolution } = await getVideoFileResolution(tempVideoPath)
-    const fps = await getVideoFileFPS(tempVideoPath)
+    const fps = await getVideoFileFPS(tempVideoPath + 's')
     const stats = await statPromise(tempVideoPath)
     const duration = await getDurationFromVideoFile(tempVideoPath)
 
index 00bde548c517758393e7e0b63a555061feb9a3f8..d85611773191f4d57032164d1c7ac191584bb954 100644 (file)
@@ -8,6 +8,7 @@ const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS)
 const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
 const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
 const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS_SEARCH)
+const SORTABLE_VIDEO_IMPORTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_IMPORTS)
 const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS)
 const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS)
 const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS)
@@ -19,6 +20,7 @@ const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
 const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS)
 const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
 const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
+const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS)
 const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS)
 const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS)
 const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS)
@@ -32,6 +34,7 @@ export {
   usersSortValidator,
   videoAbusesSortValidator,
   videoChannelsSortValidator,
+  videoImportsSortValidator,
   videosSearchSortValidator,
   videosSortValidator,
   blacklistSortValidator,
index 89eeafd6a1603c0940b619b13c8678d10d8339e2..6b8a16b65302c6eb28149840090e9083657ec47d 100644 (file)
@@ -1,4 +1,5 @@
 import {
+  AfterUpdate,
   AllowNull,
   BelongsTo,
   Column,
@@ -12,13 +13,14 @@ import {
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
-import { CONSTRAINTS_FIELDS } from '../../initializers'
-import { throwIfNotValid } from '../utils'
+import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers'
+import { getSort, throwIfNotValid } from '../utils'
 import { VideoModel } from './video'
 import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports'
 import { VideoImport, VideoImportState } from '../../../shared'
 import { VideoChannelModel } from './video-channel'
 import { AccountModel } from '../account/account'
+import { TagModel } from './tag'
 
 @DefaultScope({
   include: [
@@ -35,6 +37,10 @@ import { AccountModel } from '../account/account'
               required: true
             }
           ]
+        },
+        {
+          model: () => TagModel,
+          required: false
         }
       ]
     }
@@ -79,27 +85,89 @@ export class VideoImportModel extends Model<VideoImportModel> {
 
   @BelongsTo(() => VideoModel, {
     foreignKey: {
-      allowNull: false
+      allowNull: true
     },
-    onDelete: 'CASCADE'
+    onDelete: 'set null'
   })
   Video: VideoModel
 
+  @AfterUpdate
+  static deleteVideoIfFailed (instance: VideoImportModel, options) {
+    if (instance.state === VideoImportState.FAILED) {
+      return instance.Video.destroy({ transaction: options.transaction })
+    }
+
+    return undefined
+  }
+
   static loadAndPopulateVideo (id: number) {
     return VideoImportModel.findById(id)
   }
 
+  static listUserVideoImportsForApi (accountId: number, start: number, count: number, sort: string) {
+    const query = {
+      offset: start,
+      limit: count,
+      order: getSort(sort),
+      include: [
+        {
+          model: VideoModel,
+          required: true,
+          include: [
+            {
+              model: VideoChannelModel,
+              required: true,
+              include: [
+                {
+                  model: AccountModel,
+                  required: true,
+                  where: {
+                    id: accountId
+                  }
+                }
+              ]
+            },
+            {
+              model: TagModel,
+              required: false
+            }
+          ]
+        }
+      ]
+    }
+
+    return VideoImportModel.unscoped()
+                           .findAndCountAll(query)
+                           .then(({ rows, count }) => {
+                             return {
+                               data: rows,
+                               total: count
+                             }
+                           })
+  }
+
   toFormattedJSON (): VideoImport {
     const videoFormatOptions = {
       additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true }
     }
-    const video = Object.assign(this.Video.toFormattedJSON(videoFormatOptions), {
-      tags: this.Video.Tags.map(t => t.name)
-    })
+    const video = this.Video
+      ? Object.assign(this.Video.toFormattedJSON(videoFormatOptions), {
+        tags: this.Video.Tags.map(t => t.name)
+      })
+      : undefined
 
     return {
       targetUrl: this.targetUrl,
+      state: {
+        id: this.state,
+        label: VideoImportModel.getStateLabel(this.state)
+      },
+      updatedAt: this.updatedAt.toISOString(),
+      createdAt: this.createdAt.toISOString(),
       video
     }
   }
+  private static getStateLabel (id: number) {
+    return VIDEO_IMPORT_STATES[id] || 'Unknown'
+  }
 }
index 459fcb31e5ddb351cd749a9cd625c1eeaee667b7..f32010014d7b0b49423c4a6c39798902ca0d0df8 100644 (file)
@@ -1569,21 +1569,25 @@ export class VideoModel extends Model<VideoModel> {
   removeThumbnail () {
     const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
     return unlinkPromise(thumbnailPath)
+      .catch(err => logger.warn('Cannot delete thumbnail %s.', thumbnailPath, { err }))
   }
 
   removePreview () {
-    // Same name than video thumbnail
-    return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName())
+    const previewPath = join(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName())
+    return unlinkPromise(previewPath)
+      .catch(err => logger.warn('Cannot delete preview %s.', previewPath, { err }))
   }
 
   removeFile (videoFile: VideoFileModel) {
     const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
     return unlinkPromise(filePath)
+      .catch(err => logger.warn('Cannot delete file %s.', filePath, { err }))
   }
 
   removeTorrent (videoFile: VideoFileModel) {
     const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
     return unlinkPromise(torrentPath)
+      .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err }))
   }
 
   getActivityStreamDuration () {
index 8581085991cddcbf12c305de89c4893f6d3efc1e..b23e6b245e5b5c719ccc448cf99948ab7cbef29f 100644 (file)
@@ -1,7 +1,12 @@
 import { Video } from './video.model'
+import { VideoConstant } from './video-constant.model'
+import { VideoImportState } from '../../index'
 
 export interface VideoImport {
   targetUrl: string
+  createdAt: string
+  updatedAt: string
+  state: VideoConstant<VideoImportState>
 
-  video: Video & { tags: string[] }
+  video?: Video & { tags: string[] }
 }