import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
import { getServerActor } from '@server/models/application/application'
-import { MVideoFullLight } from '@server/types/models'
+import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
import { VideoCreate, VideoState, VideoUpdate } from '../../../../shared'
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
-import { resetSequelizeInstance } from '../../../helpers/database-utils'
+import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils'
import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils'
import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils'
import { logger } from '../../../helpers/logger'
fallback: type => generateVideoMiniature({ video, videoFile, type })
})
- // Create the torrent file
- await createTorrentAndSetInfoHash(video, videoFile)
-
const { videoCreated } = await sequelizeTypescript.transaction(async t => {
const sequelizeOptions = { transaction: t }
isNew: true,
transaction: t
})
- await federateVideoIfNeeded(video, true, t)
auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
return { videoCreated }
})
- Notifier.Instance.notifyOnNewVideoIfNeeded(videoCreated)
+ // Create the torrent file in async way because it could be long
+ createTorrentAndSetInfoHashAsync(video, videoFile)
+ .catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err }))
+ .then(() => VideoModel.loadAndPopulateAccountAndServerAndTags(video.id))
+ .then(refreshedVideo => {
+ if (!refreshedVideo) return
+
+ // Only federate and notify after the torrent creation
+ Notifier.Instance.notifyOnNewVideoIfNeeded(refreshedVideo)
+
+ return retryTransactionWrapper(() => {
+ return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t))
+ })
+ })
+ .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err }))
if (video.state === VideoState.TO_TRANSCODE) {
await addOptimizeOrMergeAudioJob(videoCreated, videoFile, res.locals.oauth.token.User)
.status(HttpStatusCode.NO_CONTENT_204)
.end()
}
+
+async function createTorrentAndSetInfoHashAsync (video: MVideo, fileArg: MVideoFile) {
+ await createTorrentAndSetInfoHash(video, fileArg)
+
+ // Refresh videoFile because the createTorrentAndSetInfoHash could be long
+ const refreshedFile = await VideoFileModel.loadWithVideo(fileArg.id)
+ // File does not exist anymore, remove the generated torrent
+ if (!refreshedFile) return fileArg.removeTorrent()
+
+ refreshedFile.infoHash = fileArg.infoHash
+ refreshedFile.torrentFilename = fileArg.torrentFilename
+
+ return refreshedFile.save()
+}
// We proxify torrent requests so use a local URL
getTorrentUrl () {
+ if (!this.torrentFilename) return null
+
return WEBSERVER.URL + this.getTorrentStaticPath()
}
getTorrentStaticPath () {
+ if (!this.torrentFilename) return null
+
return join(LAZY_STATIC_PATHS.TORRENTS, this.torrentFilename)
}
getTorrentDownloadUrl () {
+ if (!this.torrentFilename) return null
+
return WEBSERVER.URL + join(STATIC_DOWNLOAD_PATHS.TORRENTS, this.torrentFilename)
}
removeTorrent () {
+ if (!this.torrentFilename) return null
+
const torrentPath = getTorrentFilePath(this)
return remove(torrentPath)
.catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err }))
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
-import { HttpStatusCode } from '@shared/core-utils'
import { expect } from 'chai'
import { pathExists, readdir, readFile } from 'fs-extra'
import * as parseTorrent from 'parse-torrent'
import * as request from 'supertest'
import { v4 as uuidv4 } from 'uuid'
import validator from 'validator'
+import { HttpStatusCode } from '@shared/core-utils'
import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
import { VideoDetails, VideoPrivacy } from '../../models/videos'
-import { buildAbsoluteFixturePath, buildServerDirectory, dateIsValid, immutableAssign, testImage, webtorrentAdd } from '../miscs/miscs'
+import {
+ buildAbsoluteFixturePath,
+ buildServerDirectory,
+ dateIsValid,
+ immutableAssign,
+ testImage,
+ wait,
+ webtorrentAdd
+} from '../miscs/miscs'
import { makeGetRequest, makePutBodyRequest, makeRawRequest, makeUploadRequest } from '../requests/requests'
import { waitJobs } from '../server/jobs'
import { ServerInfo } from '../server/servers'
req.field('originallyPublishedAt', attributes.originallyPublishedAt)
}
- return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
+ const res = await req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
.expect(specialStatus)
+
+ // Wait torrent generation
+ if (specialStatus === HttpStatusCode.OK_200) {
+ let video: VideoDetails
+ do {
+ const resVideo = await getVideoWithToken(url, accessToken, res.body.video.uuid)
+ video = resVideo.body
+
+ await wait(50)
+ } while (!video.files[0].torrentUrl)
+ }
+
+ return res
}
function updateVideo (