dashcam 1.0.1-beta.30 → 1.0.1-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dashcam.js +2 -49
- package/lib/processManager.js +91 -106
- package/package.json +1 -1
package/bin/dashcam.js
CHANGED
|
@@ -136,55 +136,8 @@ async function recordingAction(options, command) {
|
|
|
136
136
|
log('Use "dashcam status" to check progress');
|
|
137
137
|
log('Use "dashcam stop" to stop recording and upload');
|
|
138
138
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
const handleShutdown = async (signal) => {
|
|
142
|
-
log(`\nReceived ${signal}, stopping recording...`);
|
|
143
|
-
try {
|
|
144
|
-
const result = await processManager.stopActiveRecording();
|
|
145
|
-
|
|
146
|
-
if (result) {
|
|
147
|
-
log('Recording stopped successfully');
|
|
148
|
-
log('Uploading recording...');
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const uploadResult = await upload(result.outputPath, {
|
|
152
|
-
title: options.title || 'Dashcam Recording',
|
|
153
|
-
description: description,
|
|
154
|
-
project: options.project || options.k,
|
|
155
|
-
duration: result.duration,
|
|
156
|
-
clientStartDate: result.clientStartDate,
|
|
157
|
-
apps: result.apps,
|
|
158
|
-
icons: result.icons,
|
|
159
|
-
gifPath: result.gifPath,
|
|
160
|
-
snapshotPath: result.snapshotPath
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// Write upload result for stop command to read
|
|
164
|
-
processManager.writeUploadResult({
|
|
165
|
-
shareLink: uploadResult.shareLink,
|
|
166
|
-
replayId: uploadResult.replay?.id
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
log('📹 Watch your recording:', uploadResult.shareLink);
|
|
170
|
-
} catch (uploadError) {
|
|
171
|
-
logError('Upload failed:', uploadError.message);
|
|
172
|
-
log('Recording saved locally:', result.outputPath);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
process.exit(0);
|
|
177
|
-
} catch (error) {
|
|
178
|
-
logError('Failed to stop recording:', error.message);
|
|
179
|
-
process.exit(1);
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
process.on('SIGINT', handleShutdown);
|
|
184
|
-
process.on('SIGTERM', handleShutdown);
|
|
185
|
-
|
|
186
|
-
// Keep process alive indefinitely until stopped
|
|
187
|
-
await new Promise(() => {}); // Wait forever
|
|
139
|
+
// Process can exit now - background process is detached
|
|
140
|
+
process.exit(0);
|
|
188
141
|
|
|
189
142
|
} catch (error) {
|
|
190
143
|
logError('Failed to start recording:', error.message);
|
package/lib/processManager.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
4
6
|
import { logger } from './logger.js';
|
|
5
7
|
|
|
6
8
|
// Use a fixed directory in the user's home directory for cross-process communication
|
|
@@ -151,91 +153,69 @@ class ProcessManager {
|
|
|
151
153
|
return false;
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
pid: process.pid
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
this.cleanup({ preserveResult: true });
|
|
172
|
-
return result;
|
|
156
|
+
logger.info('Stopping detached background process', { pid });
|
|
157
|
+
|
|
158
|
+
// Send signal to background process
|
|
159
|
+
const isWindows = process.platform === 'win32';
|
|
160
|
+
|
|
161
|
+
if (isWindows) {
|
|
162
|
+
logger.info('Windows detected, using taskkill to stop process');
|
|
163
|
+
try {
|
|
164
|
+
// Use taskkill to forcefully stop the process tree on Windows
|
|
165
|
+
const { execSync } = await import('child_process');
|
|
166
|
+
execSync(`taskkill /PID ${pid} /F /T`, { stdio: 'ignore' });
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.warn('Failed to kill process with taskkill', { error: error.message });
|
|
169
|
+
}
|
|
173
170
|
} else {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
171
|
+
try {
|
|
172
|
+
process.kill(pid, 'SIGINT');
|
|
173
|
+
} catch (error) {
|
|
174
|
+
logger.warn('Failed to send SIGINT', { error: error.message });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Wait for the process to actually finish
|
|
179
|
+
const maxWaitTime = 30000; // 30 seconds
|
|
180
|
+
const startWait = Date.now();
|
|
181
|
+
|
|
182
|
+
while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
|
|
183
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (this.isProcessRunning(pid)) {
|
|
187
|
+
logger.warn('Process did not stop within timeout');
|
|
188
|
+
if (!isWindows) {
|
|
190
189
|
try {
|
|
191
|
-
process.kill(pid, '
|
|
190
|
+
process.kill(pid, 'SIGKILL');
|
|
192
191
|
} catch (error) {
|
|
193
|
-
logger.warn('Failed to send
|
|
192
|
+
logger.warn('Failed to send SIGKILL', { error: error.message });
|
|
194
193
|
}
|
|
194
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
195
195
|
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (status) {
|
|
199
|
+
logger.info('Recording stopped, returning status', {
|
|
200
|
+
outputPath: status.outputPath,
|
|
201
|
+
duration: Date.now() - status.startTime
|
|
202
|
+
});
|
|
196
203
|
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (!isWindows) {
|
|
208
|
-
try {
|
|
209
|
-
process.kill(pid, 'SIGKILL');
|
|
210
|
-
} catch (error) {
|
|
211
|
-
logger.warn('Failed to send SIGKILL', { error: error.message });
|
|
212
|
-
}
|
|
213
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
214
|
-
}
|
|
215
|
-
}
|
|
204
|
+
const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
|
|
205
|
+
const result = {
|
|
206
|
+
outputPath: status.outputPath,
|
|
207
|
+
gifPath: `${basePath}.gif`,
|
|
208
|
+
snapshotPath: `${basePath}.png`,
|
|
209
|
+
duration: Date.now() - status.startTime,
|
|
210
|
+
clientStartDate: status.startTime,
|
|
211
|
+
apps: [],
|
|
212
|
+
logs: []
|
|
213
|
+
};
|
|
216
214
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
|
|
224
|
-
const result = {
|
|
225
|
-
outputPath: status.outputPath,
|
|
226
|
-
gifPath: `${basePath}.gif`,
|
|
227
|
-
snapshotPath: `${basePath}.png`,
|
|
228
|
-
duration: Date.now() - status.startTime,
|
|
229
|
-
clientStartDate: status.startTime,
|
|
230
|
-
apps: [],
|
|
231
|
-
logs: []
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
this.cleanup({ preserveResult: true });
|
|
235
|
-
return result;
|
|
236
|
-
} else {
|
|
237
|
-
throw new Error('No status information available for active recording');
|
|
238
|
-
}
|
|
215
|
+
this.cleanup({ preserveResult: true });
|
|
216
|
+
return result;
|
|
217
|
+
} else {
|
|
218
|
+
throw new Error('No status information available for active recording');
|
|
239
219
|
}
|
|
240
220
|
} catch (error) {
|
|
241
221
|
logger.error('Failed to stop recording', { error });
|
|
@@ -251,41 +231,46 @@ class ProcessManager {
|
|
|
251
231
|
throw new Error('Recording already in progress');
|
|
252
232
|
}
|
|
253
233
|
|
|
254
|
-
|
|
255
|
-
const { startRecording: startRecorderRecording } = await import('./recorder.js');
|
|
234
|
+
logger.info('Starting recording in detached background process');
|
|
256
235
|
|
|
257
|
-
|
|
236
|
+
// Get the path to the background script
|
|
237
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
238
|
+
const __dirname = path.dirname(__filename);
|
|
239
|
+
const backgroundScript = path.join(__dirname, '..', 'bin', 'dashcam-background.js');
|
|
258
240
|
|
|
259
|
-
//
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
customOutputPath: options.output || null
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
const result = await startRecorderRecording(recordingOptions);
|
|
267
|
-
|
|
268
|
-
// Write status to track the recording
|
|
269
|
-
this.writeStatus({
|
|
270
|
-
isRecording: true,
|
|
271
|
-
startTime: result.startTime,
|
|
272
|
-
options,
|
|
273
|
-
pid: process.pid,
|
|
274
|
-
outputPath: result.outputPath
|
|
241
|
+
// Spawn a detached background process
|
|
242
|
+
const child = spawn(process.execPath, [backgroundScript, JSON.stringify(options)], {
|
|
243
|
+
detached: true,
|
|
244
|
+
stdio: 'ignore'
|
|
275
245
|
});
|
|
246
|
+
|
|
247
|
+
// Unref so the parent process can exit
|
|
248
|
+
child.unref();
|
|
249
|
+
|
|
250
|
+
const pid = child.pid;
|
|
251
|
+
|
|
252
|
+
// Write PID file immediately so other commands can find the recording process
|
|
253
|
+
this.writePid(pid);
|
|
276
254
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
logger.info('Recording started successfully', {
|
|
281
|
-
pid: process.pid,
|
|
282
|
-
outputPath: result.outputPath
|
|
255
|
+
logger.info('Background process spawned successfully', {
|
|
256
|
+
pid,
|
|
257
|
+
backgroundScript
|
|
283
258
|
});
|
|
259
|
+
|
|
260
|
+
// Wait a moment for the background process to write its status
|
|
261
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
262
|
+
|
|
263
|
+
// Read the status to get output path and start time
|
|
264
|
+
const status = this.readStatus();
|
|
265
|
+
|
|
266
|
+
if (!status) {
|
|
267
|
+
throw new Error('Background process failed to write status');
|
|
268
|
+
}
|
|
284
269
|
|
|
285
270
|
return {
|
|
286
|
-
pid
|
|
287
|
-
outputPath:
|
|
288
|
-
startTime:
|
|
271
|
+
pid,
|
|
272
|
+
outputPath: status.outputPath,
|
|
273
|
+
startTime: status.startTime
|
|
289
274
|
};
|
|
290
275
|
}
|
|
291
276
|
|