aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-09-11 16:27:07 +0200
committerChocobozzz <me@florianbigard.com>2018-09-13 14:05:49 +0200
commitc48e82b5e0478434de30626d14594a97f2402e7c (patch)
treea78e5272bd0fe4f5b41831e571e02d05f1515b82 /server/models
parenta651038487faa838bda3ce04695b08bc65baff70 (diff)
downloadPeerTube-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.ts4
-rw-r--r--server/models/activitypub/actor.ts13
-rw-r--r--server/models/redundancy/video-redundancy.ts249
-rw-r--r--server/models/server/server.ts17
-rw-r--r--server/models/video/video-file.ts25
-rw-r--r--server/models/video/video.ts73
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'
21import { FollowState } from '../../../shared/models/actors' 21import { FollowState } from '../../../shared/models/actors'
22import { AccountFollow } from '../../../shared/models/actors/follow.model' 22import { ActorFollow } from '../../../shared/models/actors/follow.model'
23import { logger } from '../../helpers/logger' 23import { logger } from '../../helpers/logger'
24import { getServerActor } from '../../helpers/utils' 24import { getServerActor } from '../../helpers/utils'
25import { ACTOR_FOLLOW_SCORE } from '../../initializers' 25import { 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 @@
1import {
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'
16import { ActorModel } from '../activitypub/actor'
17import { throwIfNotValid } from '../utils'
18import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
19import { CONSTRAINTS_FIELDS, VIDEO_EXT_MIMETYPE } from '../../initializers'
20import { VideoFileModel } from '../video/video-file'
21import { isDateValid } from '../../helpers/custom-validators/misc'
22import { getServerActor } from '../../helpers/utils'
23import { VideoModel } from '../video/video'
24import { VideoRedundancyStrategy } from '../../../shared/models/redundancy'
25import { logger } from '../../helpers/logger'
26import { CacheFileObject } from '../../../shared'
27import { VideoChannelModel } from '../video/video-channel'
28import { ServerModel } from '../server/server'
29import { sample } from 'lodash'
30import { isTestInstance } from '../../helpers/core-utils'
31
32export 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})
68export 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 @@
1import { AllowNull, Column, CreatedAt, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { isHostValid } from '../../helpers/custom-validators/servers' 2import { isHostValid } from '../../helpers/custom-validators/servers'
3import { ActorModel } from '../activitypub/actor' 3import { ActorModel } from '../activitypub/actor'
4import { throwIfNotValid } from '../utils' 4import { 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 @@
1import { values } from 'lodash' 1import { values } from 'lodash'
2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import {
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'
3import { 16import {
4 isVideoFileInfoHashValid, 17 isVideoFileInfoHashValid,
5 isVideoFileResolutionValid, 18 isVideoFileResolutionValid,
@@ -10,6 +23,7 @@ import { CONSTRAINTS_FIELDS } from '../../initializers'
10import { throwIfNotValid } from '../utils' 23import { throwIfNotValid } from '../utils'
11import { VideoModel } from './video' 24import { VideoModel } from './video'
12import * as Sequelize from 'sequelize' 25import * as Sequelize from 'sequelize'
26import { 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'
30import { VideoPrivacy, VideoResolution, VideoState } from '../../../shared' 30import { ActivityUrlObject, VideoPrivacy, VideoResolution, VideoState } from '../../../shared'
31import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' 31import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
32import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' 32import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
33import { VideoFilter } from '../../../shared/models/videos/video-query.type' 33import { VideoFilter } from '../../../shared/models/videos/video-query.type'
34import { createTorrentPromise, peertubeTruncate } from '../../helpers/core-utils' 34import { createTorrentPromise, peertubeTruncate } from '../../helpers/core-utils'
35import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 35import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
36import { isBooleanValid } from '../../helpers/custom-validators/misc' 36import { isArray, isBooleanValid } from '../../helpers/custom-validators/misc'
37import { 37import {
38 isVideoCategoryValid, 38 isVideoCategoryValid,
39 isVideoDescriptionValid, 39 isVideoDescriptionValid,
@@ -90,6 +90,7 @@ import { VideoCaptionModel } from './video-caption'
90import { VideoBlacklistModel } from './video-blacklist' 90import { VideoBlacklistModel } from './video-blacklist'
91import { copy, remove, rename, stat, writeFile } from 'fs-extra' 91import { copy, remove, rename, stat, writeFile } from 'fs-extra'
92import { VideoViewModel } from './video-views' 92import { VideoViewModel } from './video-views'
93import { 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
95const indexes: Sequelize.DefineIndexesOptions[] = [ 96const 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}