]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - server/helpers/core-utils.ts
Introduce subscriptions command
[github/Chocobozzz/PeerTube.git] / server / helpers / core-utils.ts
... / ...
CommitLineData
1/* eslint-disable no-useless-call */
2
3/*
4 Different from 'utils' because we don't import other PeerTube modules.
5 Useful to avoid circular dependencies.
6*/
7
8import { exec, ExecOptions } from 'child_process'
9import { BinaryToTextEncoding, createHash, randomBytes } from 'crypto'
10import { truncate } from 'lodash'
11import { basename, extname, isAbsolute, join, resolve } from 'path'
12import * as pem from 'pem'
13import { pipeline } from 'stream'
14import { URL } from 'url'
15import { promisify } from 'util'
16
17const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => {
18 if (!oldObject || typeof oldObject !== 'object') {
19 return valueConverter(oldObject)
20 }
21
22 if (Array.isArray(oldObject)) {
23 return oldObject.map(e => objectConverter(e, keyConverter, valueConverter))
24 }
25
26 const newObject = {}
27 Object.keys(oldObject).forEach(oldKey => {
28 const newKey = keyConverter(oldKey)
29 newObject[newKey] = objectConverter(oldObject[oldKey], keyConverter, valueConverter)
30 })
31
32 return newObject
33}
34
35function mapToJSON (map: Map<any, any>) {
36 const obj: any = {}
37
38 for (const [ k, v ] of map) {
39 obj[k] = v
40 }
41
42 return obj
43}
44
45// ---------------------------------------------------------------------------
46
47const timeTable = {
48 ms: 1,
49 second: 1000,
50 minute: 60000,
51 hour: 3600000,
52 day: 3600000 * 24,
53 week: 3600000 * 24 * 7,
54 month: 3600000 * 24 * 30
55}
56
57export function parseDurationToMs (duration: number | string): number {
58 if (duration === null) return null
59 if (typeof duration === 'number') return duration
60
61 if (typeof duration === 'string') {
62 const split = duration.match(/^([\d.,]+)\s?(\w+)$/)
63
64 if (split.length === 3) {
65 const len = parseFloat(split[1])
66 let unit = split[2].replace(/s$/i, '').toLowerCase()
67 if (unit === 'm') {
68 unit = 'ms'
69 }
70
71 return (len || 1) * (timeTable[unit] || 0)
72 }
73 }
74
75 throw new Error(`Duration ${duration} could not be properly parsed`)
76}
77
78export function parseBytes (value: string | number): number {
79 if (typeof value === 'number') return value
80
81 const tgm = /^(\d+)\s*TB\s*(\d+)\s*GB\s*(\d+)\s*MB$/
82 const tg = /^(\d+)\s*TB\s*(\d+)\s*GB$/
83 const tm = /^(\d+)\s*TB\s*(\d+)\s*MB$/
84 const gm = /^(\d+)\s*GB\s*(\d+)\s*MB$/
85 const t = /^(\d+)\s*TB$/
86 const g = /^(\d+)\s*GB$/
87 const m = /^(\d+)\s*MB$/
88 const b = /^(\d+)\s*B$/
89 let match
90
91 if (value.match(tgm)) {
92 match = value.match(tgm)
93 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
94 parseInt(match[2], 10) * 1024 * 1024 * 1024 +
95 parseInt(match[3], 10) * 1024 * 1024
96 } else if (value.match(tg)) {
97 match = value.match(tg)
98 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
99 parseInt(match[2], 10) * 1024 * 1024 * 1024
100 } else if (value.match(tm)) {
101 match = value.match(tm)
102 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
103 parseInt(match[2], 10) * 1024 * 1024
104 } else if (value.match(gm)) {
105 match = value.match(gm)
106 return parseInt(match[1], 10) * 1024 * 1024 * 1024 +
107 parseInt(match[2], 10) * 1024 * 1024
108 } else if (value.match(t)) {
109 match = value.match(t)
110 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
111 } else if (value.match(g)) {
112 match = value.match(g)
113 return parseInt(match[1], 10) * 1024 * 1024 * 1024
114 } else if (value.match(m)) {
115 match = value.match(m)
116 return parseInt(match[1], 10) * 1024 * 1024
117 } else if (value.match(b)) {
118 match = value.match(b)
119 return parseInt(match[1], 10) * 1024
120 } else {
121 return parseInt(value, 10)
122 }
123}
124
125// ---------------------------------------------------------------------------
126
127function sanitizeUrl (url: string) {
128 const urlObject = new URL(url)
129
130 if (urlObject.protocol === 'https:' && urlObject.port === '443') {
131 urlObject.port = ''
132 } else if (urlObject.protocol === 'http:' && urlObject.port === '80') {
133 urlObject.port = ''
134 }
135
136 return urlObject.href.replace(/\/$/, '')
137}
138
139// Don't import remote scheme from constants because we are in core utils
140function sanitizeHost (host: string, remoteScheme: string) {
141 const toRemove = remoteScheme === 'https' ? 443 : 80
142
143 return host.replace(new RegExp(`:${toRemove}$`), '')
144}
145
146// ---------------------------------------------------------------------------
147
148function isTestInstance () {
149 return process.env.NODE_ENV === 'test'
150}
151
152function isProdInstance () {
153 return process.env.NODE_ENV === 'production'
154}
155
156function getAppNumber () {
157 return process.env.NODE_APP_INSTANCE
158}
159
160// ---------------------------------------------------------------------------
161
162let rootPath: string
163
164function root () {
165 if (rootPath) return rootPath
166
167 // We are in /helpers/utils.js
168 rootPath = join(__dirname, '..', '..')
169
170 if (basename(rootPath) === 'dist') rootPath = resolve(rootPath, '..')
171
172 return rootPath
173}
174
175function buildPath (path: string) {
176 if (isAbsolute(path)) return path
177
178 return join(root(), path)
179}
180
181function getLowercaseExtension (filename: string) {
182 const ext = extname(filename) || ''
183
184 return ext.toLowerCase()
185}
186
187// ---------------------------------------------------------------------------
188
189// Consistent with .length, lodash truncate function is not
190function peertubeTruncate (str: string, options: { length: number, separator?: RegExp, omission?: string }) {
191 const truncatedStr = truncate(str, options)
192
193 // The truncated string is okay, we can return it
194 if (truncatedStr.length <= options.length) return truncatedStr
195
196 // Lodash takes into account all UTF characters, whereas String.prototype.length does not: some characters have a length of 2
197 // We always use the .length so we need to truncate more if needed
198 options.length -= truncatedStr.length - options.length
199 return truncate(str, options)
200}
201
202function pageToStartAndCount (page: number, itemsPerPage: number) {
203 const start = (page - 1) * itemsPerPage
204
205 return { start, count: itemsPerPage }
206}
207
208// ---------------------------------------------------------------------------
209
210type SemVersion = { major: number, minor: number, patch: number }
211function parseSemVersion (s: string) {
212 const parsed = s.match(/^v?(\d+)\.(\d+)\.(\d+)$/i)
213
214 return {
215 major: parseInt(parsed[1]),
216 minor: parseInt(parsed[2]),
217 patch: parseInt(parsed[3])
218 } as SemVersion
219}
220
221// ---------------------------------------------------------------------------
222
223function sha256 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
224 return createHash('sha256').update(str).digest(encoding)
225}
226
227function sha1 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
228 return createHash('sha1').update(str).digest(encoding)
229}
230
231// ---------------------------------------------------------------------------
232
233function execShell (command: string, options?: ExecOptions) {
234 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
235 exec(command, options, (err, stdout, stderr) => {
236 // eslint-disable-next-line prefer-promise-reject-errors
237 if (err) return rej({ err, stdout, stderr })
238
239 return res({ stdout, stderr })
240 })
241 })
242}
243
244// ---------------------------------------------------------------------------
245
246function isOdd (num: number) {
247 return (num % 2) !== 0
248}
249
250function toEven (num: number) {
251 if (isOdd(num)) return num + 1
252
253 return num
254}
255
256// ---------------------------------------------------------------------------
257
258function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
259 return function promisified (): Promise<A> {
260 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
261 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
262 })
263 }
264}
265
266// Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
267function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
268 return function promisified (arg: T): Promise<A> {
269 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
270 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
271 })
272 }
273}
274
275function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
276 return function promisified (arg1: T, arg2: U): Promise<A> {
277 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
278 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
279 })
280 }
281}
282
283const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
284const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
285const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
286const execPromise2 = promisify2<string, any, string>(exec)
287const execPromise = promisify1<string, string>(exec)
288const pipelinePromise = promisify(pipeline)
289
290// ---------------------------------------------------------------------------
291
292export {
293 isTestInstance,
294 isProdInstance,
295 getAppNumber,
296
297 objectConverter,
298 mapToJSON,
299
300 root,
301 buildPath,
302 getLowercaseExtension,
303 sanitizeUrl,
304 sanitizeHost,
305
306 execShell,
307
308 pageToStartAndCount,
309 peertubeTruncate,
310
311 sha256,
312 sha1,
313
314 promisify0,
315 promisify1,
316 promisify2,
317
318 randomBytesPromise,
319 createPrivateKey,
320 getPublicKey,
321 execPromise2,
322 execPromise,
323 pipelinePromise,
324
325 parseSemVersion,
326
327 isOdd,
328 toEven
329}