@smarterhomeapp/homebridge-vantage 1.0.6 → 1.0.8
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/README.md +12 -3
- package/dist/platform.js +1 -0
- package/dist/vantageInfusion.d.ts +9 -2
- package/dist/vantageInfusion.js +172 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ This plugin replaces the one sold on smarterhome.io allowing anyone to download
|
|
|
12
12
|
- **Real-time Updates**: Live status updates from your InFusion controller
|
|
13
13
|
- **SSL Support**: Secure connections to your controller
|
|
14
14
|
- **Device Filtering**: Include/exclude specific devices by VID range
|
|
15
|
+
- **Safe Configuration Cache**: Downloads fresh controller configuration on every startup while keeping a persistent last-known-good fallback
|
|
15
16
|
|
|
16
17
|
## Installation
|
|
17
18
|
|
|
@@ -52,10 +53,18 @@ npm install -g @smarterhomeapp/homebridge-vantage
|
|
|
52
53
|
| `ipaddress` | string | Yes | IP address of your InFusion controller |
|
|
53
54
|
| `username` | string | No | Username for authentication |
|
|
54
55
|
| `password` | string | No | Password for authentication |
|
|
55
|
-
| `usecache` | boolean | No |
|
|
56
|
+
| `usecache` | boolean | No | Maintain a persistent last-known-good configuration fallback while still attempting fresh controller discovery on every startup (default: true) |
|
|
56
57
|
| `omit` | string | No | Comma-separated list of VIDs to exclude |
|
|
57
58
|
| `range` | string | No | Comma-separated VID range (min,max) |
|
|
58
59
|
|
|
60
|
+
## Configuration Discovery and Cache
|
|
61
|
+
|
|
62
|
+
On each Homebridge startup, the plugin first attempts to download the current Vantage controller configuration. If the downloaded configuration is valid and complete, it is used immediately and saved atomically as the persistent last-known-good configuration.
|
|
63
|
+
|
|
64
|
+
When `usecache` is enabled, the persistent cache is only a fallback. If the controller is offline, returns malformed XML, returns an empty configuration, or returns a suspiciously incomplete configuration, the plugin preserves the existing cache and uses the last-known-good configuration instead.
|
|
65
|
+
|
|
66
|
+
The cache is stored under Homebridge's configured storage directory in a plugin-specific `vantage` folder. Cache filenames are keyed by controller IP, for example `vantage-10.0.0.100.dc`, so multiple platform instances using different VID ranges for the same controller share one full-controller cache. Restarts naturally pick up controller/DC changes; users do not need to manually clear the cache.
|
|
67
|
+
|
|
59
68
|
## Supported Devices
|
|
60
69
|
|
|
61
70
|
### Lighting
|
|
@@ -92,7 +101,7 @@ npm run build
|
|
|
92
101
|
|
|
93
102
|
4. Link for development:
|
|
94
103
|
```bash
|
|
95
|
-
npm
|
|
104
|
+
npm link
|
|
96
105
|
```
|
|
97
106
|
|
|
98
107
|
## Troubleshooting
|
|
@@ -109,7 +118,7 @@ npm run dev
|
|
|
109
118
|
|
|
110
119
|
### Performance Issues
|
|
111
120
|
- Reduce the number of devices by using the `omit` parameter
|
|
112
|
-
-
|
|
121
|
+
- Restart Homebridge to force a fresh controller configuration download
|
|
113
122
|
- Consider using device ranges to limit discovery
|
|
114
123
|
|
|
115
124
|
## License
|
package/dist/platform.js
CHANGED
|
@@ -9,6 +9,7 @@ interface Options {
|
|
|
9
9
|
omit: string;
|
|
10
10
|
range: string;
|
|
11
11
|
log: Logger;
|
|
12
|
+
storagePath: string;
|
|
12
13
|
forceSSL?: boolean;
|
|
13
14
|
}
|
|
14
15
|
export declare class VantageInfusion extends EventEmitter {
|
|
@@ -17,7 +18,9 @@ export declare class VantageInfusion extends EventEmitter {
|
|
|
17
18
|
private command;
|
|
18
19
|
private isInsecureCmd;
|
|
19
20
|
private isInsecureCfg;
|
|
20
|
-
private
|
|
21
|
+
private getPersistentCachePath;
|
|
22
|
+
private getPersistentCacheDirectory;
|
|
23
|
+
private getLegacyCachePaths;
|
|
21
24
|
private cacheKeyForIp;
|
|
22
25
|
constructor(opts: Options);
|
|
23
26
|
/** Probe ports and open command session */
|
|
@@ -33,12 +36,16 @@ export declare class VantageInfusion extends EventEmitter {
|
|
|
33
36
|
private parseAndValidateConfigurationXml;
|
|
34
37
|
private isRelevantConfigObject;
|
|
35
38
|
private errorMessage;
|
|
39
|
+
private loadPersistentCache;
|
|
40
|
+
private migrateLegacyCacheIfNeeded;
|
|
36
41
|
/** Escape text for XML (same as sanitize in index.js). */
|
|
37
42
|
private static escapeXml;
|
|
38
43
|
/** Build cache XML from readObjects (same structure + sanitization as original index.js). */
|
|
39
44
|
private buildCacheXml;
|
|
40
45
|
private pullConfigurationXml;
|
|
41
|
-
private
|
|
46
|
+
private writePersistentCacheAtomically;
|
|
47
|
+
private writeLegacyTmpCacheMirror;
|
|
48
|
+
private validateCandidateAgainstExistingCache;
|
|
42
49
|
private validateExistingCacheForReplacement;
|
|
43
50
|
setBrightness(vid: string, level: number): Promise<void>;
|
|
44
51
|
setHue(vid: string, hue: number): Promise<void>;
|
package/dist/vantageInfusion.js
CHANGED
|
@@ -40,7 +40,6 @@ const tls = __importStar(require("tls"));
|
|
|
40
40
|
const xml2js_1 = require("xml2js");
|
|
41
41
|
const fs = __importStar(require("fs"));
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
|
-
const os = __importStar(require("os"));
|
|
44
43
|
const TYPE_THERMO = [
|
|
45
44
|
'Thermostat',
|
|
46
45
|
'Vantage.HVAC-Interface_Point_Zone_CHILD',
|
|
@@ -72,15 +71,23 @@ const OBJECT_TYPES = [
|
|
|
72
71
|
...TYPE_BLIND,
|
|
73
72
|
];
|
|
74
73
|
const DISCOVERABLE_OBJECT_TYPES = OBJECT_TYPES.filter((type) => type !== 'Area');
|
|
74
|
+
class CandidateConfigurationError extends Error {
|
|
75
|
+
constructor(message) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.name = 'CandidateConfigurationError';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
75
80
|
class VantageInfusion extends events_1.EventEmitter {
|
|
76
|
-
|
|
81
|
+
getPersistentCachePath() {
|
|
77
82
|
const fileName = `vantage-${this.cacheKeyForIp(this.opts.ipaddress)}.dc`;
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
// Controller-level cache is intentionally not range-specific; range-split main/child bridge instances share it.
|
|
84
|
+
return path.join(this.getPersistentCacheDirectory(), fileName);
|
|
85
|
+
}
|
|
86
|
+
getPersistentCacheDirectory() {
|
|
87
|
+
return path.join(this.opts.storagePath, 'vantage');
|
|
88
|
+
}
|
|
89
|
+
getLegacyCachePaths() {
|
|
90
|
+
return [path.join('/tmp', `vantage-${this.cacheKeyForIp(this.opts.ipaddress)}.dc`)];
|
|
84
91
|
}
|
|
85
92
|
cacheKeyForIp(ipaddress) {
|
|
86
93
|
return ipaddress.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
@@ -91,6 +98,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
91
98
|
this.xml = new xml2js_1.Parser({ explicitArray: false, mergeAttrs: true, trim: true });
|
|
92
99
|
this.isInsecureCmd = true; // 3001 vs 3010
|
|
93
100
|
this.isInsecureCfg = true; // 2001 vs 2010
|
|
101
|
+
this.opts.log.debug(`Persistent Vantage cache path: ${this.getPersistentCachePath()}`);
|
|
94
102
|
}
|
|
95
103
|
/** Probe ports and open command session */
|
|
96
104
|
async start() {
|
|
@@ -267,27 +275,44 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
267
275
|
}
|
|
268
276
|
/** XML/config discovery; returns devices (replaces old Discover/DiscoverSSL) */
|
|
269
277
|
async discoverDevices() {
|
|
270
|
-
// Check for cached configuration first
|
|
271
278
|
if (this.opts.usecache) {
|
|
272
|
-
|
|
273
|
-
|
|
279
|
+
await this.migrateLegacyCacheIfNeeded();
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
this.opts.log.info(`Attempting fresh Vantage configuration download from ${this.opts.ipaddress}`);
|
|
283
|
+
const xml = await this.pullConfigurationXml();
|
|
284
|
+
const validation = await this.parseAndValidateConfigurationXml(xml, 'downloaded configuration');
|
|
285
|
+
this.opts.log.info(`Fresh Vantage controller configuration validated successfully (${validation.relevantObjectCount} relevant objects)`);
|
|
286
|
+
if (this.opts.usecache) {
|
|
274
287
|
try {
|
|
275
|
-
|
|
276
|
-
const devices = await this.processConfigurationXml(cachedXml, `cached configuration from ${cachePath}`);
|
|
277
|
-
this.opts.log.info(`Valid cached configuration loaded from ${cachePath}`);
|
|
278
|
-
return devices;
|
|
288
|
+
await this.writePersistentCacheAtomically(xml, validation);
|
|
279
289
|
}
|
|
280
290
|
catch (error) {
|
|
281
|
-
|
|
291
|
+
if (error instanceof CandidateConfigurationError) {
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
this.opts.log.warn(`Persistent cache update failed; using live configuration for this run: ${this.errorMessage(error)}`);
|
|
282
295
|
}
|
|
296
|
+
await this.writeLegacyTmpCacheMirror(xml, validation);
|
|
283
297
|
}
|
|
298
|
+
// Legacy: "end configuration download"
|
|
299
|
+
this.opts.log.debug('VantagePlatform for InFusion Controller (end configuration download)');
|
|
300
|
+
const devices = await this.processConfigurationXml(xml, 'downloaded configuration');
|
|
301
|
+
this.opts.log.info('Fresh Vantage controller configuration loaded successfully');
|
|
302
|
+
return devices;
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
this.opts.log.warn(`Fresh controller discovery failed; attempting persistent fallback cache: ${this.errorMessage(error)}`);
|
|
306
|
+
if (this.opts.usecache) {
|
|
307
|
+
try {
|
|
308
|
+
return await this.loadPersistentCache();
|
|
309
|
+
}
|
|
310
|
+
catch (cacheError) {
|
|
311
|
+
this.opts.log.error(`Persistent cache unavailable; preserving existing Homebridge accessories: ${this.errorMessage(cacheError)}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
throw error;
|
|
284
315
|
}
|
|
285
|
-
const xml = await this.pullConfigurationXml();
|
|
286
|
-
// Legacy: "end configuration download"
|
|
287
|
-
this.opts.log.debug('VantagePlatform for InFusion Controller (end configuration download)');
|
|
288
|
-
const devices = await this.processConfigurationXml(xml, 'downloaded configuration');
|
|
289
|
-
this.opts.log.info('Valid configuration loaded from controller download');
|
|
290
|
-
return devices;
|
|
291
316
|
}
|
|
292
317
|
async processConfigurationXml(xml, source = 'configuration') {
|
|
293
318
|
const { objects } = await this.parseAndValidateConfigurationXml(xml, source);
|
|
@@ -471,6 +496,39 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
471
496
|
errorMessage(error) {
|
|
472
497
|
return error instanceof Error ? error.message : String(error);
|
|
473
498
|
}
|
|
499
|
+
async loadPersistentCache() {
|
|
500
|
+
const cachePath = this.getPersistentCachePath();
|
|
501
|
+
if (!fs.existsSync(cachePath)) {
|
|
502
|
+
throw new Error(`persistent cache not found at ${cachePath}`);
|
|
503
|
+
}
|
|
504
|
+
const cachedXml = fs.readFileSync(cachePath, 'utf8');
|
|
505
|
+
const devices = await this.processConfigurationXml(cachedXml, `persistent last-known-good cache ${cachePath}`);
|
|
506
|
+
this.opts.log.warn(`Using persistent last-known-good Vantage configuration from ${cachePath}`);
|
|
507
|
+
return devices;
|
|
508
|
+
}
|
|
509
|
+
async migrateLegacyCacheIfNeeded() {
|
|
510
|
+
const persistentCachePath = this.getPersistentCachePath();
|
|
511
|
+
if (fs.existsSync(persistentCachePath))
|
|
512
|
+
return;
|
|
513
|
+
for (const legacyPath of this.getLegacyCachePaths()) {
|
|
514
|
+
if (!fs.existsSync(legacyPath))
|
|
515
|
+
continue;
|
|
516
|
+
try {
|
|
517
|
+
const legacyXml = fs.readFileSync(legacyPath, 'utf8');
|
|
518
|
+
const validation = await this.parseAndValidateConfigurationXml(legacyXml, `legacy cache ${legacyPath}`);
|
|
519
|
+
await this.writePersistentCacheAtomically(legacyXml, validation);
|
|
520
|
+
this.opts.log.info(`Migrated legacy Vantage cache from ${legacyPath} to ${persistentCachePath}`);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
this.opts.log.warn(`Legacy cache rejected from ${legacyPath}: ${this.errorMessage(error)}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const unkeyedLegacyPath = path.join('/tmp', 'vantage.dc');
|
|
528
|
+
if (fs.existsSync(unkeyedLegacyPath)) {
|
|
529
|
+
this.opts.log.warn(`Legacy cache ${unkeyedLegacyPath} exists but was not migrated because it is not controller-specific.`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
474
532
|
/** Escape text for XML (same as sanitize in index.js). */
|
|
475
533
|
static escapeXml(s) {
|
|
476
534
|
return String(s)
|
|
@@ -492,32 +550,27 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
492
550
|
}
|
|
493
551
|
pullConfigurationXml() {
|
|
494
552
|
return new Promise((resolve, reject) => {
|
|
495
|
-
const finishOnce =
|
|
553
|
+
const finishOnce = (socket, xml) => {
|
|
496
554
|
if (finishOnce._done)
|
|
497
555
|
return;
|
|
498
556
|
finishOnce._done = true;
|
|
499
|
-
//
|
|
557
|
+
// legacy event
|
|
558
|
+
this.emit('endDownloadConfiguration', xml);
|
|
500
559
|
try {
|
|
501
|
-
|
|
502
|
-
await this.writeCacheAtomically(xml);
|
|
503
|
-
}
|
|
504
|
-
// legacy log + event
|
|
505
|
-
this.opts.log.debug('VantagePlatform for InFusion Controller (end configuration download)');
|
|
506
|
-
this.emit('endDownloadConfiguration', xml);
|
|
507
|
-
try {
|
|
508
|
-
socket.destroy();
|
|
509
|
-
}
|
|
510
|
-
catch { /* noop */ }
|
|
511
|
-
resolve(xml);
|
|
560
|
+
socket.destroy();
|
|
512
561
|
}
|
|
513
|
-
catch
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
562
|
+
catch { /* noop */ }
|
|
563
|
+
resolve(xml);
|
|
564
|
+
};
|
|
565
|
+
const failOnce = (socket, error) => {
|
|
566
|
+
if (finishOnce._done)
|
|
567
|
+
return;
|
|
568
|
+
finishOnce._done = true;
|
|
569
|
+
try {
|
|
570
|
+
socket.destroy();
|
|
520
571
|
}
|
|
572
|
+
catch { /* noop */ }
|
|
573
|
+
reject(error);
|
|
521
574
|
};
|
|
522
575
|
const onData = (socket) => {
|
|
523
576
|
this.opts.log.info('load dc file'); // legacy
|
|
@@ -538,7 +591,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
538
591
|
if (readObjects.length && Date.now() - lastProgress > 2000) {
|
|
539
592
|
const xml = this.buildCacheXml(readObjects);
|
|
540
593
|
clearInterval(watchdog);
|
|
541
|
-
|
|
594
|
+
finishOnce(socket, xml);
|
|
542
595
|
}
|
|
543
596
|
}, 750);
|
|
544
597
|
const writeOpenFilter = () => {
|
|
@@ -647,58 +700,78 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
647
700
|
const xml = this.buildCacheXml(readObjects);
|
|
648
701
|
clearLoginFallback();
|
|
649
702
|
clearInterval(watchdog);
|
|
650
|
-
|
|
703
|
+
finishOnce(socket, xml);
|
|
651
704
|
}
|
|
652
705
|
});
|
|
653
706
|
});
|
|
654
707
|
socket.on('end', () => {
|
|
655
708
|
this.opts.log.info("ending" + shouldBreak);
|
|
709
|
+
if (finishOnce._done)
|
|
710
|
+
return;
|
|
656
711
|
if (!finishOnce._done && readObjects.length) {
|
|
657
712
|
const xml = this.buildCacheXml(readObjects);
|
|
658
713
|
clearLoginFallback();
|
|
659
714
|
clearInterval(watchdog);
|
|
660
|
-
|
|
715
|
+
finishOnce(socket, xml);
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
clearLoginFallback();
|
|
719
|
+
clearInterval(watchdog);
|
|
720
|
+
failOnce(socket, new Error('Connection ended before Vantage configuration was downloaded'));
|
|
661
721
|
}
|
|
662
722
|
});
|
|
663
723
|
socket.on('close', () => {
|
|
664
724
|
this.opts.log.debug("closing " + shouldBreak);
|
|
725
|
+
if (finishOnce._done)
|
|
726
|
+
return;
|
|
665
727
|
if (!finishOnce._done && readObjects.length) {
|
|
666
728
|
const xml = this.buildCacheXml(readObjects);
|
|
667
729
|
clearLoginFallback();
|
|
668
730
|
clearInterval(watchdog);
|
|
669
|
-
|
|
731
|
+
finishOnce(socket, xml);
|
|
670
732
|
}
|
|
733
|
+
else {
|
|
734
|
+
clearLoginFallback();
|
|
735
|
+
clearInterval(watchdog);
|
|
736
|
+
failOnce(socket, new Error('Connection closed before Vantage configuration was downloaded'));
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
socket.on('timeout', () => {
|
|
740
|
+
clearLoginFallback();
|
|
741
|
+
clearInterval(watchdog);
|
|
742
|
+
failOnce(socket, new Error('Timed out while downloading Vantage configuration'));
|
|
671
743
|
});
|
|
744
|
+
socket.on('error', (error) => {
|
|
745
|
+
clearLoginFallback();
|
|
746
|
+
clearInterval(watchdog);
|
|
747
|
+
failOnce(socket, error);
|
|
748
|
+
});
|
|
749
|
+
socket.setTimeout(30000);
|
|
672
750
|
};
|
|
673
751
|
if (this.isInsecureCfg) {
|
|
674
752
|
const s = net.connect({ host: this.opts.ipaddress, port: 2001 }, () => onData(s));
|
|
753
|
+
s.once('error', (error) => failOnce(s, error));
|
|
754
|
+
s.setTimeout(30000, () => failOnce(s, new Error('Timed out connecting to Vantage configuration port')));
|
|
675
755
|
}
|
|
676
756
|
else {
|
|
677
757
|
const s = tls.connect(2010, this.opts.ipaddress, { rejectUnauthorized: false, requestCert: true }, () => onData(s));
|
|
758
|
+
s.once('error', (error) => failOnce(s, error));
|
|
759
|
+
s.setTimeout(30000, () => failOnce(s, new Error('Timed out connecting to Vantage configuration port')));
|
|
678
760
|
}
|
|
679
761
|
});
|
|
680
762
|
}
|
|
681
|
-
async
|
|
682
|
-
const cachePath = this.
|
|
763
|
+
async writePersistentCacheAtomically(xml, candidateValidation) {
|
|
764
|
+
const cachePath = this.getPersistentCachePath();
|
|
683
765
|
const tmpPath = `${cachePath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
684
|
-
|
|
766
|
+
await this.validateCandidateAgainstExistingCache(cachePath, candidateValidation);
|
|
685
767
|
try {
|
|
768
|
+
fs.mkdirSync(this.getPersistentCacheDirectory(), { recursive: true });
|
|
686
769
|
fs.writeFileSync(tmpPath, xml, 'utf8');
|
|
687
770
|
const tmpXml = fs.readFileSync(tmpPath, 'utf8');
|
|
688
771
|
const validation = await this.parseAndValidateConfigurationXml(tmpXml, `temporary cache ${tmpPath}`);
|
|
689
|
-
|
|
690
|
-
const existingValidation = await this.validateExistingCacheForReplacement(cachePath);
|
|
691
|
-
if (existingValidation &&
|
|
692
|
-
existingValidation.relevantObjectCount >= 20 &&
|
|
693
|
-
candidateValidation.relevantObjectCount < existingValidation.relevantObjectCount * 0.5) {
|
|
694
|
-
const message = `Downloaded configuration rejected as incomplete: ${candidateValidation.relevantObjectCount} relevant objects ` +
|
|
695
|
-
`vs ${existingValidation.relevantObjectCount} in existing cache.`;
|
|
696
|
-
this.opts.log.error(message);
|
|
697
|
-
throw new Error(message);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
772
|
+
await this.validateCandidateAgainstExistingCache(cachePath, validation);
|
|
700
773
|
fs.renameSync(tmpPath, cachePath);
|
|
701
|
-
this.opts.log.info(`Validated controller configuration accepted and cache
|
|
774
|
+
this.opts.log.info(`Validated controller configuration accepted and persistent cache updated atomically at ${cachePath} ` +
|
|
702
775
|
`(${validation.relevantObjectCount} relevant objects).`);
|
|
703
776
|
}
|
|
704
777
|
catch (error) {
|
|
@@ -712,6 +785,44 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
712
785
|
throw error;
|
|
713
786
|
}
|
|
714
787
|
}
|
|
788
|
+
async writeLegacyTmpCacheMirror(xml, candidateValidation) {
|
|
789
|
+
const cachePath = path.join('/tmp', 'vantage.dc');
|
|
790
|
+
const tmpPath = `${cachePath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
791
|
+
try {
|
|
792
|
+
fs.writeFileSync(tmpPath, xml, 'utf8');
|
|
793
|
+
const tmpXml = fs.readFileSync(tmpPath, 'utf8');
|
|
794
|
+
const validation = await this.parseAndValidateConfigurationXml(tmpXml, `legacy temporary cache mirror ${tmpPath}`);
|
|
795
|
+
if (validation.relevantObjectCount !== candidateValidation.relevantObjectCount) {
|
|
796
|
+
throw new Error(`legacy cache mirror validation mismatch: ${validation.relevantObjectCount} relevant objects ` +
|
|
797
|
+
`vs ${candidateValidation.relevantObjectCount} expected`);
|
|
798
|
+
}
|
|
799
|
+
fs.renameSync(tmpPath, cachePath);
|
|
800
|
+
this.opts.log.debug(`Legacy /tmp Vantage cache mirror updated at ${cachePath}`);
|
|
801
|
+
}
|
|
802
|
+
catch (error) {
|
|
803
|
+
try {
|
|
804
|
+
if (fs.existsSync(tmpPath))
|
|
805
|
+
fs.unlinkSync(tmpPath);
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
/* preserve original error */
|
|
809
|
+
}
|
|
810
|
+
this.opts.log.warn(`Failed to update legacy /tmp Vantage cache mirror: ${this.errorMessage(error)}`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
async validateCandidateAgainstExistingCache(cachePath, candidateValidation) {
|
|
814
|
+
if (!fs.existsSync(cachePath))
|
|
815
|
+
return;
|
|
816
|
+
const existingValidation = await this.validateExistingCacheForReplacement(cachePath);
|
|
817
|
+
if (existingValidation &&
|
|
818
|
+
existingValidation.relevantObjectCount >= 20 &&
|
|
819
|
+
candidateValidation.relevantObjectCount < existingValidation.relevantObjectCount * 0.5) {
|
|
820
|
+
const message = `Downloaded configuration rejected as incomplete: ${candidateValidation.relevantObjectCount} relevant objects ` +
|
|
821
|
+
`vs ${existingValidation.relevantObjectCount} in persistent cache.`;
|
|
822
|
+
this.opts.log.error(message);
|
|
823
|
+
throw new CandidateConfigurationError(message);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
715
826
|
async validateExistingCacheForReplacement(cachePath) {
|
|
716
827
|
try {
|
|
717
828
|
const existingXml = fs.readFileSync(cachePath, 'utf8');
|