main
ts 94 lines 3.51 KB
Raw
1 import { dirname } from 'path'
2 import { existsSync, statfsSync } from 'fs'
3 import { exec, execFile, ExecOptions } from 'child_process'
4 import { exists, isWindowsDrive, onlyTruthy, promiseBestEffort } from './misc'
5 import Parser from '@gregoranders/csv';
6 import { pid, ppid } from 'node:process'
7 import { promisify } from 'util'
8 import { IS_WINDOWS } from './const'
9 import { statfs } from 'node:fs/promises'
10
11 const DF_TIMEOUT = 2000
12
13 export function getDiskSpaceSync(path: string) {
14 while (path && !isWindowsDrive(path) && !existsSync(path))
15 path = dirname(path)
16 const res = statfsSync(path)
17 return { free: res.bavail * res.bsize, total: res.blocks * res.bsize, name: path }
18 }
19
20 export function bashEscape(par: string) {
21 return `'${par.replaceAll(/(["'$`\\])/g, "\\$1")}'`
22 }
23
24 export function cmdEscape(par: string) {
25 return `"${par.replaceAll('"', '\\"')}"`
26 }
27
28 export async function getDiskSpace(path: string) {
29 while (path && !isWindowsDrive(path) && !await exists(path))
30 path = dirname(path)
31 const res = await statfs(path)
32 return { free: res.bavail * res.bsize, total: res.blocks * res.bsize, name: path }
33 }
34
35 export async function getDiskSpaces(): Promise<{ name: string, free: number, total: number, description?: string }[]> {
36 if (IS_WINDOWS) {
37 const drives = await getDrives()
38 return onlyTruthy(await promiseBestEffort(drives.map(getDiskSpace)))
39 }
40 return parseDfResult(await promisify(exec)(`df -k`, { timeout: DF_TIMEOUT }).then(x => x.stdout, e => e))
41 }
42
43 function parseDfResult(result: string | Error) {
44 if (result instanceof Error) {
45 const { status } = result as any
46 throw status === 1 ? Error('miss') : status === 127 ? Error('unsupported') : result
47 }
48 const out = result.split('\n')
49 if (!out.shift()?.startsWith('Filesystem'))
50 throw Error('unsupported')
51 return onlyTruthy(out.map(one => {
52 const bits = one.split(/\s+/)
53 if (bits[0] === 'tempfs') return
54 const name = bits.pop() || ''
55 if (/^\/(dev|sys|run|System\/Volumes\/(VM|Preboot|Update|xarts|iSCPreboot|Hardware))\b/.test(name)) return
56 const [used=0, free=0] = bits.map(x => Number(x) * 1024).slice(2)
57 const total = used + free
58 return total && { free, total, name }
59 }))
60 }
61
62 export async function getDrives() {
63 const res = await runCmd('fsutil fsinfo drives') // example output: `Drives: C:\ D:\ Z:\`
64 return res.trim().replaceAll('\\', '').split(' ').slice(1)
65 }
66
67 // execute win32 shell commands
68 export async function runCmd(cmd: string, args: string[] = [], options: ExecOptions = {}) {
69 const line = `@chcp 65001 >nul & cmd /c ${cmd} ${args.map(x => x.includes(' ') ? `"${x}"` : x).join(' ')}`
70 const { stdout, stderr } = await promisify(exec)(line, { encoding: 'utf-8', ...options })
71 return (stderr || stdout).replace(/\r/g, '')
72 }
73
74 // returns pid-to-name object
75 async function getWindowsServicePids() {
76 const res = await runCmd('tasklist /svc /fo csv')
77 const parsed = new Parser().parse(res)
78 const no = parsed?.[1]?.[2]
79 return Object.fromEntries(parsed.slice(2).filter(x => x[2] !== no).map(x => [x[1], x[2]]))
80 }
81
82 export const runningAsWindowsService = IS_WINDOWS && getWindowsServicePids().then(x => {
83 const ret = x[pid] || x[ppid]
84 if (ret)
85 console.log("Running as service", ret)
86 return ret
87 }, e => {
88 console.log("Couldn't determine if we are running as a service")
89 console.debug(e)
90 })
91
92 export function reg(...pars: string[]) {
93 return promisify(execFile)('reg', pars).then(x => x.stdout)
94 }