diff options
author | Chocobozzz <me@florianbigard.com> | 2021-02-25 13:56:07 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2021-02-25 15:01:07 +0100 |
commit | d61893f7236abbed30c25b1823e6ecad93a8e8dd (patch) | |
tree | 9cf1d47598d9a99f390c6754d289d2573788451d | |
parent | d7df188f23bb3c4773ac26e6fa8b3d82b1229e6d (diff) | |
download | PeerTube-d61893f7236abbed30c25b1823e6ecad93a8e8dd.tar.gz PeerTube-d61893f7236abbed30c25b1823e6ecad93a8e8dd.tar.zst PeerTube-d61893f7236abbed30c25b1823e6ecad93a8e8dd.zip |
Async torrent creation
-rw-r--r-- | client/src/assets/player/webtorrent/webtorrent-plugin.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 38 | ||||
-rw-r--r-- | server/models/video/video-file.ts | 8 | ||||
-rw-r--r-- | server/models/video/video-format-utils.ts | 2 | ||||
-rw-r--r-- | shared/extra-utils/videos/videos.ts | 27 |
5 files changed, 66 insertions, 11 deletions
diff --git a/client/src/assets/player/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/webtorrent/webtorrent-plugin.ts index 4bb438e57..e557fe722 100644 --- a/client/src/assets/player/webtorrent/webtorrent-plugin.ts +++ b/client/src/assets/player/webtorrent/webtorrent-plugin.ts | |||
@@ -249,6 +249,8 @@ class WebTorrentPlugin extends Plugin { | |||
249 | options: PlayOptions, | 249 | options: PlayOptions, |
250 | done: Function | 250 | done: Function |
251 | ) { | 251 | ) { |
252 | if (!magnetOrTorrentUrl) return this.fallbackToHttp(options, done) | ||
253 | |||
252 | console.log('Adding ' + magnetOrTorrentUrl + '.') | 254 | console.log('Adding ' + magnetOrTorrentUrl + '.') |
253 | 255 | ||
254 | const oldTorrent = this.torrent | 256 | const oldTorrent = this.torrent |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index e89315930..58ab72370 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -9,12 +9,12 @@ import { LiveManager } from '@server/lib/live-manager' | |||
9 | import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' | 9 | import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' |
10 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 10 | import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
11 | import { getServerActor } from '@server/models/application/application' | 11 | import { getServerActor } from '@server/models/application/application' |
12 | import { MVideoFullLight } from '@server/types/models' | 12 | import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' |
13 | import { VideoCreate, VideoState, VideoUpdate } from '../../../../shared' | 13 | import { VideoCreate, VideoState, VideoUpdate } from '../../../../shared' |
14 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' | 14 | import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' |
15 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' | 15 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' |
16 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 16 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
17 | import { resetSequelizeInstance } from '../../../helpers/database-utils' | 17 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' |
18 | import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils' | 18 | import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils' |
19 | import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' | 19 | import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' |
20 | import { logger } from '../../../helpers/logger' | 20 | import { logger } from '../../../helpers/logger' |
@@ -221,9 +221,6 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
221 | fallback: type => generateVideoMiniature({ video, videoFile, type }) | 221 | fallback: type => generateVideoMiniature({ video, videoFile, type }) |
222 | }) | 222 | }) |
223 | 223 | ||
224 | // Create the torrent file | ||
225 | await createTorrentAndSetInfoHash(video, videoFile) | ||
226 | |||
227 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { | 224 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { |
228 | const sequelizeOptions = { transaction: t } | 225 | const sequelizeOptions = { transaction: t } |
229 | 226 | ||
@@ -258,7 +255,6 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
258 | isNew: true, | 255 | isNew: true, |
259 | transaction: t | 256 | transaction: t |
260 | }) | 257 | }) |
261 | await federateVideoIfNeeded(video, true, t) | ||
262 | 258 | ||
263 | auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON())) | 259 | auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON())) |
264 | logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) | 260 | 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) { | |||
266 | return { videoCreated } | 262 | return { videoCreated } |
267 | }) | 263 | }) |
268 | 264 | ||
269 | Notifier.Instance.notifyOnNewVideoIfNeeded(videoCreated) | 265 | // Create the torrent file in async way because it could be long |
266 | createTorrentAndSetInfoHashAsync(video, videoFile) | ||
267 | .catch(err => logger.error('Cannot create torrent file for video %s', video.url, { err })) | ||
268 | .then(() => VideoModel.loadAndPopulateAccountAndServerAndTags(video.id)) | ||
269 | .then(refreshedVideo => { | ||
270 | if (!refreshedVideo) return | ||
271 | |||
272 | // Only federate and notify after the torrent creation | ||
273 | Notifier.Instance.notifyOnNewVideoIfNeeded(refreshedVideo) | ||
274 | |||
275 | return retryTransactionWrapper(() => { | ||
276 | return sequelizeTypescript.transaction(t => federateVideoIfNeeded(refreshedVideo, true, t)) | ||
277 | }) | ||
278 | }) | ||
279 | .catch(err => logger.error('Cannot federate or notify video creation %s', video.url, { err })) | ||
270 | 280 | ||
271 | if (video.state === VideoState.TO_TRANSCODE) { | 281 | if (video.state === VideoState.TO_TRANSCODE) { |
272 | await addOptimizeOrMergeAudioJob(videoCreated, videoFile, res.locals.oauth.token.User) | 282 | await addOptimizeOrMergeAudioJob(videoCreated, videoFile, res.locals.oauth.token.User) |
@@ -526,3 +536,17 @@ async function removeVideo (req: express.Request, res: express.Response) { | |||
526 | .status(HttpStatusCode.NO_CONTENT_204) | 536 | .status(HttpStatusCode.NO_CONTENT_204) |
527 | .end() | 537 | .end() |
528 | } | 538 | } |
539 | |||
540 | async function createTorrentAndSetInfoHashAsync (video: MVideo, fileArg: MVideoFile) { | ||
541 | await createTorrentAndSetInfoHash(video, fileArg) | ||
542 | |||
543 | // Refresh videoFile because the createTorrentAndSetInfoHash could be long | ||
544 | const refreshedFile = await VideoFileModel.loadWithVideo(fileArg.id) | ||
545 | // File does not exist anymore, remove the generated torrent | ||
546 | if (!refreshedFile) return fileArg.removeTorrent() | ||
547 | |||
548 | refreshedFile.infoHash = fileArg.infoHash | ||
549 | refreshedFile.torrentFilename = fileArg.torrentFilename | ||
550 | |||
551 | return refreshedFile.save() | ||
552 | } | ||
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index 4df2c20bc..1ad796104 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -457,18 +457,26 @@ export class VideoFileModel extends Model { | |||
457 | 457 | ||
458 | // We proxify torrent requests so use a local URL | 458 | // We proxify torrent requests so use a local URL |
459 | getTorrentUrl () { | 459 | getTorrentUrl () { |
460 | if (!this.torrentFilename) return null | ||
461 | |||
460 | return WEBSERVER.URL + this.getTorrentStaticPath() | 462 | return WEBSERVER.URL + this.getTorrentStaticPath() |
461 | } | 463 | } |
462 | 464 | ||
463 | getTorrentStaticPath () { | 465 | getTorrentStaticPath () { |
466 | if (!this.torrentFilename) return null | ||
467 | |||
464 | return join(LAZY_STATIC_PATHS.TORRENTS, this.torrentFilename) | 468 | return join(LAZY_STATIC_PATHS.TORRENTS, this.torrentFilename) |
465 | } | 469 | } |
466 | 470 | ||
467 | getTorrentDownloadUrl () { | 471 | getTorrentDownloadUrl () { |
472 | if (!this.torrentFilename) return null | ||
473 | |||
468 | return WEBSERVER.URL + join(STATIC_DOWNLOAD_PATHS.TORRENTS, this.torrentFilename) | 474 | return WEBSERVER.URL + join(STATIC_DOWNLOAD_PATHS.TORRENTS, this.torrentFilename) |
469 | } | 475 | } |
470 | 476 | ||
471 | removeTorrent () { | 477 | removeTorrent () { |
478 | if (!this.torrentFilename) return null | ||
479 | |||
472 | const torrentPath = getTorrentFilePath(this) | 480 | const torrentPath = getTorrentFilePath(this) |
473 | return remove(torrentPath) | 481 | return remove(torrentPath) |
474 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) | 482 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index a6a1a4f0d..bcba90093 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -205,7 +205,7 @@ function videoFilesModelToFormattedJSON ( | |||
205 | label: videoFile.resolution + 'p' | 205 | label: videoFile.resolution + 'p' |
206 | }, | 206 | }, |
207 | 207 | ||
208 | magnetUri: includeMagnet | 208 | magnetUri: includeMagnet && videoFile.torrentFilename |
209 | ? generateMagnetUri(video, videoFile, trackerUrls) | 209 | ? generateMagnetUri(video, videoFile, trackerUrls) |
210 | : undefined, | 210 | : undefined, |
211 | 211 | ||
diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts index 929eb42ca..0b6a54046 100644 --- a/shared/extra-utils/videos/videos.ts +++ b/shared/extra-utils/videos/videos.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ |
2 | 2 | ||
3 | import { HttpStatusCode } from '@shared/core-utils' | ||
4 | import { expect } from 'chai' | 3 | import { expect } from 'chai' |
5 | import { pathExists, readdir, readFile } from 'fs-extra' | 4 | import { pathExists, readdir, readFile } from 'fs-extra' |
6 | import * as parseTorrent from 'parse-torrent' | 5 | import * as parseTorrent from 'parse-torrent' |
@@ -8,9 +7,18 @@ import { extname, join } from 'path' | |||
8 | import * as request from 'supertest' | 7 | import * as request from 'supertest' |
9 | import { v4 as uuidv4 } from 'uuid' | 8 | import { v4 as uuidv4 } from 'uuid' |
10 | import validator from 'validator' | 9 | import validator from 'validator' |
10 | import { HttpStatusCode } from '@shared/core-utils' | ||
11 | import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' | 11 | import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' |
12 | import { VideoDetails, VideoPrivacy } from '../../models/videos' | 12 | import { VideoDetails, VideoPrivacy } from '../../models/videos' |
13 | import { buildAbsoluteFixturePath, buildServerDirectory, dateIsValid, immutableAssign, testImage, webtorrentAdd } from '../miscs/miscs' | 13 | import { |
14 | buildAbsoluteFixturePath, | ||
15 | buildServerDirectory, | ||
16 | dateIsValid, | ||
17 | immutableAssign, | ||
18 | testImage, | ||
19 | wait, | ||
20 | webtorrentAdd | ||
21 | } from '../miscs/miscs' | ||
14 | import { makeGetRequest, makePutBodyRequest, makeRawRequest, makeUploadRequest } from '../requests/requests' | 22 | import { makeGetRequest, makePutBodyRequest, makeRawRequest, makeUploadRequest } from '../requests/requests' |
15 | import { waitJobs } from '../server/jobs' | 23 | import { waitJobs } from '../server/jobs' |
16 | import { ServerInfo } from '../server/servers' | 24 | import { ServerInfo } from '../server/servers' |
@@ -423,8 +431,21 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg | |||
423 | req.field('originallyPublishedAt', attributes.originallyPublishedAt) | 431 | req.field('originallyPublishedAt', attributes.originallyPublishedAt) |
424 | } | 432 | } |
425 | 433 | ||
426 | return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture)) | 434 | const res = await req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture)) |
427 | .expect(specialStatus) | 435 | .expect(specialStatus) |
436 | |||
437 | // Wait torrent generation | ||
438 | if (specialStatus === HttpStatusCode.OK_200) { | ||
439 | let video: VideoDetails | ||
440 | do { | ||
441 | const resVideo = await getVideoWithToken(url, accessToken, res.body.video.uuid) | ||
442 | video = resVideo.body | ||
443 | |||
444 | await wait(50) | ||
445 | } while (!video.files[0].torrentUrl) | ||
446 | } | ||
447 | |||
448 | return res | ||
428 | } | 449 | } |
429 | 450 | ||
430 | function updateVideo ( | 451 | function updateVideo ( |