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