]>
Commit | Line | Data |
---|---|---|
a1587156 C |
1 | // eslint-disable @typescript-eslint/no-unnecessary-type-assertion |
2 | ||
26fcf2ef C |
3 | import { registerTSPaths } from '../helpers/register-ts-paths' |
4 | registerTSPaths() | |
5 | ||
6 | import * as program from 'commander' | |
7 | import { getAdminTokenOrDie, getServerCredentials } from './cli' | |
8 | import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' | |
9 | import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy } from '@shared/extra-utils/server/redundancy' | |
f2eb23cd | 10 | import { HttpStatusCode } from '@shared/core-utils/miscs/http-error-codes' |
26fcf2ef | 11 | import validator from 'validator' |
26fcf2ef | 12 | import * as CliTable3 from 'cli-table3' |
a1587156 | 13 | import { URL } from 'url' |
26fcf2ef C |
14 | import { uniq } from 'lodash' |
15 | ||
a1587156 | 16 | import bytes = require('bytes') |
ba5a8d89 | 17 | import commander = require('commander') |
a1587156 | 18 | |
26fcf2ef C |
19 | program |
20 | .name('plugins') | |
21 | .usage('[command] [options]') | |
22 | ||
23 | program | |
24 | .command('list-remote-redundancies') | |
25 | .description('List remote redundancies on your videos') | |
26 | .option('-u, --url <url>', 'Server url') | |
27 | .option('-U, --username <username>', 'Username') | |
28 | .option('-p, --password <token>', 'Password') | |
29 | .action(() => listRedundanciesCLI('my-videos')) | |
30 | ||
31 | program | |
32 | .command('list-my-redundancies') | |
33 | .description('List your redundancies of remote videos') | |
34 | .option('-u, --url <url>', 'Server url') | |
35 | .option('-U, --username <username>', 'Username') | |
36 | .option('-p, --password <token>', 'Password') | |
37 | .action(() => listRedundanciesCLI('remote-videos')) | |
38 | ||
39 | program | |
40 | .command('add') | |
41 | .description('Duplicate a video in your redundancy system') | |
42 | .option('-u, --url <url>', 'Server url') | |
43 | .option('-U, --username <username>', 'Username') | |
44 | .option('-p, --password <token>', 'Password') | |
45 | .option('-v, --video <videoId>', 'Video id to duplicate') | |
ba5a8d89 | 46 | .action((options, command) => addRedundancyCLI(options, command)) |
26fcf2ef C |
47 | |
48 | program | |
49 | .command('remove') | |
50 | .description('Remove a video from your redundancies') | |
51 | .option('-u, --url <url>', 'Server url') | |
52 | .option('-U, --username <username>', 'Username') | |
53 | .option('-p, --password <token>', 'Password') | |
54 | .option('-v, --video <videoId>', 'Video id to remove from redundancies') | |
ba5a8d89 | 55 | .action((options, command) => removeRedundancyCLI(options, command)) |
26fcf2ef C |
56 | |
57 | if (!process.argv.slice(2).length) { | |
58 | program.outputHelp() | |
59 | } | |
60 | ||
61 | program.parse(process.argv) | |
62 | ||
63 | // ---------------------------------------------------------------------------- | |
64 | ||
65 | async function listRedundanciesCLI (target: VideoRedundanciesTarget) { | |
66 | const { url, username, password } = await getServerCredentials(program) | |
67 | const accessToken = await getAdminTokenOrDie(url, username, password) | |
68 | ||
69 | const redundancies = await listVideoRedundanciesData(url, accessToken, target) | |
70 | ||
71 | const table = new CliTable3({ | |
72 | head: [ 'video id', 'video name', 'video url', 'files', 'playlists', 'by instances', 'total size' ] | |
a1587156 | 73 | }) as any |
26fcf2ef C |
74 | |
75 | for (const redundancy of redundancies) { | |
76 | const webtorrentFiles = redundancy.redundancies.files | |
77 | const streamingPlaylists = redundancy.redundancies.streamingPlaylists | |
78 | ||
79 | let totalSize = '' | |
80 | if (target === 'remote-videos') { | |
81 | const tmp = webtorrentFiles.concat(streamingPlaylists) | |
82 | .reduce((a, b) => a + b.size, 0) | |
83 | ||
84 | totalSize = bytes(tmp) | |
85 | } | |
86 | ||
87 | const instances = uniq( | |
88 | webtorrentFiles.concat(streamingPlaylists) | |
89 | .map(r => r.fileUrl) | |
a1587156 | 90 | .map(u => new URL(u).host) |
26fcf2ef C |
91 | ) |
92 | ||
93 | table.push([ | |
94 | redundancy.id.toString(), | |
95 | redundancy.name, | |
96 | redundancy.url, | |
97 | webtorrentFiles.length, | |
98 | streamingPlaylists.length, | |
99 | instances.join('\n'), | |
100 | totalSize | |
101 | ]) | |
102 | } | |
103 | ||
104 | console.log(table.toString()) | |
105 | process.exit(0) | |
106 | } | |
107 | ||
ba5a8d89 C |
108 | async function addRedundancyCLI (options: { video: number }, command: commander.CommanderStatic) { |
109 | const { url, username, password } = await getServerCredentials(command) | |
26fcf2ef C |
110 | const accessToken = await getAdminTokenOrDie(url, username, password) |
111 | ||
ba5a8d89 | 112 | if (!options.video || validator.isInt('' + options.video) === false) { |
26fcf2ef | 113 | console.error('You need to specify the video id to duplicate and it should be a number.\n') |
ba5a8d89 | 114 | command.outputHelp() |
26fcf2ef C |
115 | process.exit(-1) |
116 | } | |
117 | ||
118 | try { | |
119 | await addVideoRedundancy({ | |
120 | url, | |
121 | accessToken, | |
ba5a8d89 | 122 | videoId: options.video |
26fcf2ef C |
123 | }) |
124 | ||
125 | console.log('Video will be duplicated by your instance!') | |
126 | ||
127 | process.exit(0) | |
128 | } catch (err) { | |
f2eb23cd | 129 | if (err.message.includes(HttpStatusCode.CONFLICT_409)) { |
26fcf2ef | 130 | console.error('This video is already duplicated by your instance.') |
f2eb23cd | 131 | } else if (err.message.includes(HttpStatusCode.NOT_FOUND_404)) { |
26fcf2ef C |
132 | console.error('This video id does not exist.') |
133 | } else { | |
134 | console.error(err) | |
135 | } | |
136 | ||
137 | process.exit(-1) | |
138 | } | |
139 | } | |
140 | ||
ba5a8d89 C |
141 | async function removeRedundancyCLI (options: { video: number }, command: commander.CommanderStatic) { |
142 | const { url, username, password } = await getServerCredentials(command) | |
26fcf2ef C |
143 | const accessToken = await getAdminTokenOrDie(url, username, password) |
144 | ||
ba5a8d89 | 145 | if (!options.video || validator.isInt('' + options.video) === false) { |
26fcf2ef | 146 | console.error('You need to specify the video id to remove from your redundancies.\n') |
ba5a8d89 | 147 | command.outputHelp() |
26fcf2ef C |
148 | process.exit(-1) |
149 | } | |
150 | ||
ba5a8d89 | 151 | const videoId = parseInt(options.video + '', 10) |
26fcf2ef C |
152 | |
153 | let redundancies = await listVideoRedundanciesData(url, accessToken, 'my-videos') | |
154 | let videoRedundancy = redundancies.find(r => videoId === r.id) | |
155 | ||
156 | if (!videoRedundancy) { | |
157 | redundancies = await listVideoRedundanciesData(url, accessToken, 'remote-videos') | |
158 | videoRedundancy = redundancies.find(r => videoId === r.id) | |
159 | } | |
160 | ||
161 | if (!videoRedundancy) { | |
162 | console.error('Video redundancy not found.') | |
163 | process.exit(-1) | |
164 | } | |
165 | ||
166 | try { | |
167 | const ids = videoRedundancy.redundancies.files | |
168 | .concat(videoRedundancy.redundancies.streamingPlaylists) | |
169 | .map(r => r.id) | |
170 | ||
171 | for (const id of ids) { | |
172 | await removeVideoRedundancy({ | |
173 | url, | |
174 | accessToken, | |
175 | redundancyId: id | |
176 | }) | |
177 | } | |
178 | ||
179 | console.log('Video redundancy removed!') | |
180 | ||
181 | process.exit(0) | |
182 | } catch (err) { | |
183 | console.error(err) | |
184 | process.exit(-1) | |
185 | } | |
186 | } | |
187 | ||
188 | async function listVideoRedundanciesData (url: string, accessToken: string, target: VideoRedundanciesTarget) { | |
189 | const res = await listVideoRedundancies({ | |
190 | url, | |
191 | accessToken, | |
192 | start: 0, | |
193 | count: 100, | |
194 | sort: 'name', | |
195 | target | |
196 | }) | |
197 | ||
198 | return res.body.data as VideoRedundancy[] | |
199 | } |