mirror of https://github.com/Chocobozzz/PeerTube
Optimize torrent URL update
parent
9df52d660f
commit
1f6125be8b
|
@ -32,7 +32,7 @@ jobs:
|
||||||
- 10389:10389
|
- 10389:10389
|
||||||
|
|
||||||
s3ninja:
|
s3ninja:
|
||||||
image: scireum/s3-ninja
|
image: chocobozzz/s3-ninja
|
||||||
ports:
|
ports:
|
||||||
- 9444:9000
|
- 9444:9000
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@
|
||||||
"async": "^3.0.1",
|
"async": "^3.0.1",
|
||||||
"async-lru": "^1.1.1",
|
"async-lru": "^1.1.1",
|
||||||
"bcrypt": "5.0.1",
|
"bcrypt": "5.0.1",
|
||||||
|
"bencode": "^2.0.2",
|
||||||
"bittorrent-tracker": "^9.0.0",
|
"bittorrent-tracker": "^9.0.0",
|
||||||
"bluebird": "^3.5.0",
|
"bluebird": "^3.5.0",
|
||||||
"bull": "^3.4.2",
|
"bull": "^3.4.2",
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { VideoCommentModel } from '../server/models/video/video-comment'
|
||||||
import { AccountModel } from '../server/models/account/account'
|
import { AccountModel } from '../server/models/account/account'
|
||||||
import { VideoChannelModel } from '../server/models/video/video-channel'
|
import { VideoChannelModel } from '../server/models/video/video-channel'
|
||||||
import { initDatabaseModels } from '../server/initializers/database'
|
import { initDatabaseModels } from '../server/initializers/database'
|
||||||
import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
|
import { updateTorrentUrls } from '@server/helpers/webtorrent'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
|
||||||
run()
|
run()
|
||||||
|
@ -126,7 +126,7 @@ async function run () {
|
||||||
|
|
||||||
for (const file of video.VideoFiles) {
|
for (const file of video.VideoFiles) {
|
||||||
console.log('Updating torrent file %s of video %s.', file.resolution, video.uuid)
|
console.log('Updating torrent file %s of video %s.', file.resolution, video.uuid)
|
||||||
await createTorrentAndSetInfoHash(video, file)
|
await updateTorrentUrls(video, file)
|
||||||
|
|
||||||
await file.save()
|
await file.save()
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ async function run () {
|
||||||
for (const file of (playlist?.VideoFiles || [])) {
|
for (const file of (playlist?.VideoFiles || [])) {
|
||||||
console.log('Updating fragmented torrent file %s of video %s.', file.resolution, video.uuid)
|
console.log('Updating fragmented torrent file %s of video %s.', file.resolution, video.uuid)
|
||||||
|
|
||||||
await createTorrentAndSetInfoHash(video, file)
|
await updateTorrentUrls(video, file)
|
||||||
|
|
||||||
await file.save()
|
await file.save()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
import * as bencode from 'bencode'
|
||||||
import * as createTorrent from 'create-torrent'
|
import * as createTorrent from 'create-torrent'
|
||||||
import { createWriteStream, ensureDir, remove, writeFile } from 'fs-extra'
|
import { createWriteStream, ensureDir, readFile, remove, writeFile } from 'fs-extra'
|
||||||
import * as magnetUtil from 'magnet-uri'
|
import * as magnetUtil from 'magnet-uri'
|
||||||
import * as parseTorrent from 'parse-torrent'
|
import * as parseTorrent from 'parse-torrent'
|
||||||
import { dirname, join } from 'path'
|
import { dirname, join } from 'path'
|
||||||
|
@ -79,43 +80,65 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTorrentAndSetInfoHash (
|
function createTorrentAndSetInfoHash (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
|
||||||
videoOrPlaylist: MVideo | MStreamingPlaylistVideo,
|
|
||||||
videoFile: MVideoFile
|
|
||||||
) {
|
|
||||||
const video = extractVideo(videoOrPlaylist)
|
const video = extractVideo(videoOrPlaylist)
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
// Keep the extname, it's used by the client to stream the file inside a web browser
|
// Keep the extname, it's used by the client to stream the file inside a web browser
|
||||||
name: `${video.name} ${videoFile.resolution}p${videoFile.extname}`,
|
name: `${video.name} ${videoFile.resolution}p${videoFile.extname}`,
|
||||||
createdBy: 'PeerTube',
|
createdBy: 'PeerTube',
|
||||||
announceList: [
|
announceList: buildAnnounceList(),
|
||||||
[ WEBSERVER.WS + '://' + WEBSERVER.HOSTNAME + ':' + WEBSERVER.PORT + '/tracker/socket' ],
|
urlList: buildUrlList(video, videoFile)
|
||||||
[ WEBSERVER.URL + '/tracker/announce' ]
|
|
||||||
],
|
|
||||||
urlList: [ videoFile.getFileUrl(video) ]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return VideoPathManager.Instance.makeAvailableVideoFile(videoOrPlaylist, videoFile, async videoPath => {
|
return VideoPathManager.Instance.makeAvailableVideoFile(videoOrPlaylist, videoFile, async videoPath => {
|
||||||
const torrent = await createTorrentPromise(videoPath, options)
|
const torrentContent = await createTorrentPromise(videoPath, options)
|
||||||
|
|
||||||
const torrentFilename = generateTorrentFileName(videoOrPlaylist, videoFile.resolution)
|
const torrentFilename = generateTorrentFileName(videoOrPlaylist, videoFile.resolution)
|
||||||
const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, torrentFilename)
|
const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, torrentFilename)
|
||||||
logger.info('Creating torrent %s.', torrentPath)
|
logger.info('Creating torrent %s.', torrentPath)
|
||||||
|
|
||||||
await writeFile(torrentPath, torrent)
|
await writeFile(torrentPath, torrentContent)
|
||||||
|
|
||||||
// Remove old torrent file if it existed
|
// Remove old torrent file if it existed
|
||||||
if (videoFile.hasTorrent()) {
|
if (videoFile.hasTorrent()) {
|
||||||
await remove(join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename))
|
await remove(join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename))
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedTorrent = parseTorrent(torrent)
|
const parsedTorrent = parseTorrent(torrentContent)
|
||||||
videoFile.infoHash = parsedTorrent.infoHash
|
videoFile.infoHash = parsedTorrent.infoHash
|
||||||
videoFile.torrentFilename = torrentFilename
|
videoFile.torrentFilename = torrentFilename
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateTorrentUrls (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
|
||||||
|
const video = extractVideo(videoOrPlaylist)
|
||||||
|
|
||||||
|
const oldTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename)
|
||||||
|
|
||||||
|
const torrentContent = await readFile(oldTorrentPath)
|
||||||
|
const decoded = bencode.decode(torrentContent)
|
||||||
|
|
||||||
|
decoded['announce-list'] = buildAnnounceList()
|
||||||
|
decoded.announce = decoded['announce-list'][0][0]
|
||||||
|
|
||||||
|
decoded['url-list'] = buildUrlList(video, videoFile)
|
||||||
|
|
||||||
|
const newTorrentFilename = generateTorrentFileName(videoOrPlaylist, videoFile.resolution)
|
||||||
|
const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, newTorrentFilename)
|
||||||
|
|
||||||
|
logger.info('Updating torrent URLs %s.', newTorrentPath)
|
||||||
|
|
||||||
|
await writeFile(newTorrentPath, bencode.encode(decoded))
|
||||||
|
|
||||||
|
// Remove old torrent file if it existed
|
||||||
|
if (videoFile.hasTorrent()) {
|
||||||
|
await remove(join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename))
|
||||||
|
}
|
||||||
|
|
||||||
|
videoFile.torrentFilename = newTorrentFilename
|
||||||
|
}
|
||||||
|
|
||||||
function generateMagnetUri (
|
function generateMagnetUri (
|
||||||
video: MVideo,
|
video: MVideo,
|
||||||
videoFile: MVideoFileRedundanciesOpt,
|
videoFile: MVideoFileRedundanciesOpt,
|
||||||
|
@ -143,6 +166,7 @@ function generateMagnetUri (
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createTorrentPromise,
|
createTorrentPromise,
|
||||||
|
updateTorrentUrls,
|
||||||
createTorrentAndSetInfoHash,
|
createTorrentAndSetInfoHash,
|
||||||
generateMagnetUri,
|
generateMagnetUri,
|
||||||
downloadWebTorrentVideo
|
downloadWebTorrentVideo
|
||||||
|
@ -186,3 +210,14 @@ function deleteDownloadedFile (downloadedFile: { directoryPath: string, filepath
|
||||||
remove(toRemovePath)
|
remove(toRemovePath)
|
||||||
.catch(err => logger.error('Cannot remove torrent file %s in webtorrent download.', toRemovePath, { err }))
|
.catch(err => logger.error('Cannot remove torrent file %s in webtorrent download.', toRemovePath, { err }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildAnnounceList () {
|
||||||
|
return [
|
||||||
|
[ WEBSERVER.WS + '://' + WEBSERVER.HOSTNAME + ':' + WEBSERVER.PORT + '/tracker/socket' ],
|
||||||
|
[ WEBSERVER.URL + '/tracker/announce' ]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUrlList (video: MVideo, videoFile: MVideoFile) {
|
||||||
|
return [ videoFile.getFileUrl(video) ]
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as Bull from 'bull'
|
||||||
import { remove } from 'fs-extra'
|
import { remove } from 'fs-extra'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { logger } from '@server/helpers/logger'
|
import { logger } from '@server/helpers/logger'
|
||||||
import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
|
import { updateTorrentUrls } from '@server/helpers/webtorrent'
|
||||||
import { CONFIG } from '@server/initializers/config'
|
import { CONFIG } from '@server/initializers/config'
|
||||||
import { storeHLSFile, storeWebTorrentFile } from '@server/lib/object-storage'
|
import { storeHLSFile, storeWebTorrentFile } from '@server/lib/object-storage'
|
||||||
import { getHLSDirectory, getHlsResolutionPlaylistFilename } from '@server/lib/paths'
|
import { getHLSDirectory, getHlsResolutionPlaylistFilename } from '@server/lib/paths'
|
||||||
|
@ -106,7 +106,7 @@ async function onFileMoved (options: {
|
||||||
file.fileUrl = fileUrl
|
file.fileUrl = fileUrl
|
||||||
file.storage = VideoStorage.OBJECT_STORAGE
|
file.storage = VideoStorage.OBJECT_STORAGE
|
||||||
|
|
||||||
await createTorrentAndSetInfoHash(videoOrPlaylist, file)
|
await updateTorrentUrls(videoOrPlaylist, file)
|
||||||
await file.save()
|
await file.save()
|
||||||
|
|
||||||
logger.debug('Removing %s because it\'s now on object storage', oldPath)
|
logger.debug('Removing %s because it\'s now on object storage', oldPath)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
createMultipleServers,
|
createMultipleServers,
|
||||||
createSingleServer,
|
createSingleServer,
|
||||||
doubleFollow,
|
doubleFollow,
|
||||||
|
expectLogDoesNotContain,
|
||||||
expectStartWith,
|
expectStartWith,
|
||||||
killallServers,
|
killallServers,
|
||||||
makeRawRequest,
|
makeRawRequest,
|
||||||
|
@ -235,6 +236,12 @@ function runTestSuite (options: {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should not have downloaded files from object storage', async function () {
|
||||||
|
for (const server of servers) {
|
||||||
|
await expectLogDoesNotContain(server, 'from object storage')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
mockObjectStorage.terminate()
|
mockObjectStorage.terminate()
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,12 @@ function expectStartWith (str: string, start: string) {
|
||||||
expect(str.startsWith(start), `${str} does not start with ${start}`).to.be.true
|
expect(str.startsWith(start), `${str} does not start with ${start}`).to.be.true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function expectLogDoesNotContain (server: PeerTubeServer, str: string) {
|
||||||
|
const content = await server.servers.getLogContent()
|
||||||
|
|
||||||
|
expect(content.toString()).to.not.contain(str)
|
||||||
|
}
|
||||||
|
|
||||||
async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') {
|
async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') {
|
||||||
const res = await makeGetRequest({
|
const res = await makeGetRequest({
|
||||||
url,
|
url,
|
||||||
|
@ -46,6 +52,7 @@ async function testFileExistsOrNot (server: PeerTubeServer, directory: string, f
|
||||||
export {
|
export {
|
||||||
dateIsValid,
|
dateIsValid,
|
||||||
testImage,
|
testImage,
|
||||||
|
expectLogDoesNotContain,
|
||||||
testFileExistsOrNot,
|
testFileExistsOrNot,
|
||||||
expectStartWith
|
expectStartWith
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ export class ServersCommand extends AbstractCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitUntilLog (str: string, count = 1, strictCount = true) {
|
async waitUntilLog (str: string, count = 1, strictCount = true) {
|
||||||
const logfile = this.server.servers.buildDirectory('logs/peertube.log')
|
const logfile = this.buildDirectory('logs/peertube.log')
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const buf = await readFile(logfile)
|
const buf = await readFile(logfile)
|
||||||
|
@ -80,6 +80,10 @@ export class ServersCommand extends AbstractCommand {
|
||||||
return this.buildDirectory(join('streaming-playlists', 'hls', videoUUID, basename(fileUrl)))
|
return this.buildDirectory(join('streaming-playlists', 'hls', videoUUID, basename(fileUrl)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLogContent () {
|
||||||
|
return readFile(this.buildDirectory('logs/peertube.log'))
|
||||||
|
}
|
||||||
|
|
||||||
async getServerFileSize (subPath: string) {
|
async getServerFileSize (subPath: string) {
|
||||||
const path = this.server.servers.buildDirectory(subPath)
|
const path = this.server.servers.buildDirectory(subPath)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue