0.11.0 Multi LW
This commit is contained in:
@@ -190,6 +190,7 @@ class DiskDetectionService extends EventEmitter {
|
||||
this.timer = null;
|
||||
this.lastDetected = null;
|
||||
this.lastPresent = false;
|
||||
this.detectedDiscs = new Map(); // devicePath → device object (multi-drive tracking)
|
||||
this.deviceLocks = new Map();
|
||||
this.pollingSuspended = false;
|
||||
}
|
||||
@@ -248,8 +249,8 @@ class DiskDetectionService extends EventEmitter {
|
||||
if (this.pollingSuspended) {
|
||||
logger.debug('poll:skip:suspended', { nextDelay });
|
||||
} else if (autoDetectionEnabled) {
|
||||
const detected = await this.detectDisc(map);
|
||||
this.applyDetectionResult(detected, { forceInsertEvent: false });
|
||||
const detectedList = await this.detectAllDiscs(map);
|
||||
this.applyMultiDetectionResults(detectedList, { forceInsertEvent: false });
|
||||
} else {
|
||||
logger.debug('poll:skip:auto-detection-disabled', { nextDelay });
|
||||
}
|
||||
@@ -270,17 +271,30 @@ class DiskDetectionService extends EventEmitter {
|
||||
driveDevice: map.drive_device
|
||||
});
|
||||
|
||||
const detected = await this.detectDisc(map);
|
||||
const result = this.applyDetectionResult(detected, { forceInsertEvent: true });
|
||||
const detectedList = await this.detectAllDiscs(map);
|
||||
const results = this.applyMultiDetectionResults(detectedList, { forceInsertEvent: true });
|
||||
|
||||
const insertedResults = results.filter((r) => r.emitted === 'discInserted');
|
||||
const removedResults = results.filter((r) => r.emitted === 'discRemoved');
|
||||
const present = detectedList.length > 0;
|
||||
const firstInserted = insertedResults[0] || null;
|
||||
|
||||
logger.info('rescan:done', {
|
||||
present: result.present,
|
||||
emitted: result.emitted,
|
||||
changed: result.changed,
|
||||
detected: result.device || null
|
||||
driveCount: detectedList.length,
|
||||
inserted: insertedResults.length,
|
||||
removed: removedResults.length
|
||||
});
|
||||
|
||||
return result;
|
||||
// Backward-compat return: report on first inserted device (or removed if nothing inserted)
|
||||
return {
|
||||
present,
|
||||
changed: insertedResults.length > 0 || removedResults.length > 0,
|
||||
emitted: insertedResults.length > 0
|
||||
? 'discInserted'
|
||||
: (removedResults.length > 0 ? 'discRemoved' : 'none'),
|
||||
device: firstInserted?.device || null,
|
||||
allDetected: detectedList
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('rescan:error', { error: errorToMeta(error) });
|
||||
throw error;
|
||||
@@ -425,6 +439,83 @@ class DiskDetectionService extends EventEmitter {
|
||||
return this.detectAuto();
|
||||
}
|
||||
|
||||
// Returns ALL discs with media (not just the first one)
|
||||
async detectAllDiscs(settingsMap) {
|
||||
if (settingsMap.drive_mode === 'explicit') {
|
||||
// drive_devices is a JSON array of paths; fall back to legacy drive_device
|
||||
let explicitPaths = [];
|
||||
try {
|
||||
const parsed = JSON.parse(settingsMap.drive_devices || '[]');
|
||||
if (Array.isArray(parsed)) {
|
||||
explicitPaths = parsed.map((p) => String(p || '').trim()).filter(Boolean);
|
||||
}
|
||||
} catch (_error) {
|
||||
// malformed JSON — ignore, fall through to legacy
|
||||
}
|
||||
if (explicitPaths.length === 0) {
|
||||
const legacy = String(settingsMap.drive_device || '').trim();
|
||||
if (legacy) {
|
||||
explicitPaths = [legacy];
|
||||
}
|
||||
}
|
||||
const results = await Promise.all(explicitPaths.map((p) => this.detectExplicit(p)));
|
||||
return results.filter(Boolean);
|
||||
}
|
||||
|
||||
return this.detectAllAuto();
|
||||
}
|
||||
|
||||
// Multi-drive: tracks per-device state and emits insert/remove events for each
|
||||
applyMultiDetectionResults(detectedList, { forceInsertEvent = false } = {}) {
|
||||
const detected = Array.isArray(detectedList) ? detectedList : [];
|
||||
const newMap = new Map();
|
||||
for (const device of detected) {
|
||||
if (device?.path) {
|
||||
newMap.set(device.path, device);
|
||||
}
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
// Check for new or changed devices
|
||||
for (const [devicePath, device] of newMap) {
|
||||
const previous = this.detectedDiscs.get(devicePath);
|
||||
const changed = previous ? buildSignature(previous) !== buildSignature(device) : false;
|
||||
const shouldEmitInserted = forceInsertEvent || !previous || changed;
|
||||
if (shouldEmitInserted) {
|
||||
logger.info('disc:inserted', { device, forceInsertEvent, changed });
|
||||
this.emit('discInserted', device);
|
||||
results.push({ path: devicePath, emitted: 'discInserted', device });
|
||||
} else {
|
||||
results.push({ path: devicePath, emitted: 'none', device });
|
||||
}
|
||||
}
|
||||
|
||||
// Check for removed devices
|
||||
for (const [devicePath, device] of this.detectedDiscs) {
|
||||
if (!newMap.has(devicePath)) {
|
||||
logger.info('disc:removed', { device });
|
||||
this.emit('discRemoved', device);
|
||||
results.push({ path: devicePath, emitted: 'discRemoved', device: null });
|
||||
}
|
||||
}
|
||||
|
||||
this.detectedDiscs = newMap;
|
||||
|
||||
// Update legacy single-disc tracking for backward compat
|
||||
if (newMap.size > 0) {
|
||||
this.lastDetected = Array.from(newMap.values())[0];
|
||||
this.lastPresent = true;
|
||||
} else {
|
||||
if (this.lastPresent) {
|
||||
this.lastDetected = null;
|
||||
this.lastPresent = false;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async detectExplicit(devicePath) {
|
||||
if (this.isDeviceLocked(devicePath)) {
|
||||
logger.debug('detect:explicit:locked', {
|
||||
@@ -527,6 +618,60 @@ class DiskDetectionService extends EventEmitter {
|
||||
return null;
|
||||
}
|
||||
|
||||
async detectAllAuto() {
|
||||
const details = await this.getBlockDeviceInfo();
|
||||
const romCandidates = details.filter((entry) => entry.type === 'rom');
|
||||
const results = [];
|
||||
|
||||
for (const item of romCandidates) {
|
||||
const path = item.path || (item.name ? `/dev/${item.name}` : null);
|
||||
if (!path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.isDeviceLocked(path)) {
|
||||
logger.debug('detect:all-auto:skip-locked', {
|
||||
path,
|
||||
activeLocks: this.getActiveLocks()
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const mediaState = await this.checkMediaPresent(path);
|
||||
if (!mediaState.hasMedia) {
|
||||
continue;
|
||||
}
|
||||
const discLabel = await this.getDiscLabel(path);
|
||||
const detectedFsType = String(item.fstype || mediaState.type || '').trim() || null;
|
||||
|
||||
const mediaProfile = await this.inferMediaProfile(path, {
|
||||
discLabel,
|
||||
label: item.label,
|
||||
model: item.model,
|
||||
fstype: detectedFsType,
|
||||
mountpoint: item.mountpoint
|
||||
});
|
||||
|
||||
const detected = {
|
||||
mode: 'auto',
|
||||
path,
|
||||
name: item.name,
|
||||
model: item.model || 'Optical Drive',
|
||||
label: item.label || null,
|
||||
discLabel: discLabel || null,
|
||||
mountpoint: item.mountpoint || null,
|
||||
fstype: detectedFsType,
|
||||
mediaProfile: mediaProfile || null,
|
||||
index: this.guessDiscIndex(item.name)
|
||||
};
|
||||
logger.debug('detect:all-auto:found', { detected });
|
||||
results.push(detected);
|
||||
}
|
||||
|
||||
logger.debug('detect:all-auto:done', { count: results.length });
|
||||
return results;
|
||||
}
|
||||
|
||||
async getBlockDeviceInfo() {
|
||||
try {
|
||||
const { stdout } = await execFileAsync('lsblk', [
|
||||
|
||||
Reference in New Issue
Block a user