]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/video-path-manager.ts
Merge branch 'release/5.1.0' into develop
[github/Chocobozzz/PeerTube.git] / server / lib / video-path-manager.ts
CommitLineData
3545e72c 1import { Mutex } from 'async-mutex'
0305db28
JB
2import { remove } from 'fs-extra'
3import { extname, join } from 'path'
3545e72c 4import { logger, loggerTagsFactory } from '@server/helpers/logger'
0305db28
JB
5import { extractVideo } from '@server/helpers/video'
6import { CONFIG } from '@server/initializers/config'
3545e72c
C
7import { DIRECTORIES } from '@server/initializers/constants'
8import { MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '@server/types/models'
0628157f 9import { buildUUID } from '@shared/extra-utils'
0305db28
JB
10import { VideoStorage } from '@shared/models'
11import { makeHLSFileAvailable, makeWebTorrentFileAvailable } from './object-storage'
12import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFilename } from './paths'
3545e72c 13import { isVideoInPrivateDirectory } from './video-privacy'
0305db28
JB
14
15type MakeAvailableCB <T> = (path: string) => Promise<T> | T
16
3545e72c
C
17const lTags = loggerTagsFactory('video-path-manager')
18
0305db28
JB
19class VideoPathManager {
20
21 private static instance: VideoPathManager
22
3545e72c
C
23 // Key is a video UUID
24 private readonly videoFileMutexStore = new Map<string, Mutex>()
25
0305db28
JB
26 private constructor () {}
27
3545e72c 28 getFSHLSOutputPath (video: MVideo, filename?: string) {
0305db28
JB
29 const base = getHLSDirectory(video)
30 if (!filename) return base
31
32 return join(base, filename)
33 }
34
35 getFSRedundancyVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
36 if (videoFile.isHLS()) {
37 const video = extractVideo(videoOrPlaylist)
38
39 return join(getHLSRedundancyDirectory(video), videoFile.filename)
40 }
41
42 return join(CONFIG.STORAGE.REDUNDANCY_DIR, videoFile.filename)
43 }
44
45 getFSVideoFileOutputPath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
3545e72c 46 const video = extractVideo(videoOrPlaylist)
0305db28 47
3545e72c 48 if (videoFile.isHLS()) {
0305db28
JB
49 return join(getHLSDirectory(video), videoFile.filename)
50 }
51
3545e72c
C
52 if (isVideoInPrivateDirectory(video.privacy)) {
53 return join(DIRECTORIES.VIDEOS.PRIVATE, videoFile.filename)
54 }
55
56 return join(DIRECTORIES.VIDEOS.PUBLIC, videoFile.filename)
0305db28
JB
57 }
58
ad5db104 59 async makeAvailableVideoFile <T> (videoFile: MVideoFileVideo | MVideoFileStreamingPlaylistVideo, cb: MakeAvailableCB<T>) {
0305db28
JB
60 if (videoFile.storage === VideoStorage.FILE_SYSTEM) {
61 return this.makeAvailableFactory(
ad5db104 62 () => this.getFSVideoFileOutputPath(videoFile.getVideoOrStreamingPlaylist(), videoFile),
0305db28
JB
63 false,
64 cb
65 )
66 }
67
68 const destination = this.buildTMPDestination(videoFile.filename)
69
70 if (videoFile.isHLS()) {
ad5db104 71 const playlist = (videoFile as MVideoFileStreamingPlaylistVideo).VideoStreamingPlaylist
0305db28
JB
72
73 return this.makeAvailableFactory(
ad5db104 74 () => makeHLSFileAvailable(playlist, videoFile.filename, destination),
0305db28
JB
75 true,
76 cb
77 )
78 }
79
80 return this.makeAvailableFactory(
81 () => makeWebTorrentFileAvailable(videoFile.filename, destination),
82 true,
83 cb
84 )
85 }
86
ad5db104 87 async makeAvailableResolutionPlaylistFile <T> (videoFile: MVideoFileStreamingPlaylistVideo, cb: MakeAvailableCB<T>) {
0305db28
JB
88 const filename = getHlsResolutionPlaylistFilename(videoFile.filename)
89
90 if (videoFile.storage === VideoStorage.FILE_SYSTEM) {
91 return this.makeAvailableFactory(
ad5db104 92 () => join(getHLSDirectory(videoFile.getVideo()), filename),
0305db28
JB
93 false,
94 cb
95 )
96 }
97
ad5db104 98 const playlist = videoFile.VideoStreamingPlaylist
0305db28 99 return this.makeAvailableFactory(
ad5db104 100 () => makeHLSFileAvailable(playlist, filename, this.buildTMPDestination(filename)),
0305db28
JB
101 true,
102 cb
103 )
104 }
105
106 async makeAvailablePlaylistFile <T> (playlist: MStreamingPlaylistVideo, filename: string, cb: MakeAvailableCB<T>) {
107 if (playlist.storage === VideoStorage.FILE_SYSTEM) {
108 return this.makeAvailableFactory(
109 () => join(getHLSDirectory(playlist.Video), filename),
110 false,
111 cb
112 )
113 }
114
115 return this.makeAvailableFactory(
ad5db104 116 () => makeHLSFileAvailable(playlist, filename, this.buildTMPDestination(filename)),
0305db28
JB
117 true,
118 cb
119 )
120 }
121
3545e72c
C
122 async lockFiles (videoUUID: string) {
123 if (!this.videoFileMutexStore.has(videoUUID)) {
124 this.videoFileMutexStore.set(videoUUID, new Mutex())
125 }
126
127 const mutex = this.videoFileMutexStore.get(videoUUID)
128 const releaser = await mutex.acquire()
129
130 logger.debug('Locked files of %s.', videoUUID, lTags(videoUUID))
131
132 return releaser
133 }
134
135 unlockFiles (videoUUID: string) {
136 const mutex = this.videoFileMutexStore.get(videoUUID)
137
138 mutex.release()
139
140 logger.debug('Released lockfiles of %s.', videoUUID, lTags(videoUUID))
141 }
142
0305db28
JB
143 private async makeAvailableFactory <T> (method: () => Promise<string> | string, clean: boolean, cb: MakeAvailableCB<T>) {
144 let result: T
145
146 const destination = await method()
147
148 try {
149 result = await cb(destination)
150 } catch (err) {
151 if (destination && clean) await remove(destination)
152 throw err
153 }
154
155 if (clean) await remove(destination)
156
157 return result
158 }
159
160 private buildTMPDestination (filename: string) {
161 return join(CONFIG.STORAGE.TMP_DIR, buildUUID() + extname(filename))
162
163 }
164
165 static get Instance () {
166 return this.instance || (this.instance = new this())
167 }
168}
169
170// ---------------------------------------------------------------------------
171
172export {
173 VideoPathManager
174}