Async torrent creation

This commit is contained in:
Chocobozzz 2021-02-25 13:56:07 +01:00 committed by Chocobozzz
parent d7df188f23
commit d61893f723
5 changed files with 66 additions and 11 deletions

View File

@ -249,6 +249,8 @@ class WebTorrentPlugin extends Plugin {
options: PlayOptions,
done: Function
) {
if (!magnetOrTorrentUrl) return this.fallbackToHttp(options, done)
console.log('Adding ' + magnetOrTorrentUrl + '.')
const oldTorrent = this.torrent

View File

@ -9,12 +9,12 @@ import { LiveManager } from '@server/lib/live-manager'
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'
@ -221,9 +221,6 @@ async function addVideo (req: express.Request, res: express.Response) {
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 }
@ -258,7 +255,6 @@ async function addVideo (req: express.Request, res: express.Response) {
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)
@ -266,7 +262,21 @@ async function addVideo (req: express.Request, res: express.Response) {
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)
@ -526,3 +536,17 @@ async function removeVideo (req: express.Request, res: express.Response) {
.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()
}

View File

@ -457,18 +457,26 @@ export class VideoFileModel extends Model {
// 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 }))

View File

@ -205,7 +205,7 @@ function videoFilesModelToFormattedJSON (
label: videoFile.resolution + 'p'
},
magnetUri: includeMagnet
magnetUri: includeMagnet && videoFile.torrentFilename
? generateMagnetUri(video, videoFile, trackerUrls)
: undefined,

View File

@ -1,6 +1,5 @@
/* 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'
@ -8,9 +7,18 @@ import { extname, join } from 'path'
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'
@ -423,8 +431,21 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
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 (