UNPKG

4.25 kBJavaScriptView Raw
1const cp = require('child_process')
2const EOL = require('os').EOL
3
4function writeStdIn(proc, data, encoding) {
5 // console.log('write stdin', data)
6 proc.stdin.write(data, encoding)
7 proc.stdin.write(EOL, encoding)
8}
9
10function close(proc) {
11 return new Promise((resolve) => {
12 proc.on('close', resolve)
13 writeStdIn(proc, '-stay_open')
14 writeStdIn(proc, 'false')
15 })
16}
17
18function isString(s) {
19 return (typeof s).toLowerCase() === 'string'
20}
21
22/**
23 * Get arguments. Split by new line to write to exiftool
24 */
25function getArgs(args, noSplit) {
26 if(!(Array.isArray(args) && args.length)) {
27 return []
28 }
29 return args
30 .filter(isString)
31 .map(arg => `-${arg}`)
32 .reduce((acc, arg) =>
33 [].concat(acc, noSplit ? [arg] : arg.split(/\s+/))
34 , [])
35}
36
37/**
38 * Write command data to the exiftool's stdin.
39 * @param {ChildProcess} process - exiftool process executed with -stay_open True -@ -
40 * @param {string} command - which command to execute
41 * @param {string} commandNumber - text which will be echoed before and after results
42 * @param {string[]} args - any additional arguments
43 * @param {string[]} noSplitArgs - arguments which should not be broken up like args
44 * @param {string} encoding - which encoding to write in. default no encoding
45 */
46function execute(proc, command, commandNumber, args, noSplitArgs, encoding) {
47 const extendedArgs = getArgs(args)
48 const extendedArgsNoSplit = getArgs(noSplitArgs, true)
49
50 command = command !== undefined ? command : '';
51
52 [].concat(
53 extendedArgsNoSplit,
54 extendedArgs,
55 ['-json', '-s'],
56 [
57 command,
58 '-echo1',
59 `{begin${commandNumber}}`,
60 '-echo2',
61 `{begin${commandNumber}}`,
62 '-echo4',
63 `{ready${commandNumber}}`,
64 `-execute${commandNumber}`,
65 ]
66 )
67 .forEach(arg => writeStdIn(proc, arg, encoding))
68}
69
70function genCommandNumber() {
71 return String(Math.floor(Math.random() * 1000000))
72}
73
74function executeCommand(proc, stdoutRws, stderrRws, command, args, noSplitArgs, encoding) {
75 const commandNumber = genCommandNumber()
76
77 if (proc === process) { // debugging
78 execute(proc, command, commandNumber, args, noSplitArgs, encoding)
79 return Promise.resolve({ data: 'debug', error: null })
80 }
81
82 const dataPromise = new Promise(resolve => {
83 stdoutRws.addToResolveMap(commandNumber, resolve)
84 })
85 const errPromise = new Promise(resolve =>
86 stderrRws.addToResolveMap(commandNumber, resolve)
87 )
88
89 execute(proc, command, commandNumber, args, noSplitArgs, encoding)
90
91 return Promise.all([
92 dataPromise,
93 errPromise,
94 ])
95 .then(res => ({
96 data: res[0] ? JSON.parse(res[0]) : null,
97 error: res[1] || null,
98 }))
99}
100
101function spawn(bin) {
102 return new Promise((resolve, reject) => {
103 const echoString = Date.now().toString()
104 const process = cp.spawn(bin, ['-echo2', echoString, '-stay_open', 'True', '-@', '-'])
105 process.once('error', reject)
106 const echoHandler = (data) => {
107 const d = data.toString().trim()
108 // listening for echo2 in stderr (echo and echo1 won't work)
109 if (d === echoString) {
110 resolve(process)
111 } else {
112 reject(new Error(`Unexpected string on start: ${d}`))
113 }
114 }
115 process.stderr.once('data', echoHandler)
116 })
117}
118
119function checkDataObject(data) {
120 return data === Object(data) && !Array.isArray(data)
121}
122
123function mapDataToTagArray(data, array) {
124 const res = Array.isArray(array) ? array : []
125 Object
126 .keys(data)
127 .forEach(tag => {
128 const value = data[tag]
129 if (Array.isArray(value)) {
130 value.forEach((v) => {
131 const arg = `${tag}=${v}`
132 res.push(arg)
133 })
134 } else {
135 res.push(`${tag}=${value}`)
136 }
137 })
138 return res
139}
140
141module.exports = {
142 spawn,
143 close,
144 executeCommand,
145 checkDataObject,
146 mapDataToTagArray,
147 getArgs,
148 execute,
149 isString,
150}