diff options
author | Chocobozzz <me@florianbigard.com> | 2021-06-10 14:43:55 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-06-10 15:26:18 +0200 |
commit | d9bf974f5df787bbeaab5b04949ca91a2b3ca2a3 (patch) | |
tree | aa02ee0cc28c845432e91da43b1e6de2a2f04039 /server/models/video/sql/shared | |
parent | e5dbd5084e7ae91ce118c0bccd5b84c47b88c55f (diff) | |
download | PeerTube-d9bf974f5df787bbeaab5b04949ca91a2b3ca2a3.tar.gz PeerTube-d9bf974f5df787bbeaab5b04949ca91a2b3ca2a3.tar.zst PeerTube-d9bf974f5df787bbeaab5b04949ca91a2b3ca2a3.zip |
Use raw SQL for video get request
Diffstat (limited to 'server/models/video/sql/shared')
4 files changed, 776 insertions, 0 deletions
diff --git a/server/models/video/sql/shared/abstract-videos-model-query-builder.ts b/server/models/video/sql/shared/abstract-videos-model-query-builder.ts new file mode 100644 index 000000000..bdf926cbe --- /dev/null +++ b/server/models/video/sql/shared/abstract-videos-model-query-builder.ts | |||
@@ -0,0 +1,239 @@ | |||
1 | import { AbstractVideosQueryBuilder } from './abstract-videos-query-builder' | ||
2 | import { VideoAttributes } from './video-attributes' | ||
3 | import { VideoModelBuilder } from './video-model-builder' | ||
4 | |||
5 | export class AbstractVideosModelQueryBuilder extends AbstractVideosQueryBuilder { | ||
6 | protected attributes: { [key: string]: string } = {} | ||
7 | protected joins: string[] = [] | ||
8 | |||
9 | protected videoAttributes: VideoAttributes | ||
10 | protected videoModelBuilder: VideoModelBuilder | ||
11 | |||
12 | constructor (private readonly mode: 'list' | 'get') { | ||
13 | super() | ||
14 | |||
15 | this.videoAttributes = new VideoAttributes(this.mode) | ||
16 | this.videoModelBuilder = new VideoModelBuilder(this.mode, this.videoAttributes) | ||
17 | } | ||
18 | |||
19 | protected buildSelect () { | ||
20 | return 'SELECT ' + Object.keys(this.attributes).map(key => { | ||
21 | const value = this.attributes[key] | ||
22 | if (value) return `${key} AS ${value}` | ||
23 | |||
24 | return key | ||
25 | }).join(', ') | ||
26 | } | ||
27 | |||
28 | protected includeChannels () { | ||
29 | this.joins.push( | ||
30 | 'INNER JOIN "videoChannel" AS "VideoChannel" ON "video"."channelId" = "VideoChannel"."id"', | ||
31 | 'INNER JOIN "actor" AS "VideoChannel->Actor" ON "VideoChannel"."actorId" = "VideoChannel->Actor"."id"', | ||
32 | |||
33 | 'LEFT OUTER JOIN "server" AS "VideoChannel->Actor->Server" ON "VideoChannel->Actor"."serverId" = "VideoChannel->Actor->Server"."id"', | ||
34 | |||
35 | 'LEFT OUTER JOIN "actorImage" AS "VideoChannel->Actor->Avatar" ' + | ||
36 | 'ON "VideoChannel->Actor"."avatarId" = "VideoChannel->Actor->Avatar"."id"' | ||
37 | ) | ||
38 | |||
39 | this.attributes = { | ||
40 | ...this.attributes, | ||
41 | |||
42 | ...this.buildAttributesObject('VideoChannel', this.videoAttributes.getChannelAttributes()), | ||
43 | ...this.buildActorInclude('VideoChannel->Actor'), | ||
44 | ...this.buildAvatarInclude('VideoChannel->Actor->Avatar'), | ||
45 | ...this.buildServerInclude('VideoChannel->Actor->Server') | ||
46 | } | ||
47 | } | ||
48 | |||
49 | protected includeAccounts () { | ||
50 | this.joins.push( | ||
51 | 'INNER JOIN "account" AS "VideoChannel->Account" ON "VideoChannel"."accountId" = "VideoChannel->Account"."id"', | ||
52 | 'INNER JOIN "actor" AS "VideoChannel->Account->Actor" ON "VideoChannel->Account"."actorId" = "VideoChannel->Account->Actor"."id"', | ||
53 | |||
54 | 'LEFT OUTER JOIN "server" AS "VideoChannel->Account->Actor->Server" ' + | ||
55 | 'ON "VideoChannel->Account->Actor"."serverId" = "VideoChannel->Account->Actor->Server"."id"', | ||
56 | |||
57 | 'LEFT OUTER JOIN "actorImage" AS "VideoChannel->Account->Actor->Avatar" ' + | ||
58 | 'ON "VideoChannel->Account->Actor"."avatarId" = "VideoChannel->Account->Actor->Avatar"."id"' | ||
59 | ) | ||
60 | |||
61 | this.attributes = { | ||
62 | ...this.attributes, | ||
63 | |||
64 | ...this.buildAttributesObject('VideoChannel->Account', this.videoAttributes.getAccountAttributes()), | ||
65 | ...this.buildActorInclude('VideoChannel->Account->Actor'), | ||
66 | ...this.buildAvatarInclude('VideoChannel->Account->Actor->Avatar'), | ||
67 | ...this.buildServerInclude('VideoChannel->Account->Actor->Server') | ||
68 | } | ||
69 | } | ||
70 | |||
71 | protected includeThumbnails () { | ||
72 | this.joins.push('LEFT OUTER JOIN "thumbnail" AS "Thumbnails" ON "video"."id" = "Thumbnails"."videoId"') | ||
73 | |||
74 | this.attributes = { | ||
75 | ...this.attributes, | ||
76 | |||
77 | ...this.buildAttributesObject('Thumbnails', this.videoAttributes.getThumbnailAttributes()) | ||
78 | } | ||
79 | } | ||
80 | |||
81 | protected includeFiles () { | ||
82 | this.joins.push( | ||
83 | 'LEFT JOIN "videoFile" AS "VideoFiles" ON "VideoFiles"."videoId" = "video"."id"', | ||
84 | |||
85 | 'LEFT JOIN "videoStreamingPlaylist" AS "VideoStreamingPlaylists" ON "VideoStreamingPlaylists"."videoId" = "video"."id"', | ||
86 | |||
87 | 'LEFT JOIN "videoFile" AS "VideoStreamingPlaylists->VideoFiles" ' + | ||
88 | 'ON "VideoStreamingPlaylists->VideoFiles"."videoStreamingPlaylistId" = "VideoStreamingPlaylists"."id"' | ||
89 | ) | ||
90 | |||
91 | this.attributes = { | ||
92 | ...this.attributes, | ||
93 | |||
94 | ...this.buildAttributesObject('VideoFiles', this.videoAttributes.getFileAttributes()), | ||
95 | |||
96 | ...this.buildAttributesObject('VideoStreamingPlaylists', this.videoAttributes.getStreamingPlaylistAttributes()), | ||
97 | ...this.buildAttributesObject('VideoStreamingPlaylists->VideoFiles', this.videoAttributes.getFileAttributes()) | ||
98 | } | ||
99 | } | ||
100 | |||
101 | protected includeUserHistory (userId: number) { | ||
102 | this.joins.push( | ||
103 | 'LEFT OUTER JOIN "userVideoHistory" ' + | ||
104 | 'ON "video"."id" = "userVideoHistory"."videoId" AND "userVideoHistory"."userId" = :userVideoHistoryId' | ||
105 | ) | ||
106 | |||
107 | this.replacements.userVideoHistoryId = userId | ||
108 | |||
109 | this.attributes = { | ||
110 | ...this.attributes, | ||
111 | |||
112 | ...this.buildAttributesObject('userVideoHistory', this.videoAttributes.getUserHistoryAttributes()) | ||
113 | } | ||
114 | } | ||
115 | |||
116 | protected includePlaylist (playlistId: number) { | ||
117 | this.joins.push( | ||
118 | 'INNER JOIN "videoPlaylistElement" as "VideoPlaylistElement" ON "videoPlaylistElement"."videoId" = "video"."id" ' + | ||
119 | 'AND "VideoPlaylistElement"."videoPlaylistId" = :videoPlaylistId' | ||
120 | ) | ||
121 | |||
122 | this.replacements.videoPlaylistId = playlistId | ||
123 | |||
124 | this.attributes = { | ||
125 | ...this.attributes, | ||
126 | |||
127 | ...this.buildAttributesObject('VideoPlaylistElement', this.videoAttributes.getPlaylistAttributes()) | ||
128 | } | ||
129 | } | ||
130 | |||
131 | protected includeTags () { | ||
132 | this.joins.push( | ||
133 | 'LEFT OUTER JOIN (' + | ||
134 | '"videoTag" AS "Tags->VideoTagModel" INNER JOIN "tag" AS "Tags" ON "Tags"."id" = "Tags->VideoTagModel"."tagId"' + | ||
135 | ') ' + | ||
136 | 'ON "video"."id" = "Tags->VideoTagModel"."videoId"' | ||
137 | ) | ||
138 | |||
139 | this.attributes = { | ||
140 | ...this.attributes, | ||
141 | |||
142 | ...this.buildAttributesObject('Tags', this.videoAttributes.getTagAttributes()), | ||
143 | ...this.buildAttributesObject('Tags->VideoTagModel', this.videoAttributes.getVideoTagAttributes()) | ||
144 | } | ||
145 | } | ||
146 | |||
147 | protected includeBlacklisted () { | ||
148 | this.joins.push( | ||
149 | 'LEFT OUTER JOIN "videoBlacklist" AS "VideoBlacklist" ON "video"."id" = "VideoBlacklist"."videoId"' | ||
150 | ) | ||
151 | |||
152 | this.attributes = { | ||
153 | ...this.attributes, | ||
154 | |||
155 | ...this.buildAttributesObject('VideoBlacklist', this.videoAttributes.getBlacklistedAttributes()) | ||
156 | } | ||
157 | } | ||
158 | |||
159 | protected includeScheduleUpdate () { | ||
160 | this.joins.push( | ||
161 | 'LEFT OUTER JOIN "scheduleVideoUpdate" AS "ScheduleVideoUpdate" ON "video"."id" = "ScheduleVideoUpdate"."videoId"' | ||
162 | ) | ||
163 | |||
164 | this.attributes = { | ||
165 | ...this.attributes, | ||
166 | |||
167 | ...this.buildAttributesObject('ScheduleVideoUpdate', this.videoAttributes.getScheduleUpdateAttributes()) | ||
168 | } | ||
169 | } | ||
170 | |||
171 | protected includeLive () { | ||
172 | this.joins.push( | ||
173 | 'LEFT OUTER JOIN "videoLive" AS "VideoLive" ON "video"."id" = "VideoLive"."videoId"' | ||
174 | ) | ||
175 | |||
176 | this.attributes = { | ||
177 | ...this.attributes, | ||
178 | |||
179 | ...this.buildAttributesObject('VideoLive', this.videoAttributes.getLiveAttributes()) | ||
180 | } | ||
181 | } | ||
182 | |||
183 | protected includeTrackers () { | ||
184 | this.joins.push( | ||
185 | 'LEFT OUTER JOIN (' + | ||
186 | '"videoTracker" AS "Trackers->VideoTrackerModel" ' + | ||
187 | 'INNER JOIN "tracker" AS "Trackers" ON "Trackers"."id" = "Trackers->VideoTrackerModel"."trackerId"' + | ||
188 | ') ON "video"."id" = "Trackers->VideoTrackerModel"."videoId"' | ||
189 | ) | ||
190 | |||
191 | this.attributes = { | ||
192 | ...this.attributes, | ||
193 | |||
194 | ...this.buildAttributesObject('Trackers', this.videoAttributes.getTrackerAttributes()), | ||
195 | ...this.buildAttributesObject('Trackers->VideoTrackerModel', this.videoAttributes.getVideoTrackerAttributes()) | ||
196 | } | ||
197 | } | ||
198 | |||
199 | protected includeRedundancies () { | ||
200 | this.joins.push( | ||
201 | 'LEFT OUTER JOIN "videoRedundancy" AS "VideoStreamingPlaylists->RedundancyVideos" ' + | ||
202 | 'ON "VideoStreamingPlaylists"."id" = "VideoStreamingPlaylists->RedundancyVideos"."videoStreamingPlaylistId"', | ||
203 | |||
204 | 'LEFT OUTER JOIN "videoRedundancy" AS "VideoFiles->RedundancyVideos" ON ' + | ||
205 | '"VideoFiles"."id" = "VideoFiles->RedundancyVideos"."videoFileId"' | ||
206 | ) | ||
207 | |||
208 | this.attributes = { | ||
209 | ...this.attributes, | ||
210 | |||
211 | ...this.buildAttributesObject('VideoFiles->RedundancyVideos', this.videoAttributes.getRedundancyAttributes()), | ||
212 | ...this.buildAttributesObject('VideoStreamingPlaylists->RedundancyVideos', this.videoAttributes.getRedundancyAttributes()) | ||
213 | } | ||
214 | } | ||
215 | |||
216 | protected buildActorInclude (prefixKey: string) { | ||
217 | return this.buildAttributesObject(prefixKey, this.videoAttributes.getActorAttributes()) | ||
218 | } | ||
219 | |||
220 | protected buildAvatarInclude (prefixKey: string) { | ||
221 | return this.buildAttributesObject(prefixKey, this.videoAttributes.getAvatarAttributes()) | ||
222 | } | ||
223 | |||
224 | protected buildServerInclude (prefixKey: string) { | ||
225 | return this.buildAttributesObject(prefixKey, this.videoAttributes.getServerAttributes()) | ||
226 | } | ||
227 | |||
228 | protected buildAttributesObject (prefixKey: string, attributeKeys: string[]) { | ||
229 | const result: { [id: string]: string} = {} | ||
230 | |||
231 | const prefixValue = prefixKey.replace(/->/g, '.') | ||
232 | |||
233 | for (const attribute of attributeKeys) { | ||
234 | result[`"${prefixKey}"."${attribute}"`] = `"${prefixValue}.${attribute}"` | ||
235 | } | ||
236 | |||
237 | return result | ||
238 | } | ||
239 | } | ||
diff --git a/server/models/video/sql/shared/abstract-videos-query-builder.ts b/server/models/video/sql/shared/abstract-videos-query-builder.ts new file mode 100644 index 000000000..01694e691 --- /dev/null +++ b/server/models/video/sql/shared/abstract-videos-query-builder.ts | |||
@@ -0,0 +1,22 @@ | |||
1 | import { QueryTypes, Sequelize, Transaction } from 'sequelize' | ||
2 | import { logger } from '@server/helpers/logger' | ||
3 | |||
4 | export class AbstractVideosQueryBuilder { | ||
5 | protected sequelize: Sequelize | ||
6 | |||
7 | protected query: string | ||
8 | protected replacements: any = {} | ||
9 | |||
10 | protected runQuery (transaction?: Transaction, nest?: boolean) { | ||
11 | logger.debug('Running videos query.', { query: this.query, replacements: this.replacements }) | ||
12 | |||
13 | const options = { | ||
14 | transaction, | ||
15 | replacements: this.replacements, | ||
16 | type: QueryTypes.SELECT as QueryTypes.SELECT, | ||
17 | nest | ||
18 | } | ||
19 | |||
20 | return this.sequelize.query<any>(this.query, options) | ||
21 | } | ||
22 | } | ||
diff --git a/server/models/video/sql/shared/video-attributes.ts b/server/models/video/sql/shared/video-attributes.ts new file mode 100644 index 000000000..1a1650dc7 --- /dev/null +++ b/server/models/video/sql/shared/video-attributes.ts | |||
@@ -0,0 +1,247 @@ | |||
1 | export class VideoAttributes { | ||
2 | |||
3 | constructor (readonly mode: 'get' | 'list') { | ||
4 | |||
5 | } | ||
6 | |||
7 | getChannelAttributes () { | ||
8 | let attributeKeys = [ | ||
9 | 'id', | ||
10 | 'name', | ||
11 | 'description', | ||
12 | 'actorId' | ||
13 | ] | ||
14 | |||
15 | if (this.mode === 'get') { | ||
16 | attributeKeys = attributeKeys.concat([ | ||
17 | 'support', | ||
18 | 'createdAt', | ||
19 | 'updatedAt' | ||
20 | ]) | ||
21 | } | ||
22 | |||
23 | return attributeKeys | ||
24 | } | ||
25 | |||
26 | getAccountAttributes () { | ||
27 | let attributeKeys = [ 'id', 'name', 'actorId' ] | ||
28 | |||
29 | if (this.mode === 'get') { | ||
30 | attributeKeys = attributeKeys.concat([ | ||
31 | 'description', | ||
32 | 'createdAt', | ||
33 | 'updatedAt' | ||
34 | ]) | ||
35 | } | ||
36 | |||
37 | return attributeKeys | ||
38 | } | ||
39 | |||
40 | getThumbnailAttributes () { | ||
41 | let attributeKeys = [ 'id', 'type', 'filename' ] | ||
42 | |||
43 | if (this.mode === 'get') { | ||
44 | attributeKeys = attributeKeys.concat([ | ||
45 | 'height', | ||
46 | 'width', | ||
47 | 'fileUrl', | ||
48 | 'automaticallyGenerated', | ||
49 | 'videoId', | ||
50 | 'videoPlaylistId', | ||
51 | 'createdAt', | ||
52 | 'updatedAt' | ||
53 | ]) | ||
54 | } | ||
55 | |||
56 | return attributeKeys | ||
57 | } | ||
58 | |||
59 | getFileAttributes () { | ||
60 | return [ | ||
61 | 'id', | ||
62 | 'createdAt', | ||
63 | 'updatedAt', | ||
64 | 'resolution', | ||
65 | 'size', | ||
66 | 'extname', | ||
67 | 'filename', | ||
68 | 'fileUrl', | ||
69 | 'torrentFilename', | ||
70 | 'torrentUrl', | ||
71 | 'infoHash', | ||
72 | 'fps', | ||
73 | 'metadataUrl', | ||
74 | 'videoStreamingPlaylistId', | ||
75 | 'videoId' | ||
76 | ] | ||
77 | } | ||
78 | |||
79 | getStreamingPlaylistAttributes () { | ||
80 | let playlistKeys = [ 'id', 'playlistUrl', 'type' ] | ||
81 | |||
82 | if (this.mode === 'get') { | ||
83 | playlistKeys = playlistKeys.concat([ | ||
84 | 'p2pMediaLoaderInfohashes', | ||
85 | 'p2pMediaLoaderPeerVersion', | ||
86 | 'segmentsSha256Url', | ||
87 | 'videoId', | ||
88 | 'createdAt', | ||
89 | 'updatedAt' | ||
90 | ]) | ||
91 | } | ||
92 | |||
93 | return playlistKeys | ||
94 | } | ||
95 | |||
96 | getUserHistoryAttributes () { | ||
97 | return [ 'id', 'currentTime' ] | ||
98 | } | ||
99 | |||
100 | getPlaylistAttributes () { | ||
101 | return [ | ||
102 | 'createdAt', | ||
103 | 'updatedAt', | ||
104 | 'url', | ||
105 | 'position', | ||
106 | 'startTimestamp', | ||
107 | 'stopTimestamp', | ||
108 | 'videoPlaylistId' | ||
109 | ] | ||
110 | } | ||
111 | |||
112 | getTagAttributes () { | ||
113 | return [ 'id', 'name' ] | ||
114 | } | ||
115 | |||
116 | getVideoTagAttributes () { | ||
117 | return [ 'videoId', 'tagId', 'createdAt', 'updatedAt' ] | ||
118 | } | ||
119 | |||
120 | getBlacklistedAttributes () { | ||
121 | return [ 'id', 'reason', 'unfederated' ] | ||
122 | } | ||
123 | |||
124 | getScheduleUpdateAttributes () { | ||
125 | return [ | ||
126 | 'id', | ||
127 | 'updateAt', | ||
128 | 'privacy', | ||
129 | 'videoId', | ||
130 | 'createdAt', | ||
131 | 'updatedAt' | ||
132 | ] | ||
133 | } | ||
134 | |||
135 | getLiveAttributes () { | ||
136 | return [ | ||
137 | 'id', | ||
138 | 'streamKey', | ||
139 | 'saveReplay', | ||
140 | 'permanentLive', | ||
141 | 'videoId', | ||
142 | 'createdAt', | ||
143 | 'updatedAt' | ||
144 | ] | ||
145 | } | ||
146 | |||
147 | getTrackerAttributes () { | ||
148 | return [ 'id', 'url' ] | ||
149 | } | ||
150 | |||
151 | getVideoTrackerAttributes () { | ||
152 | return [ | ||
153 | 'videoId', | ||
154 | 'trackerId', | ||
155 | 'createdAt', | ||
156 | 'updatedAt' | ||
157 | ] | ||
158 | } | ||
159 | |||
160 | getRedundancyAttributes () { | ||
161 | return [ 'id', 'fileUrl' ] | ||
162 | } | ||
163 | |||
164 | getActorAttributes () { | ||
165 | let attributeKeys = [ | ||
166 | 'id', | ||
167 | 'preferredUsername', | ||
168 | 'url', | ||
169 | 'serverId', | ||
170 | 'avatarId' | ||
171 | ] | ||
172 | |||
173 | if (this.mode === 'get') { | ||
174 | attributeKeys = attributeKeys.concat([ | ||
175 | 'type', | ||
176 | 'followersCount', | ||
177 | 'followingCount', | ||
178 | 'inboxUrl', | ||
179 | 'outboxUrl', | ||
180 | 'sharedInboxUrl', | ||
181 | 'followersUrl', | ||
182 | 'followingUrl', | ||
183 | 'remoteCreatedAt', | ||
184 | 'createdAt', | ||
185 | 'updatedAt' | ||
186 | ]) | ||
187 | } | ||
188 | |||
189 | return attributeKeys | ||
190 | } | ||
191 | |||
192 | getAvatarAttributes () { | ||
193 | let attributeKeys = [ | ||
194 | 'id', | ||
195 | 'filename', | ||
196 | 'fileUrl', | ||
197 | 'onDisk', | ||
198 | 'createdAt', | ||
199 | 'updatedAt' | ||
200 | ] | ||
201 | |||
202 | if (this.mode === 'get') { | ||
203 | attributeKeys = attributeKeys.concat([ | ||
204 | 'height', | ||
205 | 'width', | ||
206 | 'type' | ||
207 | ]) | ||
208 | } | ||
209 | |||
210 | return attributeKeys | ||
211 | } | ||
212 | |||
213 | getServerAttributes () { | ||
214 | return [ 'id', 'host' ] | ||
215 | } | ||
216 | |||
217 | getVideoAttributes () { | ||
218 | return [ | ||
219 | 'id', | ||
220 | 'uuid', | ||
221 | 'name', | ||
222 | 'category', | ||
223 | 'licence', | ||
224 | 'language', | ||
225 | 'privacy', | ||
226 | 'nsfw', | ||
227 | 'description', | ||
228 | 'support', | ||
229 | 'duration', | ||
230 | 'views', | ||
231 | 'likes', | ||
232 | 'dislikes', | ||
233 | 'remote', | ||
234 | 'isLive', | ||
235 | 'url', | ||
236 | 'commentsEnabled', | ||
237 | 'downloadEnabled', | ||
238 | 'waitTranscoding', | ||
239 | 'state', | ||
240 | 'publishedAt', | ||
241 | 'originallyPublishedAt', | ||
242 | 'channelId', | ||
243 | 'createdAt', | ||
244 | 'updatedAt' | ||
245 | ] | ||
246 | } | ||
247 | } | ||
diff --git a/server/models/video/sql/shared/video-model-builder.ts b/server/models/video/sql/shared/video-model-builder.ts new file mode 100644 index 000000000..9719f6d2e --- /dev/null +++ b/server/models/video/sql/shared/video-model-builder.ts | |||
@@ -0,0 +1,268 @@ | |||
1 | import { pick } from 'lodash' | ||
2 | import { AccountModel } from '@server/models/account/account' | ||
3 | import { ActorModel } from '@server/models/actor/actor' | ||
4 | import { ActorImageModel } from '@server/models/actor/actor-image' | ||
5 | import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy' | ||
6 | import { ServerModel } from '@server/models/server/server' | ||
7 | import { TrackerModel } from '@server/models/server/tracker' | ||
8 | import { UserVideoHistoryModel } from '@server/models/user/user-video-history' | ||
9 | import { ScheduleVideoUpdateModel } from '../../schedule-video-update' | ||
10 | import { TagModel } from '../../tag' | ||
11 | import { ThumbnailModel } from '../../thumbnail' | ||
12 | import { VideoModel } from '../../video' | ||
13 | import { VideoBlacklistModel } from '../../video-blacklist' | ||
14 | import { VideoChannelModel } from '../../video-channel' | ||
15 | import { VideoFileModel } from '../../video-file' | ||
16 | import { VideoLiveModel } from '../../video-live' | ||
17 | import { VideoStreamingPlaylistModel } from '../../video-streaming-playlist' | ||
18 | import { VideoAttributes } from './video-attributes' | ||
19 | |||
20 | export class VideoModelBuilder { | ||
21 | private videosMemo: { [ id: number ]: VideoModel } | ||
22 | private videoStreamingPlaylistMemo: { [ id: number ]: VideoStreamingPlaylistModel } | ||
23 | private videoFileMemo: { [ id: number ]: VideoFileModel } | ||
24 | |||
25 | private thumbnailsDone: Set<number> | ||
26 | private historyDone: Set<number> | ||
27 | private blacklistDone: Set<number> | ||
28 | private liveDone: Set<number> | ||
29 | private redundancyDone: Set<number> | ||
30 | private scheduleVideoUpdateDone: Set<number> | ||
31 | |||
32 | private trackersDone: Set<string> | ||
33 | private tagsDone: Set<string> | ||
34 | |||
35 | private videos: VideoModel[] | ||
36 | |||
37 | private readonly buildOpts = { raw: true, isNewRecord: false } | ||
38 | |||
39 | constructor ( | ||
40 | readonly mode: 'get' | 'list', | ||
41 | readonly videoAttributes: VideoAttributes | ||
42 | ) { | ||
43 | |||
44 | } | ||
45 | |||
46 | buildVideosFromRows (rows: any[]) { | ||
47 | this.reinit() | ||
48 | |||
49 | for (const row of rows) { | ||
50 | this.buildVideo(row) | ||
51 | |||
52 | const videoModel = this.videosMemo[row.id] | ||
53 | |||
54 | this.setUserHistory(row, videoModel) | ||
55 | this.addThumbnail(row, videoModel) | ||
56 | this.addWebTorrentFile(row, videoModel) | ||
57 | |||
58 | this.addStreamingPlaylist(row, videoModel) | ||
59 | this.addStreamingPlaylistFile(row) | ||
60 | |||
61 | if (this.mode === 'get') { | ||
62 | this.addTag(row, videoModel) | ||
63 | this.addTracker(row, videoModel) | ||
64 | this.setBlacklisted(row, videoModel) | ||
65 | this.setScheduleVideoUpdate(row, videoModel) | ||
66 | this.setLive(row, videoModel) | ||
67 | |||
68 | if (row.VideoFiles.id) { | ||
69 | this.addRedundancy(row.VideoFiles.RedundancyVideos, this.videoFileMemo[row.VideoFiles.id]) | ||
70 | } | ||
71 | |||
72 | if (row.VideoStreamingPlaylists.id) { | ||
73 | this.addRedundancy(row.VideoStreamingPlaylists.RedundancyVideos, this.videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id]) | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | return this.videos | ||
79 | } | ||
80 | |||
81 | private reinit () { | ||
82 | this.videosMemo = {} | ||
83 | this.videoStreamingPlaylistMemo = {} | ||
84 | this.videoFileMemo = {} | ||
85 | |||
86 | this.thumbnailsDone = new Set<number>() | ||
87 | this.historyDone = new Set<number>() | ||
88 | this.blacklistDone = new Set<number>() | ||
89 | this.liveDone = new Set<number>() | ||
90 | this.redundancyDone = new Set<number>() | ||
91 | this.scheduleVideoUpdateDone = new Set<number>() | ||
92 | |||
93 | this.trackersDone = new Set<string>() | ||
94 | this.tagsDone = new Set<string>() | ||
95 | |||
96 | this.videos = [] | ||
97 | } | ||
98 | |||
99 | private buildVideo (row: any) { | ||
100 | if (this.videosMemo[row.id]) return | ||
101 | |||
102 | // Build Channel | ||
103 | const channel = row.VideoChannel | ||
104 | const channelModel = new VideoChannelModel(pick(channel, this.videoAttributes.getChannelAttributes()), this.buildOpts) | ||
105 | channelModel.Actor = this.buildActor(channel.Actor) | ||
106 | |||
107 | const account = row.VideoChannel.Account | ||
108 | const accountModel = new AccountModel(pick(account, this.videoAttributes.getAccountAttributes()), this.buildOpts) | ||
109 | accountModel.Actor = this.buildActor(account.Actor) | ||
110 | |||
111 | channelModel.Account = accountModel | ||
112 | |||
113 | const videoModel = new VideoModel(pick(row, this.videoAttributes.getVideoAttributes()), this.buildOpts) | ||
114 | videoModel.VideoChannel = channelModel | ||
115 | |||
116 | this.videosMemo[row.id] = videoModel | ||
117 | |||
118 | videoModel.UserVideoHistories = [] | ||
119 | videoModel.Thumbnails = [] | ||
120 | videoModel.VideoFiles = [] | ||
121 | videoModel.VideoStreamingPlaylists = [] | ||
122 | videoModel.Tags = [] | ||
123 | videoModel.Trackers = [] | ||
124 | |||
125 | // Keep rows order | ||
126 | this.videos.push(videoModel) | ||
127 | } | ||
128 | |||
129 | private buildActor (rowActor: any) { | ||
130 | const avatarModel = rowActor.Avatar.id !== null | ||
131 | ? new ActorImageModel(pick(rowActor.Avatar, this.videoAttributes.getAvatarAttributes()), this.buildOpts) | ||
132 | : null | ||
133 | |||
134 | const serverModel = rowActor.Server.id !== null | ||
135 | ? new ServerModel(pick(rowActor.Server, this.videoAttributes.getServerAttributes()), this.buildOpts) | ||
136 | : null | ||
137 | |||
138 | const actorModel = new ActorModel(pick(rowActor, this.videoAttributes.getActorAttributes()), this.buildOpts) | ||
139 | actorModel.Avatar = avatarModel | ||
140 | actorModel.Server = serverModel | ||
141 | |||
142 | return actorModel | ||
143 | } | ||
144 | |||
145 | private setUserHistory (row: any, videoModel: VideoModel) { | ||
146 | if (!row.userVideoHistory?.id || this.historyDone.has(row.userVideoHistory.id)) return | ||
147 | |||
148 | const attributes = pick(row.userVideoHistory, this.videoAttributes.getUserHistoryAttributes()) | ||
149 | const historyModel = new UserVideoHistoryModel(attributes, this.buildOpts) | ||
150 | videoModel.UserVideoHistories.push(historyModel) | ||
151 | |||
152 | this.historyDone.add(row.userVideoHistory.id) | ||
153 | } | ||
154 | |||
155 | private addThumbnail (row: any, videoModel: VideoModel) { | ||
156 | if (!row.Thumbnails?.id || this.thumbnailsDone.has(row.Thumbnails.id)) return | ||
157 | |||
158 | const attributes = pick(row.Thumbnails, this.videoAttributes.getThumbnailAttributes()) | ||
159 | const thumbnailModel = new ThumbnailModel(attributes, this.buildOpts) | ||
160 | videoModel.Thumbnails.push(thumbnailModel) | ||
161 | |||
162 | this.thumbnailsDone.add(row.Thumbnails.id) | ||
163 | } | ||
164 | |||
165 | private addWebTorrentFile (row: any, videoModel: VideoModel) { | ||
166 | if (!row.VideoFiles?.id || this.videoFileMemo[row.VideoFiles.id]) return | ||
167 | |||
168 | const attributes = pick(row.VideoFiles, this.videoAttributes.getFileAttributes()) | ||
169 | const videoFileModel = new VideoFileModel(attributes, this.buildOpts) | ||
170 | videoModel.VideoFiles.push(videoFileModel) | ||
171 | |||
172 | this.videoFileMemo[row.VideoFiles.id] = videoFileModel | ||
173 | } | ||
174 | |||
175 | private addStreamingPlaylist (row: any, videoModel: VideoModel) { | ||
176 | if (!row.VideoStreamingPlaylists?.id || this.videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id]) return | ||
177 | |||
178 | const attributes = pick(row.VideoStreamingPlaylists, this.videoAttributes.getStreamingPlaylistAttributes()) | ||
179 | const streamingPlaylist = new VideoStreamingPlaylistModel(attributes, this.buildOpts) | ||
180 | streamingPlaylist.VideoFiles = [] | ||
181 | |||
182 | videoModel.VideoStreamingPlaylists.push(streamingPlaylist) | ||
183 | |||
184 | this.videoStreamingPlaylistMemo[streamingPlaylist.id] = streamingPlaylist | ||
185 | } | ||
186 | |||
187 | private addStreamingPlaylistFile (row: any) { | ||
188 | if (!row.VideoStreamingPlaylists?.VideoFiles?.id || this.videoFileMemo[row.VideoStreamingPlaylists.VideoFiles.id]) return | ||
189 | |||
190 | const streamingPlaylist = this.videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id] | ||
191 | |||
192 | const attributes = pick(row.VideoStreamingPlaylists.VideoFiles, this.videoAttributes.getFileAttributes()) | ||
193 | const videoFileModel = new VideoFileModel(attributes, this.buildOpts) | ||
194 | streamingPlaylist.VideoFiles.push(videoFileModel) | ||
195 | |||
196 | this.videoFileMemo[row.VideoStreamingPlaylists.VideoFiles.id] = videoFileModel | ||
197 | } | ||
198 | |||
199 | private addRedundancy (redundancyRow: any, to: VideoFileModel | VideoStreamingPlaylistModel) { | ||
200 | if (!to.RedundancyVideos) to.RedundancyVideos = [] | ||
201 | |||
202 | if (!redundancyRow?.id || this.redundancyDone.has(redundancyRow.id)) return | ||
203 | |||
204 | const attributes = pick(redundancyRow, this.videoAttributes.getRedundancyAttributes()) | ||
205 | const redundancyModel = new VideoRedundancyModel(attributes, this.buildOpts) | ||
206 | to.RedundancyVideos.push(redundancyModel) | ||
207 | |||
208 | this.redundancyDone.add(redundancyRow.id) | ||
209 | } | ||
210 | |||
211 | private addTag (row: any, videoModel: VideoModel) { | ||
212 | if (!row.Tags?.name) return | ||
213 | const association = row.Tags.VideoTagModel | ||
214 | |||
215 | const key = `${association.videoId}-${association.tagId}` | ||
216 | if (this.tagsDone.has(key)) return | ||
217 | |||
218 | const attributes = pick(row.Tags, this.videoAttributes.getTagAttributes()) | ||
219 | const tagModel = new TagModel(attributes, this.buildOpts) | ||
220 | videoModel.Tags.push(tagModel) | ||
221 | |||
222 | this.tagsDone.add(key) | ||
223 | } | ||
224 | |||
225 | private addTracker (row: any, videoModel: VideoModel) { | ||
226 | if (!row.Trackers?.id) return | ||
227 | const association = row.Trackers.VideoTrackerModel | ||
228 | |||
229 | const key = `${association.videoId}-${association.trackerId}` | ||
230 | if (this.trackersDone.has(key)) return | ||
231 | |||
232 | const attributes = pick(row.Trackers, this.videoAttributes.getTrackerAttributes()) | ||
233 | const trackerModel = new TrackerModel(attributes, this.buildOpts) | ||
234 | videoModel.Trackers.push(trackerModel) | ||
235 | |||
236 | this.trackersDone.add(key) | ||
237 | } | ||
238 | |||
239 | private setBlacklisted (row: any, videoModel: VideoModel) { | ||
240 | if (!row.VideoBlacklist?.id) return | ||
241 | if (this.blacklistDone.has(row.VideoBlacklist.id)) return | ||
242 | |||
243 | const attributes = pick(row.VideoBlacklist, this.videoAttributes.getBlacklistedAttributes()) | ||
244 | videoModel.VideoBlacklist = new VideoBlacklistModel(attributes, this.buildOpts) | ||
245 | |||
246 | this.blacklistDone.add(row.VideoBlacklist.id) | ||
247 | } | ||
248 | |||
249 | private setScheduleVideoUpdate (row: any, videoModel: VideoModel) { | ||
250 | if (!row.ScheduleVideoUpdate?.id) return | ||
251 | if (this.scheduleVideoUpdateDone.has(row.ScheduleVideoUpdate.id)) return | ||
252 | |||
253 | const attributes = pick(row.ScheduleVideoUpdate, this.videoAttributes.getScheduleUpdateAttributes()) | ||
254 | videoModel.ScheduleVideoUpdate = new ScheduleVideoUpdateModel(attributes, this.buildOpts) | ||
255 | |||
256 | this.scheduleVideoUpdateDone.add(row.ScheduleVideoUpdate.id) | ||
257 | } | ||
258 | |||
259 | private setLive (row: any, videoModel: VideoModel) { | ||
260 | if (!row.VideoLive?.id) return | ||
261 | if (this.liveDone.has(row.VideoLive.id)) return | ||
262 | |||
263 | const attributes = pick(row.VideoLive, this.videoAttributes.getLiveAttributes()) | ||
264 | videoModel.VideoLive = new VideoLiveModel(attributes, this.buildOpts) | ||
265 | |||
266 | this.liveDone.add(row.ScheduleVideoUpdate.id) | ||
267 | } | ||
268 | } | ||