Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974
This commit is contained in:
commit
c737a37f71
531 files changed
+119270
No files matched your search
@@ -0,0 +1,103 @@
|
||||
(function initProtectionModel(root, factory) {
|
||||
const api = factory();
|
||||
if (typeof module === 'object' && module.exports) module.exports = api;
|
||||
if (root) root.chimeraProtectionModel = api;
|
||||
})(typeof window !== 'undefined' ? window : globalThis, function createProtectionModel() {
|
||||
'use strict';
|
||||
|
||||
const list = value => Array.isArray(value) ? value : [];
|
||||
const value = input => input == null || input === '' ? null : input;
|
||||
const first = (...inputs) => inputs.map(value).find(item => item != null) ?? null;
|
||||
const lower = input => String(input || '').trim().toLowerCase();
|
||||
const missingStates = new Set(['missing', 'unavailable', 'disconnected', 'offline', 'not_mounted', 'absent']);
|
||||
const failedIntegrityStates = new Set(['failed', 'corrupt', 'mismatch', 'invalid']);
|
||||
|
||||
function normalizeCopy(title, copy, index) {
|
||||
const volume = copy.volume || {};
|
||||
const device = copy.device || volume.device || copy.storageDevice || {};
|
||||
const id = first(copy.id, copy.copyId, copy.copy_id);
|
||||
const volumeId = first(copy.volumeId, copy.volume_id, volume.id);
|
||||
const deviceId = first(copy.deviceId, copy.device_id, device.id, volume.deviceId, volume.device_id);
|
||||
const deviceFingerprint = first(copy.deviceFingerprint, copy.stableFingerprint, device.stableFingerprint, device.stable_fingerprint);
|
||||
const volumeKey = first(copy.stableVolumeKey, copy.stable_volume_key, volume.stableVolumeKey, volume.stable_volume_key);
|
||||
const deviceKey = deviceId != null ? `device:${deviceId}` : deviceFingerprint ? `fingerprint:${deviceFingerprint}` : 'device:unknown';
|
||||
const relativePath = first(copy.relativePath, copy.relative_path, copy.path, '');
|
||||
const mountPath = first(copy.mountPath, copy.mount_path, volume.mountPath, volume.mount_path, '');
|
||||
const fullPath = mountPath && relativePath
|
||||
? `${String(mountPath).replace(/[\\/]+$/, '')}/${String(relativePath).replace(/^[\\/]+/, '')}`
|
||||
: String(first(relativePath, mountPath, 'Unknown path'));
|
||||
const availabilityState = lower(first(copy.availabilityState, copy.availability_state, volume.availabilityState, volume.availability_state, device.availabilityState, device.availability_state, 'unknown'));
|
||||
const integrityState = lower(first(copy.integrityState, copy.integrity_state, 'unknown'));
|
||||
const hashState = lower(first(copy.hashState, copy.hash_state, copy.inventoryHash || copy.inventory_hash ? 'recorded' : 'missing'));
|
||||
const missing = missingStates.has(availabilityState);
|
||||
const integrityFailed = failedIntegrityStates.has(integrityState);
|
||||
|
||||
return Object.freeze({
|
||||
copyId: id != null ? String(id) : `unidentified-${index + 1}`,
|
||||
titleId: String(first(title.game_id, title.titleId, title.title_id, title.id, 'unknown')),
|
||||
titleName: String(first(title.prospero_name, title.name, title.title, title.game_id, 'Unknown title')),
|
||||
region: String(first(title.prospero_region, title.region, '')),
|
||||
deviceId: deviceId == null ? null : String(deviceId),
|
||||
deviceKey,
|
||||
deviceName: String(first(copy.deviceName, copy.displayName, device.displayName, device.display_name, deviceFingerprint, 'Unknown device')),
|
||||
deviceFingerprint: deviceFingerprint == null ? null : String(deviceFingerprint),
|
||||
volumeId: volumeId == null ? null : String(volumeId),
|
||||
volumeKey: volumeKey == null ? null : String(volumeKey),
|
||||
path: fullPath,
|
||||
sizeBytes: Number(first(copy.sizeBytes, copy.size_bytes, 0)) || 0,
|
||||
hash: first(copy.inventoryHash, copy.inventory_hash),
|
||||
hashAlgorithm: String(first(copy.hashAlgorithm, copy.hash_algorithm, 'sha256')),
|
||||
hashState,
|
||||
integrityState,
|
||||
lastVerifiedAt: first(copy.lastVerifiedAt, copy.last_verified_at),
|
||||
lastSeenAt: first(copy.lastSeenAt, copy.last_seen_at),
|
||||
availabilityState,
|
||||
missing,
|
||||
integrityFailed,
|
||||
restoreTarget: Object.freeze({
|
||||
copyId: id != null ? String(id) : `unidentified-${index + 1}`,
|
||||
titleId: String(first(title.game_id, title.titleId, title.title_id, title.id, 'unknown')),
|
||||
volumeId: volumeId == null ? null : String(volumeId),
|
||||
deviceId: deviceId == null ? null : String(deviceId),
|
||||
path: fullPath
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function evaluate(copies, requiredDistinctDevices = 2) {
|
||||
const concrete = list(copies);
|
||||
const healthyCopies = concrete.filter(copy => !copy.missing && !copy.integrityFailed);
|
||||
const knownDevices = new Set(healthyCopies.map(copy => copy.deviceKey).filter(key => key !== 'device:unknown'));
|
||||
const unknownDeviceCopies = healthyCopies.filter(copy => copy.deviceKey === 'device:unknown').length;
|
||||
const distinctDevices = knownDevices.size + (unknownDeviceCopies ? 1 : 0);
|
||||
const required = Math.max(1, Number(requiredDistinctDevices) || 2);
|
||||
const missingCopies = concrete.filter(copy => copy.missing).length;
|
||||
const failedCopies = concrete.filter(copy => copy.integrityFailed).length;
|
||||
const healthy = distinctDevices >= required;
|
||||
const sameDeviceExcess = Math.max(0, healthyCopies.length - distinctDevices);
|
||||
const status = healthy ? 'healthy' : healthyCopies.length ? 'needs-diversity' : concrete.length ? 'degraded' : 'unprotected';
|
||||
return Object.freeze({
|
||||
copyCount: concrete.length,
|
||||
healthyCopyCount: healthyCopies.length,
|
||||
distinctDeviceCount: distinctDevices,
|
||||
requiredDistinctDevices: required,
|
||||
missingCopyCount: missingCopies,
|
||||
failedCopyCount: failedCopies,
|
||||
sameDeviceExcess,
|
||||
healthy,
|
||||
status
|
||||
});
|
||||
}
|
||||
|
||||
function flatten(titles, options = {}) {
|
||||
const result = [];
|
||||
for (const title of list(titles)) {
|
||||
const copies = list(title.localCopies).map((copy, index) => normalizeCopy(title, copy, index));
|
||||
const policy = evaluate(copies, options.requiredDistinctDevices);
|
||||
copies.forEach(copy => result.push(Object.freeze({ ...copy, policy })));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return Object.freeze({ normalizeCopy, evaluate, flatten });
|
||||
});
|
||||
Reference in new issue
Block a user