changeset 2091:9324fc8285ad

Effect repairs duplicate :settings edges when it finds them
author drewp@bigasterisk.com
date Mon, 30 May 2022 23:21:50 -0700
parents cb7556869244
children b39aafe12319
files light9/web/live/Effect.ts light9/web/live/Light9LiveControls.ts light9/web/patch.ts
diffstat 3 files changed, 24 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/light9/web/live/Effect.ts	Mon May 30 23:18:58 2022 -0700
+++ b/light9/web/live/Effect.ts	Mon May 30 23:21:50 2022 -0700
@@ -1,7 +1,7 @@
 import debug from "debug";
 import { Literal, NamedNode, Quad_Object, Quad_Predicate, Quad_Subject, Term } from "n3";
 import { some } from "underscore";
-import { Patch, patchContainsPreds } from "../patch";
+import { Patch, patchContainsPreds, patchUpdate } from "../patch";
 import { SyncedGraph } from "../SyncedGraph";
 
 type Color = string;
@@ -60,13 +60,16 @@
     const U = this.graph.U();
     if (patch && !patchContainsPreds(patch, [U(":setting"), U(":device"), U(":deviceAttr")])) {
       // that's an approx list of preds , but it just means we'll miss some pathological settings edits
-    //   return;
+      //   return;
     }
 
     // log("syncFromGraph", this.uri);
 
+    // this repeats work- it gathers all settings when really some values changed (and we might even know about them). maybe push the value-fetching into a secnod phase of the run, and have the 1st phase drop out early
     const newSettings = [];
 
+    const seenDevAttrPairs: Set<string> = new Set();
+
     for (let setting of Array.from(this.graph.objects(this.uri, U(":setting")))) {
       //   log(`  setting ${setting.value}`);
       if (!isUri(setting)) throw new Error();
@@ -87,7 +90,7 @@
           value = this.graph.stringValue(setting, pred); // this may find multi values and throw
         }
       }
-    //   log(`change: graph contains ${deviceAttr.value} ${value}`);
+      //   log(`change: graph contains ${deviceAttr.value} ${value}`);
 
       newSettings.push({ device, deviceAttr, setting, value });
     }
@@ -109,10 +112,13 @@
   edit(device: NamedNode, deviceAttr: NamedNode, newValue: ControlValue | null): Patch {
     log(`edit: value=${newValue}`);
     let existingSetting: NamedNode | null = null;
+    let result = { adds: [], dels: [] };
     for (let s of this.settings) {
       if (device.equals(s.device) && deviceAttr.equals(s.deviceAttr)) {
         if (existingSetting !== null) {
-          throw new Error(`${this.uri.value} had two settings for ${device.value} - ${deviceAttr.value}`);
+          // this is corrupt. There was only supposed to be one setting per (dev,attr) pair. But we can fix it because we're going to update existingSetting to the user's requested value.
+          log(`${this.uri.value} had two settings for ${device.value} - ${deviceAttr.value} - deleting ${s.setting}`);
+          patchUpdate(result,  this._removeEffectSetting(s.setting));
         }
         existingSetting = s.setting;
       }
@@ -120,16 +126,16 @@
 
     if (newValue !== null && this.shouldBeStored(deviceAttr, newValue)) {
       if (existingSetting === null) {
-        return this._addEffectSetting(device, deviceAttr, newValue);
+        patchUpdate(result, this._addEffectSetting(device, deviceAttr, newValue));
       } else {
-        return this._patchExistingEffectSetting(existingSetting, deviceAttr, newValue);
+        patchUpdate(result, this._patchExistingEffectSetting(existingSetting, deviceAttr, newValue));
       }
     } else {
       if (existingSetting !== null) {
-        return this._removeEffectSetting(existingSetting);
+        patchUpdate(result, this._removeEffectSetting(existingSetting));
       }
     }
-    return { adds: [], dels: [] };
+    return result;
   }
 
   shouldBeStored(deviceAttr: NamedNode, value: ControlValue | null): boolean {
--- a/light9/web/live/Light9LiveControls.ts	Mon May 30 23:18:58 2022 -0700
+++ b/light9/web/live/Light9LiveControls.ts	Mon May 30 23:21:50 2022 -0700
@@ -7,8 +7,8 @@
 import { getTopGraph } from "../RdfdbSyncedGraph";
 import { SyncedGraph } from "../SyncedGraph";
 import { GraphToControls } from "./GraphToControls";
+export { EditChoice } from "../EditChoice";
 export { Light9DeviceControl as Light9LiveDeviceControl } from "./Light9DeviceControl";
-export { EditChoice } from "../EditChoice";
 const log = debug("controls");
 
 @customElement("light9-live-controls")
--- a/light9/web/patch.ts	Mon May 30 23:18:58 2022 -0700
+++ b/light9/web/patch.ts	Mon May 30 23:21:50 2022 -0700
@@ -1,6 +1,6 @@
+import * as async from "async";
 import debug from "debug";
-import * as async from "async";
-import { Writer, Parser, Quad, NamedNode } from "n3";
+import { NamedNode, Parser, Quad, Writer } from "n3";
 const log = debug("patch");
 
 export interface Patch {
@@ -14,6 +14,12 @@
   patch: { adds: string; deletes: string };
 }
 
+export function patchUpdate(p1: Patch, p2: Patch): void {
+  // this is approx, since it doesnt handle matching existing quads.
+  p1.adds = p1.adds.concat(p2.adds);
+  p1.dels = p1.dels.concat(p2.dels);
+}
+
 export function patchSizeSummary(patch: Patch) {
   return "-" + patch.dels.length + " +" + patch.adds.length;
 }
@@ -43,6 +49,7 @@
     });
   };
 
+  // todo: is it faster to run them in series? might be
   return async.parallel([parseAdds, parseDels], (err: any) => cb(patch));
 }