diff options
author | Chocobozzz <me@florianbigard.com> | 2018-09-11 16:27:07 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-09-13 14:05:49 +0200 |
commit | c48e82b5e0478434de30626d14594a97f2402e7c (patch) | |
tree | a78e5272bd0fe4f5b41831e571e02d05f1515b82 /server/models | |
parent | a651038487faa838bda3ce04695b08bc65baff70 (diff) | |
download | PeerTube-c48e82b5e0478434de30626d14594a97f2402e7c.tar.gz PeerTube-c48e82b5e0478434de30626d14594a97f2402e7c.tar.zst PeerTube-c48e82b5e0478434de30626d14594a97f2402e7c.zip |
Basic video redundancy implementation
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/activitypub/actor-follow.ts | 4 | ||||
-rw-r--r-- | server/models/activitypub/actor.ts | 13 | ||||
-rw-r--r-- | server/models/redundancy/video-redundancy.ts | 249 | ||||
-rw-r--r-- | server/models/server/server.ts | 17 | ||||
-rw-r--r-- | server/models/video/video-file.ts | 25 | ||||
-rw-r--r-- | server/models/video/video.ts | 73 |
6 files changed, 344 insertions, 37 deletions
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index 8bc095997..27bb43dae 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -19,7 +19,7 @@ import { | |||
19 | UpdatedAt | 19 | UpdatedAt |
20 | } from 'sequelize-typescript' | 20 | } from 'sequelize-typescript' |
21 | import { FollowState } from '../../../shared/models/actors' | 21 | import { FollowState } from '../../../shared/models/actors' |
22 | import { AccountFollow } from '../../../shared/models/actors/follow.model' | 22 | import { ActorFollow } from '../../../shared/models/actors/follow.model' |
23 | import { logger } from '../../helpers/logger' | 23 | import { logger } from '../../helpers/logger' |
24 | import { getServerActor } from '../../helpers/utils' | 24 | import { getServerActor } from '../../helpers/utils' |
25 | import { ACTOR_FOLLOW_SCORE } from '../../initializers' | 25 | import { ACTOR_FOLLOW_SCORE } from '../../initializers' |
@@ -529,7 +529,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
529 | return ActorFollowModel.findAll(query) | 529 | return ActorFollowModel.findAll(query) |
530 | } | 530 | } |
531 | 531 | ||
532 | toFormattedJSON (): AccountFollow { | 532 | toFormattedJSON (): ActorFollow { |
533 | const follower = this.ActorFollower.toFormattedJSON() | 533 | const follower = this.ActorFollower.toFormattedJSON() |
534 | const following = this.ActorFollowing.toFormattedJSON() | 534 | const following = this.ActorFollowing.toFormattedJSON() |
535 | 535 | ||
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 119d0c1da..ef8dd9f7c 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -76,7 +76,13 @@ export const unusedActorAttributesForAPI = [ | |||
76 | }, | 76 | }, |
77 | { | 77 | { |
78 | model: () => VideoChannelModel.unscoped(), | 78 | model: () => VideoChannelModel.unscoped(), |
79 | required: false | 79 | required: false, |
80 | include: [ | ||
81 | { | ||
82 | model: () => AccountModel, | ||
83 | required: true | ||
84 | } | ||
85 | ] | ||
80 | }, | 86 | }, |
81 | { | 87 | { |
82 | model: () => ServerModel, | 88 | model: () => ServerModel, |
@@ -337,6 +343,7 @@ export class ActorModel extends Model<ActorModel> { | |||
337 | uuid: this.uuid, | 343 | uuid: this.uuid, |
338 | name: this.preferredUsername, | 344 | name: this.preferredUsername, |
339 | host: this.getHost(), | 345 | host: this.getHost(), |
346 | hostRedundancyAllowed: this.getRedundancyAllowed(), | ||
340 | followingCount: this.followingCount, | 347 | followingCount: this.followingCount, |
341 | followersCount: this.followersCount, | 348 | followersCount: this.followersCount, |
342 | avatar, | 349 | avatar, |
@@ -440,6 +447,10 @@ export class ActorModel extends Model<ActorModel> { | |||
440 | return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST | 447 | return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST |
441 | } | 448 | } |
442 | 449 | ||
450 | getRedundancyAllowed () { | ||
451 | return this.Server ? this.Server.redundancyAllowed : false | ||
452 | } | ||
453 | |||
443 | getAvatarUrl () { | 454 | getAvatarUrl () { |
444 | if (!this.avatarId) return undefined | 455 | if (!this.avatarId) return undefined |
445 | 456 | ||
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts new file mode 100644 index 000000000..48ec77206 --- /dev/null +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -0,0 +1,249 @@ | |||
1 | import { | ||
2 | AfterDestroy, | ||
3 | AllowNull, | ||
4 | BelongsTo, | ||
5 | Column, | ||
6 | CreatedAt, | ||
7 | DataType, | ||
8 | ForeignKey, | ||
9 | Is, | ||
10 | Model, | ||
11 | Scopes, | ||
12 | Sequelize, | ||
13 | Table, | ||
14 | UpdatedAt | ||
15 | } from 'sequelize-typescript' | ||
16 | import { ActorModel } from '../activitypub/actor' | ||
17 | import { throwIfNotValid } from '../utils' | ||
18 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' | ||
19 | import { CONSTRAINTS_FIELDS, VIDEO_EXT_MIMETYPE } from '../../initializers' | ||
20 | import { VideoFileModel } from '../video/video-file' | ||
21 | import { isDateValid } from '../../helpers/custom-validators/misc' | ||
22 | import { getServerActor } from '../../helpers/utils' | ||
23 | import { VideoModel } from '../video/video' | ||
24 | import { VideoRedundancyStrategy } from '../../../shared/models/redundancy' | ||
25 | import { logger } from '../../helpers/logger' | ||
26 | import { CacheFileObject } from '../../../shared' | ||
27 | import { VideoChannelModel } from '../video/video-channel' | ||
28 | import { ServerModel } from '../server/server' | ||
29 | import { sample } from 'lodash' | ||
30 | import { isTestInstance } from '../../helpers/core-utils' | ||
31 | |||
32 | export enum ScopeNames { | ||
33 | WITH_VIDEO = 'WITH_VIDEO' | ||
34 | } | ||
35 | |||
36 | @Scopes({ | ||
37 | [ ScopeNames.WITH_VIDEO ]: { | ||
38 | include: [ | ||
39 | { | ||
40 | model: () => VideoFileModel, | ||
41 | required: true, | ||
42 | include: [ | ||
43 | { | ||
44 | model: () => VideoModel, | ||
45 | required: true | ||
46 | } | ||
47 | ] | ||
48 | } | ||
49 | ] | ||
50 | } | ||
51 | }) | ||
52 | |||
53 | @Table({ | ||
54 | tableName: 'videoRedundancy', | ||
55 | indexes: [ | ||
56 | { | ||
57 | fields: [ 'videoFileId' ] | ||
58 | }, | ||
59 | { | ||
60 | fields: [ 'actorId' ] | ||
61 | }, | ||
62 | { | ||
63 | fields: [ 'url' ], | ||
64 | unique: true | ||
65 | } | ||
66 | ] | ||
67 | }) | ||
68 | export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | ||
69 | |||
70 | @CreatedAt | ||
71 | createdAt: Date | ||
72 | |||
73 | @UpdatedAt | ||
74 | updatedAt: Date | ||
75 | |||
76 | @AllowNull(false) | ||
77 | @Column | ||
78 | expiresOn: Date | ||
79 | |||
80 | @AllowNull(false) | ||
81 | @Is('VideoRedundancyFileUrl', value => throwIfNotValid(value, isUrlValid, 'fileUrl')) | ||
82 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS_REDUNDANCY.URL.max)) | ||
83 | fileUrl: string | ||
84 | |||
85 | @AllowNull(false) | ||
86 | @Is('VideoRedundancyUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) | ||
87 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS_REDUNDANCY.URL.max)) | ||
88 | url: string | ||
89 | |||
90 | @AllowNull(true) | ||
91 | @Column | ||
92 | strategy: string // Only used by us | ||
93 | |||
94 | @ForeignKey(() => VideoFileModel) | ||
95 | @Column | ||
96 | videoFileId: number | ||
97 | |||
98 | @BelongsTo(() => VideoFileModel, { | ||
99 | foreignKey: { | ||
100 | allowNull: false | ||
101 | }, | ||
102 | onDelete: 'cascade' | ||
103 | }) | ||
104 | VideoFile: VideoFileModel | ||
105 | |||
106 | @ForeignKey(() => ActorModel) | ||
107 | @Column | ||
108 | actorId: number | ||
109 | |||
110 | @BelongsTo(() => ActorModel, { | ||
111 | foreignKey: { | ||
112 | allowNull: false | ||
113 | }, | ||
114 | onDelete: 'cascade' | ||
115 | }) | ||
116 | Actor: ActorModel | ||
117 | |||
118 | @AfterDestroy | ||
119 | static removeFilesAndSendDelete (instance: VideoRedundancyModel) { | ||
120 | // Not us | ||
121 | if (!instance.strategy) return | ||
122 | |||
123 | logger.info('Removing video file %s-.', instance.VideoFile.Video.uuid, instance.VideoFile.resolution) | ||
124 | |||
125 | return instance.VideoFile.Video.removeFile(instance.VideoFile) | ||
126 | } | ||
127 | |||
128 | static loadByFileId (videoFileId: number) { | ||
129 | const query = { | ||
130 | where: { | ||
131 | videoFileId | ||
132 | } | ||
133 | } | ||
134 | |||
135 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | ||
136 | } | ||
137 | |||
138 | static loadByUrl (url: string) { | ||
139 | const query = { | ||
140 | where: { | ||
141 | url | ||
142 | } | ||
143 | } | ||
144 | |||
145 | return VideoRedundancyModel.findOne(query) | ||
146 | } | ||
147 | |||
148 | static async findMostViewToDuplicate (randomizedFactor: number) { | ||
149 | // On VideoModel! | ||
150 | const query = { | ||
151 | logging: !isTestInstance(), | ||
152 | limit: randomizedFactor, | ||
153 | order: [ [ 'views', 'DESC' ] ], | ||
154 | include: [ | ||
155 | { | ||
156 | model: VideoFileModel.unscoped(), | ||
157 | required: true, | ||
158 | where: { | ||
159 | id: { | ||
160 | [ Sequelize.Op.notIn ]: await VideoRedundancyModel.buildExcludeIn() | ||
161 | } | ||
162 | } | ||
163 | }, | ||
164 | { | ||
165 | attributes: [], | ||
166 | model: VideoChannelModel.unscoped(), | ||
167 | required: true, | ||
168 | include: [ | ||
169 | { | ||
170 | attributes: [], | ||
171 | model: ActorModel.unscoped(), | ||
172 | required: true, | ||
173 | include: [ | ||
174 | { | ||
175 | attributes: [], | ||
176 | model: ServerModel.unscoped(), | ||
177 | required: true, | ||
178 | where: { | ||
179 | redundancyAllowed: true | ||
180 | } | ||
181 | } | ||
182 | ] | ||
183 | } | ||
184 | ] | ||
185 | } | ||
186 | ] | ||
187 | } | ||
188 | |||
189 | const rows = await VideoModel.unscoped().findAll(query) | ||
190 | |||
191 | return sample(rows) | ||
192 | } | ||
193 | |||
194 | static async getVideoFiles (strategy: VideoRedundancyStrategy) { | ||
195 | const actor = await getServerActor() | ||
196 | |||
197 | const queryVideoFiles = { | ||
198 | logging: !isTestInstance(), | ||
199 | where: { | ||
200 | actorId: actor.id, | ||
201 | strategy | ||
202 | } | ||
203 | } | ||
204 | |||
205 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO) | ||
206 | .findAll(queryVideoFiles) | ||
207 | } | ||
208 | |||
209 | static listAllExpired () { | ||
210 | const query = { | ||
211 | logging: !isTestInstance(), | ||
212 | where: { | ||
213 | expiresOn: { | ||
214 | [Sequelize.Op.lt]: new Date() | ||
215 | } | ||
216 | } | ||
217 | } | ||
218 | |||
219 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO) | ||
220 | .findAll(query) | ||
221 | } | ||
222 | |||
223 | toActivityPubObject (): CacheFileObject { | ||
224 | return { | ||
225 | id: this.url, | ||
226 | type: 'CacheFile' as 'CacheFile', | ||
227 | object: this.VideoFile.Video.url, | ||
228 | expires: this.expiresOn.toISOString(), | ||
229 | url: { | ||
230 | type: 'Link', | ||
231 | mimeType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any, | ||
232 | href: this.fileUrl, | ||
233 | height: this.VideoFile.resolution, | ||
234 | size: this.VideoFile.size, | ||
235 | fps: this.VideoFile.fps | ||
236 | } | ||
237 | } | ||
238 | } | ||
239 | |||
240 | private static async buildExcludeIn () { | ||
241 | const actor = await getServerActor() | ||
242 | |||
243 | return Sequelize.literal( | ||
244 | '(' + | ||
245 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "expiresOn" >= NOW()` + | ||
246 | ')' | ||
247 | ) | ||
248 | } | ||
249 | } | ||
diff --git a/server/models/server/server.ts b/server/models/server/server.ts index 9749f503e..ca3b24d51 100644 --- a/server/models/server/server.ts +++ b/server/models/server/server.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { AllowNull, Column, CreatedAt, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { isHostValid } from '../../helpers/custom-validators/servers' | 2 | import { isHostValid } from '../../helpers/custom-validators/servers' |
3 | import { ActorModel } from '../activitypub/actor' | 3 | import { ActorModel } from '../activitypub/actor' |
4 | import { throwIfNotValid } from '../utils' | 4 | import { throwIfNotValid } from '../utils' |
@@ -19,6 +19,11 @@ export class ServerModel extends Model<ServerModel> { | |||
19 | @Column | 19 | @Column |
20 | host: string | 20 | host: string |
21 | 21 | ||
22 | @AllowNull(false) | ||
23 | @Default(false) | ||
24 | @Column | ||
25 | redundancyAllowed: boolean | ||
26 | |||
22 | @CreatedAt | 27 | @CreatedAt |
23 | createdAt: Date | 28 | createdAt: Date |
24 | 29 | ||
@@ -34,4 +39,14 @@ export class ServerModel extends Model<ServerModel> { | |||
34 | hooks: true | 39 | hooks: true |
35 | }) | 40 | }) |
36 | Actors: ActorModel[] | 41 | Actors: ActorModel[] |
42 | |||
43 | static loadByHost (host: string) { | ||
44 | const query = { | ||
45 | where: { | ||
46 | host | ||
47 | } | ||
48 | } | ||
49 | |||
50 | return ServerModel.findOne(query) | ||
51 | } | ||
37 | } | 52 | } |
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index 3bc4855f3..0907ea569 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -1,5 +1,18 @@ | |||
1 | import { values } from 'lodash' | 1 | import { values } from 'lodash' |
2 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { |
3 | AllowNull, | ||
4 | BelongsTo, | ||
5 | Column, | ||
6 | CreatedAt, | ||
7 | DataType, | ||
8 | Default, | ||
9 | ForeignKey, | ||
10 | HasMany, | ||
11 | Is, | ||
12 | Model, | ||
13 | Table, | ||
14 | UpdatedAt | ||
15 | } from 'sequelize-typescript' | ||
3 | import { | 16 | import { |
4 | isVideoFileInfoHashValid, | 17 | isVideoFileInfoHashValid, |
5 | isVideoFileResolutionValid, | 18 | isVideoFileResolutionValid, |
@@ -10,6 +23,7 @@ import { CONSTRAINTS_FIELDS } from '../../initializers' | |||
10 | import { throwIfNotValid } from '../utils' | 23 | import { throwIfNotValid } from '../utils' |
11 | import { VideoModel } from './video' | 24 | import { VideoModel } from './video' |
12 | import * as Sequelize from 'sequelize' | 25 | import * as Sequelize from 'sequelize' |
26 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | ||
13 | 27 | ||
14 | @Table({ | 28 | @Table({ |
15 | tableName: 'videoFile', | 29 | tableName: 'videoFile', |
@@ -70,6 +84,15 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
70 | }) | 84 | }) |
71 | Video: VideoModel | 85 | Video: VideoModel |
72 | 86 | ||
87 | @HasMany(() => VideoRedundancyModel, { | ||
88 | foreignKey: { | ||
89 | allowNull: false | ||
90 | }, | ||
91 | onDelete: 'CASCADE', | ||
92 | hooks: true | ||
93 | }) | ||
94 | RedundancyVideos: VideoRedundancyModel[] | ||
95 | |||
73 | static isInfohashExists (infoHash: string) { | 96 | static isInfohashExists (infoHash: string) { |
74 | const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1' | 97 | const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1' |
75 | const options = { | 98 | const options = { |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 86316653f..27c631dcd 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -27,13 +27,13 @@ import { | |||
27 | Table, | 27 | Table, |
28 | UpdatedAt | 28 | UpdatedAt |
29 | } from 'sequelize-typescript' | 29 | } from 'sequelize-typescript' |
30 | import { VideoPrivacy, VideoResolution, VideoState } from '../../../shared' | 30 | import { ActivityUrlObject, VideoPrivacy, VideoResolution, VideoState } from '../../../shared' |
31 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 31 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
32 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | 32 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' |
33 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' | 33 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' |
34 | import { createTorrentPromise, peertubeTruncate } from '../../helpers/core-utils' | 34 | import { createTorrentPromise, peertubeTruncate } from '../../helpers/core-utils' |
35 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 35 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
36 | import { isBooleanValid } from '../../helpers/custom-validators/misc' | 36 | import { isArray, isBooleanValid } from '../../helpers/custom-validators/misc' |
37 | import { | 37 | import { |
38 | isVideoCategoryValid, | 38 | isVideoCategoryValid, |
39 | isVideoDescriptionValid, | 39 | isVideoDescriptionValid, |
@@ -90,6 +90,7 @@ import { VideoCaptionModel } from './video-caption' | |||
90 | import { VideoBlacklistModel } from './video-blacklist' | 90 | import { VideoBlacklistModel } from './video-blacklist' |
91 | import { copy, remove, rename, stat, writeFile } from 'fs-extra' | 91 | import { copy, remove, rename, stat, writeFile } from 'fs-extra' |
92 | import { VideoViewModel } from './video-views' | 92 | import { VideoViewModel } from './video-views' |
93 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | ||
93 | 94 | ||
94 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 95 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
95 | const indexes: Sequelize.DefineIndexesOptions[] = [ | 96 | const indexes: Sequelize.DefineIndexesOptions[] = [ |
@@ -470,7 +471,13 @@ type AvailableForListIDsOptions = { | |||
470 | include: [ | 471 | include: [ |
471 | { | 472 | { |
472 | model: () => VideoFileModel.unscoped(), | 473 | model: () => VideoFileModel.unscoped(), |
473 | required: false | 474 | required: false, |
475 | include: [ | ||
476 | { | ||
477 | model: () => VideoRedundancyModel.unscoped(), | ||
478 | required: false | ||
479 | } | ||
480 | ] | ||
474 | } | 481 | } |
475 | ] | 482 | ] |
476 | }, | 483 | }, |
@@ -633,6 +640,7 @@ export class VideoModel extends Model<VideoModel> { | |||
633 | name: 'videoId', | 640 | name: 'videoId', |
634 | allowNull: false | 641 | allowNull: false |
635 | }, | 642 | }, |
643 | hooks: true, | ||
636 | onDelete: 'cascade' | 644 | onDelete: 'cascade' |
637 | }) | 645 | }) |
638 | VideoFiles: VideoFileModel[] | 646 | VideoFiles: VideoFileModel[] |
@@ -1325,9 +1333,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1325 | [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ], | 1333 | [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ], |
1326 | [ CONFIG.WEBSERVER.URL + '/tracker/announce' ] | 1334 | [ CONFIG.WEBSERVER.URL + '/tracker/announce' ] |
1327 | ], | 1335 | ], |
1328 | urlList: [ | 1336 | urlList: [ CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) ] |
1329 | CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) | ||
1330 | ] | ||
1331 | } | 1337 | } |
1332 | 1338 | ||
1333 | const torrent = await createTorrentPromise(this.getVideoFilePath(videoFile), options) | 1339 | const torrent = await createTorrentPromise(this.getVideoFilePath(videoFile), options) |
@@ -1535,11 +1541,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1535 | } | 1541 | } |
1536 | } | 1542 | } |
1537 | 1543 | ||
1538 | const url = [] | 1544 | const url: ActivityUrlObject[] = [] |
1539 | for (const file of this.VideoFiles) { | 1545 | for (const file of this.VideoFiles) { |
1540 | url.push({ | 1546 | url.push({ |
1541 | type: 'Link', | 1547 | type: 'Link', |
1542 | mimeType: VIDEO_EXT_MIMETYPE[ file.extname ], | 1548 | mimeType: VIDEO_EXT_MIMETYPE[ file.extname ] as any, |
1543 | href: this.getVideoFileUrl(file, baseUrlHttp), | 1549 | href: this.getVideoFileUrl(file, baseUrlHttp), |
1544 | height: file.resolution, | 1550 | height: file.resolution, |
1545 | size: file.size, | 1551 | size: file.size, |
@@ -1548,14 +1554,14 @@ export class VideoModel extends Model<VideoModel> { | |||
1548 | 1554 | ||
1549 | url.push({ | 1555 | url.push({ |
1550 | type: 'Link', | 1556 | type: 'Link', |
1551 | mimeType: 'application/x-bittorrent', | 1557 | mimeType: 'application/x-bittorrent' as 'application/x-bittorrent', |
1552 | href: this.getTorrentUrl(file, baseUrlHttp), | 1558 | href: this.getTorrentUrl(file, baseUrlHttp), |
1553 | height: file.resolution | 1559 | height: file.resolution |
1554 | }) | 1560 | }) |
1555 | 1561 | ||
1556 | url.push({ | 1562 | url.push({ |
1557 | type: 'Link', | 1563 | type: 'Link', |
1558 | mimeType: 'application/x-bittorrent;x-scheme-handler/magnet', | 1564 | mimeType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet', |
1559 | href: this.generateMagnetUri(file, baseUrlHttp, baseUrlWs), | 1565 | href: this.generateMagnetUri(file, baseUrlHttp, baseUrlWs), |
1560 | height: file.resolution | 1566 | height: file.resolution |
1561 | }) | 1567 | }) |
@@ -1796,7 +1802,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1796 | (now - updatedAtTime) > ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL | 1802 | (now - updatedAtTime) > ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL |
1797 | } | 1803 | } |
1798 | 1804 | ||
1799 | private getBaseUrls () { | 1805 | getBaseUrls () { |
1800 | let baseUrlHttp | 1806 | let baseUrlHttp |
1801 | let baseUrlWs | 1807 | let baseUrlWs |
1802 | 1808 | ||
@@ -1811,39 +1817,42 @@ export class VideoModel extends Model<VideoModel> { | |||
1811 | return { baseUrlHttp, baseUrlWs } | 1817 | return { baseUrlHttp, baseUrlWs } |
1812 | } | 1818 | } |
1813 | 1819 | ||
1814 | private getThumbnailUrl (baseUrlHttp: string) { | 1820 | generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { |
1821 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) | ||
1822 | const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] | ||
1823 | let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] | ||
1824 | |||
1825 | const redundancies = videoFile.RedundancyVideos | ||
1826 | if (isArray(redundancies)) urlList = urlList.concat(redundancies.map(r => r.fileUrl)) | ||
1827 | |||
1828 | const magnetHash = { | ||
1829 | xs, | ||
1830 | announce, | ||
1831 | urlList, | ||
1832 | infoHash: videoFile.infoHash, | ||
1833 | name: this.name | ||
1834 | } | ||
1835 | |||
1836 | return magnetUtil.encode(magnetHash) | ||
1837 | } | ||
1838 | |||
1839 | getThumbnailUrl (baseUrlHttp: string) { | ||
1815 | return baseUrlHttp + STATIC_PATHS.THUMBNAILS + this.getThumbnailName() | 1840 | return baseUrlHttp + STATIC_PATHS.THUMBNAILS + this.getThumbnailName() |
1816 | } | 1841 | } |
1817 | 1842 | ||
1818 | private getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 1843 | getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) { |
1819 | return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) | 1844 | return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) |
1820 | } | 1845 | } |
1821 | 1846 | ||
1822 | private getTorrentDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 1847 | getTorrentDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { |
1823 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile) | 1848 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile) |
1824 | } | 1849 | } |
1825 | 1850 | ||
1826 | private getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 1851 | getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) { |
1827 | return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) | 1852 | return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) |
1828 | } | 1853 | } |
1829 | 1854 | ||
1830 | private getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 1855 | getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { |
1831 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) | 1856 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) |
1832 | } | 1857 | } |
1833 | |||
1834 | private generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { | ||
1835 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) | ||
1836 | const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] | ||
1837 | const urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] | ||
1838 | |||
1839 | const magnetHash = { | ||
1840 | xs, | ||
1841 | announce, | ||
1842 | urlList, | ||
1843 | infoHash: videoFile.infoHash, | ||
1844 | name: this.name | ||
1845 | } | ||
1846 | |||
1847 | return magnetUtil.encode(magnetHash) | ||
1848 | } | ||
1849 | } | 1858 | } |