aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/download.ts
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-07-31 14:34:36 +0200
committerChocobozzz <me@florianbigard.com>2023-08-11 15:02:33 +0200
commit3a4992633ee62d5edfbb484d9c6bcb3cf158489d (patch)
treee4510b39bdac9c318fdb4b47018d08f15368b8f0 /server/controllers/download.ts
parent04d1da5621d25d59bd5fa1543b725c497bf5d9a8 (diff)
downloadPeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.gz
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.zst
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.zip
Migrate server to ESM
Sorry for the very big commit that may lead to git log issues and merge conflicts, but it's a major step forward: * Server can be faster at startup because imports() are async and we can easily lazy import big modules * Angular doesn't seem to support ES import (with .js extension), so we had to correctly organize peertube into a monorepo: * Use yarn workspace feature * Use typescript reference projects for dependencies * Shared projects have been moved into "packages", each one is now a node module (with a dedicated package.json/tsconfig.json) * server/tools have been moved into apps/ and is now a dedicated app bundled and published on NPM so users don't have to build peertube cli tools manually * server/tests have been moved into packages/ so we don't compile them every time we want to run the server * Use isolatedModule option: * Had to move from const enum to const (https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums) * Had to explictely specify "type" imports when used in decorators * Prefer tsx (that uses esbuild under the hood) instead of ts-node to load typescript files (tests with mocha or scripts): * To reduce test complexity as esbuild doesn't support decorator metadata, we only test server files that do not import server models * We still build tests files into js files for a faster CI * Remove unmaintained peertube CLI import script * Removed some barrels to speed up execution (less imports)
Diffstat (limited to 'server/controllers/download.ts')
-rw-r--r--server/controllers/download.ts213
1 files changed, 0 insertions, 213 deletions
diff --git a/server/controllers/download.ts b/server/controllers/download.ts
deleted file mode 100644
index 4b94e34bd..000000000
--- a/server/controllers/download.ts
+++ /dev/null
@@ -1,213 +0,0 @@
1import cors from 'cors'
2import express from 'express'
3import { logger } from '@server/helpers/logger'
4import { VideoTorrentsSimpleFileCache } from '@server/lib/files-cache'
5import { generateHLSFilePresignedUrl, generateWebVideoPresignedUrl } from '@server/lib/object-storage'
6import { Hooks } from '@server/lib/plugins/hooks'
7import { VideoPathManager } from '@server/lib/video-path-manager'
8import { MStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
9import { forceNumber } from '@shared/core-utils'
10import { HttpStatusCode, VideoStorage, VideoStreamingPlaylistType } from '@shared/models'
11import { STATIC_DOWNLOAD_PATHS } from '../initializers/constants'
12import { asyncMiddleware, optionalAuthenticate, videosDownloadValidator } from '../middlewares'
13
14const downloadRouter = express.Router()
15
16downloadRouter.use(cors())
17
18downloadRouter.use(
19 STATIC_DOWNLOAD_PATHS.TORRENTS + ':filename',
20 asyncMiddleware(downloadTorrent)
21)
22
23downloadRouter.use(
24 STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension',
25 optionalAuthenticate,
26 asyncMiddleware(videosDownloadValidator),
27 asyncMiddleware(downloadVideoFile)
28)
29
30downloadRouter.use(
31 STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension',
32 optionalAuthenticate,
33 asyncMiddleware(videosDownloadValidator),
34 asyncMiddleware(downloadHLSVideoFile)
35)
36
37// ---------------------------------------------------------------------------
38
39export {
40 downloadRouter
41}
42
43// ---------------------------------------------------------------------------
44
45async function downloadTorrent (req: express.Request, res: express.Response) {
46 const result = await VideoTorrentsSimpleFileCache.Instance.getFilePath(req.params.filename)
47 if (!result) {
48 return res.fail({
49 status: HttpStatusCode.NOT_FOUND_404,
50 message: 'Torrent file not found'
51 })
52 }
53
54 const allowParameters = {
55 req,
56 res,
57 torrentPath: result.path,
58 downloadName: result.downloadName
59 }
60
61 const allowedResult = await Hooks.wrapFun(
62 isTorrentDownloadAllowed,
63 allowParameters,
64 'filter:api.download.torrent.allowed.result'
65 )
66
67 if (!checkAllowResult(res, allowParameters, allowedResult)) return
68
69 return res.download(result.path, result.downloadName)
70}
71
72async function downloadVideoFile (req: express.Request, res: express.Response) {
73 const video = res.locals.videoAll
74
75 const videoFile = getVideoFile(req, video.VideoFiles)
76 if (!videoFile) {
77 return res.fail({
78 status: HttpStatusCode.NOT_FOUND_404,
79 message: 'Video file not found'
80 })
81 }
82
83 const allowParameters = {
84 req,
85 res,
86 video,
87 videoFile
88 }
89
90 const allowedResult = await Hooks.wrapFun(
91 isVideoDownloadAllowed,
92 allowParameters,
93 'filter:api.download.video.allowed.result'
94 )
95
96 if (!checkAllowResult(res, allowParameters, allowedResult)) return
97
98 // Express uses basename on filename parameter
99 const videoName = video.name.replace(/[/\\]/g, '_')
100 const downloadFilename = `${videoName}-${videoFile.resolution}p${videoFile.extname}`
101
102 if (videoFile.storage === VideoStorage.OBJECT_STORAGE) {
103 return redirectToObjectStorage({ req, res, video, file: videoFile, downloadFilename })
104 }
105
106 await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(video), path => {
107 return res.download(path, downloadFilename)
108 })
109}
110
111async function downloadHLSVideoFile (req: express.Request, res: express.Response) {
112 const video = res.locals.videoAll
113 const streamingPlaylist = getHLSPlaylist(video)
114 if (!streamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).end
115
116 const videoFile = getVideoFile(req, streamingPlaylist.VideoFiles)
117 if (!videoFile) {
118 return res.fail({
119 status: HttpStatusCode.NOT_FOUND_404,
120 message: 'Video file not found'
121 })
122 }
123
124 const allowParameters = {
125 req,
126 res,
127 video,
128 streamingPlaylist,
129 videoFile
130 }
131
132 const allowedResult = await Hooks.wrapFun(
133 isVideoDownloadAllowed,
134 allowParameters,
135 'filter:api.download.video.allowed.result'
136 )
137
138 if (!checkAllowResult(res, allowParameters, allowedResult)) return
139
140 const downloadFilename = `${video.name}-${videoFile.resolution}p-${streamingPlaylist.getStringType()}${videoFile.extname}`
141
142 if (videoFile.storage === VideoStorage.OBJECT_STORAGE) {
143 return redirectToObjectStorage({ req, res, video, streamingPlaylist, file: videoFile, downloadFilename })
144 }
145
146 await VideoPathManager.Instance.makeAvailableVideoFile(videoFile.withVideoOrPlaylist(streamingPlaylist), path => {
147 return res.download(path, downloadFilename)
148 })
149}
150
151function getVideoFile (req: express.Request, files: MVideoFile[]) {
152 const resolution = forceNumber(req.params.resolution)
153 return files.find(f => f.resolution === resolution)
154}
155
156function getHLSPlaylist (video: MVideoFullLight) {
157 const playlist = video.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
158 if (!playlist) return undefined
159
160 return Object.assign(playlist, { Video: video })
161}
162
163type AllowedResult = {
164 allowed: boolean
165 errorMessage?: string
166}
167
168function isTorrentDownloadAllowed (_object: {
169 torrentPath: string
170}): AllowedResult {
171 return { allowed: true }
172}
173
174function isVideoDownloadAllowed (_object: {
175 video: MVideo
176 videoFile: MVideoFile
177 streamingPlaylist?: MStreamingPlaylist
178}): AllowedResult {
179 return { allowed: true }
180}
181
182function checkAllowResult (res: express.Response, allowParameters: any, result?: AllowedResult) {
183 if (!result || result.allowed !== true) {
184 logger.info('Download is not allowed.', { result, allowParameters })
185
186 res.fail({
187 status: HttpStatusCode.FORBIDDEN_403,
188 message: result?.errorMessage || 'Refused download'
189 })
190 return false
191 }
192
193 return true
194}
195
196async function redirectToObjectStorage (options: {
197 req: express.Request
198 res: express.Response
199 video: MVideo
200 file: MVideoFile
201 streamingPlaylist?: MStreamingPlaylistVideo
202 downloadFilename: string
203}) {
204 const { res, video, streamingPlaylist, file, downloadFilename } = options
205
206 const url = streamingPlaylist
207 ? await generateHLSFilePresignedUrl({ streamingPlaylist, file, downloadFilename })
208 : await generateWebVideoPresignedUrl({ file, downloadFilename })
209
210 logger.debug('Generating pre-signed URL %s for video %s', url, video.uuid)
211
212 return res.redirect(url)
213}