UNPKG

9.5 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.ControllerStorage = void 0;
4const tslib_1 = require("tslib");
5const util_1 = tslib_1.__importDefault(require("util"));
6const debug_1 = tslib_1.__importDefault(require("debug"));
7const HAPStorage_1 = require("./HAPStorage");
8const debug = (0, debug_1.default)("HAP-NodeJS:ControllerStorage");
9/**
10 * @group Model
11 */
12class ControllerStorage {
13 accessoryUUID;
14 initialized = false;
15 // ----- properties only set in parent storage object ------
16 username;
17 fileCreated = false;
18 purgeUnidentifiedAccessoryData = true;
19 // ---------------------------------------------------------
20 trackedControllers = []; // used to track controllers before data was loaded from disk
21 controllerData = {};
22 restoredAccessories; // indexed by accessory UUID
23 parent;
24 linkedAccessories;
25 queuedSaveTimeout;
26 queuedSaveTime;
27 constructor(accessory) {
28 this.accessoryUUID = accessory.UUID;
29 }
30 enqueueSaveRequest(timeout = 0) {
31 if (this.parent) {
32 this.parent.enqueueSaveRequest(timeout);
33 return;
34 }
35 const plannedTime = Date.now() + timeout;
36 if (this.queuedSaveTimeout) {
37 if (plannedTime <= (this.queuedSaveTime ?? 0)) {
38 return;
39 }
40 clearTimeout(this.queuedSaveTimeout);
41 }
42 this.queuedSaveTimeout = setTimeout(() => {
43 this.queuedSaveTimeout = this.queuedSaveTime = undefined;
44 this.save();
45 }, timeout).unref();
46 this.queuedSaveTime = Date.now() + timeout;
47 }
48 /**
49 * Links a bridged accessory to the ControllerStorage of the bridge accessory.
50 *
51 * @param accessory
52 */
53 linkAccessory(accessory) {
54 if (!this.linkedAccessories) {
55 this.linkedAccessories = [];
56 }
57 const storage = accessory.controllerStorage;
58 this.linkedAccessories.push(storage);
59 storage.parent = this;
60 const saved = this.restoredAccessories && this.restoredAccessories[accessory.UUID];
61 if (this.initialized) {
62 storage.init(saved);
63 }
64 }
65 trackController(controller) {
66 controller.setupStateChangeDelegate(this.handleStateChange.bind(this, controller)); // setup delegate
67 if (!this.initialized) { // track controller if data isn't loaded yet
68 this.trackedControllers.push(controller);
69 }
70 else {
71 this.restoreController(controller);
72 }
73 }
74 untrackController(controller) {
75 const index = this.trackedControllers.indexOf(controller);
76 if (index !== -1) { // remove from trackedControllers if storage wasn't initialized yet
77 this.trackedControllers.splice(index, 1);
78 }
79 controller.setupStateChangeDelegate(undefined); // remove association with this storage object
80 this.purgeControllerData(controller);
81 }
82 purgeControllerData(controller) {
83 delete this.controllerData[controller.controllerId()];
84 if (this.initialized) {
85 this.enqueueSaveRequest(100);
86 }
87 }
88 handleStateChange(controller) {
89 const id = controller.controllerId();
90 const serialized = controller.serialize();
91 if (!serialized) { // can be undefined when controller wishes to delete data
92 delete this.controllerData[id];
93 }
94 else {
95 const controllerData = this.controllerData[id];
96 if (!controllerData) {
97 this.controllerData[id] = {
98 data: serialized,
99 };
100 }
101 else {
102 controllerData.data = serialized;
103 }
104 }
105 if (this.initialized) { // only save if data was loaded
106 // run save data "async", as handleStateChange call will probably always be caused by a http request
107 // this should improve our response time
108 this.enqueueSaveRequest(100);
109 }
110 }
111 restoreController(controller) {
112 if (!this.initialized) {
113 throw new Error("Illegal state. Controller data wasn't loaded yet!");
114 }
115 const controllerData = this.controllerData[controller.controllerId()];
116 if (controllerData) {
117 try {
118 controller.deserialize(controllerData.data);
119 }
120 catch (error) {
121 console.warn(`Could not initialize controller of type '${controller.controllerId()}' from data stored on disk. Resetting to default: ${error.stack}`);
122 controller.handleFactoryReset();
123 }
124 controllerData.purgeOnNextLoad = undefined;
125 }
126 }
127 /**
128 * Called when this particular Storage object is feed with data loaded from disk.
129 * This method is only called once.
130 *
131 * @param data - array of {@link StoredControllerData}. undefined if nothing was stored on disk for this particular storage object
132 */
133 init(data) {
134 if (this.initialized) {
135 throw new Error(`ControllerStorage for accessory ${this.accessoryUUID} was already initialized!`);
136 }
137 this.initialized = true;
138 // storing data into our local controllerData Record
139 data && data.forEach(saved => this.controllerData[saved.type] = saved.controllerData);
140 const restoredControllers = [];
141 this.trackedControllers.forEach(controller => {
142 this.restoreController(controller);
143 restoredControllers.push(controller.controllerId());
144 });
145 this.trackedControllers.splice(0, this.trackedControllers.length); // clear tracking list
146 let purgedData = false;
147 Object.entries(this.controllerData).forEach(([id, data]) => {
148 if (data.purgeOnNextLoad) {
149 delete this.controllerData[id];
150 purgedData = true;
151 return;
152 }
153 if (!restoredControllers.includes(id)) {
154 data.purgeOnNextLoad = true;
155 }
156 });
157 if (purgedData) {
158 this.enqueueSaveRequest(500);
159 }
160 }
161 load(username) {
162 if (this.username) {
163 throw new Error("ControllerStorage was already loaded!");
164 }
165 this.username = username;
166 const key = ControllerStorage.persistKey(username);
167 const saved = HAPStorage_1.HAPStorage.storage().getItem(key);
168 let ownData;
169 if (saved) {
170 this.fileCreated = true;
171 ownData = saved.accessories[this.accessoryUUID];
172 delete saved.accessories[this.accessoryUUID];
173 }
174 this.init(ownData);
175 if (this.linkedAccessories) {
176 this.linkedAccessories.forEach(linkedStorage => {
177 const savedData = saved && saved.accessories[linkedStorage.accessoryUUID];
178 linkedStorage.init(savedData);
179 if (saved) {
180 delete saved.accessories[linkedStorage.accessoryUUID];
181 }
182 });
183 }
184 if (saved && Object.keys(saved.accessories).length > 0) {
185 if (!this.purgeUnidentifiedAccessoryData) {
186 this.restoredAccessories = saved.accessories; // save data for controllers which aren't linked yet
187 }
188 else {
189 debug("Purging unidentified controller data for bridge %s", username);
190 }
191 }
192 }
193 save() {
194 if (this.parent) {
195 this.parent.save();
196 return;
197 }
198 if (!this.initialized) {
199 throw new Error("ControllerStorage has not yet been loaded!");
200 }
201 if (!this.username) {
202 throw new Error("Cannot save controllerData for a storage without a username!");
203 }
204 const accessories = {
205 [this.accessoryUUID]: this.controllerData,
206 };
207 if (this.linkedAccessories) { // grab data from all linked storage objects
208 this.linkedAccessories.forEach(accessory => accessories[accessory.accessoryUUID] = accessory.controllerData);
209 }
210 // TODO removed accessories won't ever be deleted?
211 const accessoryData = this.restoredAccessories || {};
212 Object.entries(accessories).forEach(([uuid, controllerData]) => {
213 const entries = Object.entries(controllerData);
214 if (entries.length > 0) {
215 accessoryData[uuid] = entries.map(([id, data]) => ({
216 type: id,
217 controllerData: data,
218 }));
219 }
220 });
221 const key = ControllerStorage.persistKey(this.username);
222 if (Object.keys(accessoryData).length > 0) {
223 const saved = {
224 accessories: accessoryData,
225 };
226 this.fileCreated = true;
227 HAPStorage_1.HAPStorage.storage().setItemSync(key, saved);
228 }
229 else if (this.fileCreated) {
230 this.fileCreated = false;
231 HAPStorage_1.HAPStorage.storage().removeItemSync(key);
232 }
233 }
234 static persistKey(username) {
235 return util_1.default.format("ControllerStorage.%s.json", username.replace(/:/g, "").toUpperCase());
236 }
237 static remove(username) {
238 const key = ControllerStorage.persistKey(username);
239 HAPStorage_1.HAPStorage.storage().removeItemSync(key);
240 }
241}
242exports.ControllerStorage = ControllerStorage;
243//# sourceMappingURL=ControllerStorage.js.map
\No newline at end of file