diff --git a/Pepperdash Core/CLZ Builds/PepperDash_Core.clz b/Pepperdash Core/CLZ Builds/PepperDash_Core.clz
deleted file mode 100644
index 0c8ad6e..0000000
Binary files a/Pepperdash Core/CLZ Builds/PepperDash_Core.clz and /dev/null differ
diff --git a/Pepperdash Core/CLZ Builds/PepperDash_Core.dll b/Pepperdash Core/CLZ Builds/PepperDash_Core.dll
deleted file mode 100644
index 3edffd1..0000000
Binary files a/Pepperdash Core/CLZ Builds/PepperDash_Core.dll and /dev/null differ
diff --git a/Pepperdash Core/Pepperdash Core/Comm/GenericHttpSseClient.cs b/Pepperdash Core/Pepperdash Core/Comm/GenericHttpSseClient.cs
index 9e5121f..1feac8f 100644
--- a/Pepperdash Core/Pepperdash Core/Comm/GenericHttpSseClient.cs
+++ b/Pepperdash Core/Pepperdash Core/Comm/GenericHttpSseClient.cs
@@ -97,7 +97,6 @@ namespace PepperDash.Core
private void GetRequestStreamCallback(HttpClientRequest request, HTTP_CALLBACK_ERROR error, object status)
{
-#warning Explore simplifying this later....
try
{
@@ -136,8 +135,6 @@ namespace PepperDash.Core
Debug.Console(1, this, "Client Disconnected");
-#warning Need to explore using a heartbeat to verify connection to the server
-
Stream streamResponse = response.ContentStream;
// Object containing various states to be passed back to async callback below
RequestState asyncState = new RequestState();
diff --git a/Pepperdash Core/Pepperdash Core/Config/PortalConfigReader.cs b/Pepperdash Core/Pepperdash Core/Config/PortalConfigReader.cs
new file mode 100644
index 0000000..f1d866f
--- /dev/null
+++ b/Pepperdash Core/Pepperdash Core/Config/PortalConfigReader.cs
@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Crestron.SimplSharp;
+using Crestron.SimplSharp.CrestronIO;
+
+using PepperDash.Core;
+
+namespace PepperDash.Core.Config
+{
+ public class PortalConfigReader
+ {
+ ///
+ /// Reads the config file, checks if it needs a merge, merges and saves, then returns the merged Object.
+ ///
+ /// JObject of config file
+ public static void ReadAndMergeFileIfNecessary(string filePath, string savePath)
+ {
+ try
+ {
+ if (!File.Exists(filePath))
+ {
+ Debug.Console(1, Debug.ErrorLogLevel.Error,
+ "ERROR: Configuration file not present. Please load file to {0} and reset program", filePath);
+ }
+
+ using (StreamReader fs = new StreamReader(filePath))
+ {
+ var jsonObj = JObject.Parse(fs.ReadToEnd());
+ if(jsonObj["template"] != null && jsonObj["system"] != null)
+ {
+ // it's a double-config, merge it.
+ var merged = MergeConfigs(jsonObj);
+ if (jsonObj["system_url"] != null)
+ {
+ merged["systemUrl"] = jsonObj["system_url"].Value();
+ }
+
+ if (jsonObj["template_url"] != null)
+ {
+ merged["templateUrl"] = jsonObj["template_url"].Value();
+ }
+
+ jsonObj = merged;
+ }
+
+ using (StreamWriter fw = new StreamWriter(savePath))
+ {
+ fw.Write(jsonObj.ToString(Formatting.Indented));
+ Debug.Console(1, "JSON config merged and saved to {0}", savePath);
+ }
+
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.Console(1, Debug.ErrorLogLevel.Error, "ERROR: Config load failed: \r{0}", e);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ static JObject MergeConfigs(JObject doubleConfig)
+ {
+ var system = JObject.FromObject(doubleConfig["system"]);
+ var template = JObject.FromObject(doubleConfig["template"]);
+ var merged = new JObject();
+
+ // Put together top-level objects
+ if (system["info"] != null)
+ merged.Add("info", Merge(template["info"], system["info"]));
+ else
+ merged.Add("info", template["info"]);
+
+ merged.Add("devices", MergeArraysOnTopLevelProperty(template["devices"] as JArray,
+ system["devices"] as JArray, "uid"));
+
+ if (system["rooms"] == null)
+ merged.Add("rooms", template["rooms"]);
+ else
+ merged.Add("rooms", MergeArraysOnTopLevelProperty(template["rooms"] as JArray,
+ system["rooms"] as JArray, "key"));
+
+ if (system["sourceLists"] == null)
+ merged.Add("sourceLists", template["sourceLists"]);
+ else
+ merged.Add("sourceLists", Merge(template["sourceLists"], system["sourceLists"]));
+
+ // Template tie lines take precedence. Config tool doesn't do them at system
+ // level anyway...
+ if (template["tieLines"] != null)
+ merged.Add("tieLines", template["tieLines"]);
+ else if (system["tieLines"] != null)
+ merged.Add("tieLines", system["tieLines"]);
+ else
+ merged.Add("tieLines", new JArray());
+
+ if (system["global"] != null)
+ merged.Add("global", Merge(template["global"], system["global"]));
+ else
+ merged.Add("global", template["global"]);
+
+ Debug.Console(2, "MERGED CONFIG RESULT: \x0d\x0a{0}", merged);
+ return merged;
+ }
+
+ ///
+ /// Merges the contents of a base and a delta array, matching the entries on a top-level property
+ /// given by propertyName. Returns a merge of them. Items in the delta array that do not have
+ /// a matched item in base array will not be merged.
+ ///
+ static JArray MergeArraysOnTopLevelProperty(JArray a1, JArray a2, string propertyName)
+ {
+ var result = new JArray();
+ if (a2 == null)
+ result = a1;
+ else if (a1 != null)
+ {
+ for (int i = 0; i < a1.Count(); i++)
+ {
+ var a1Dev = a1[i];
+ // Try to get a system device and if found, merge it onto template
+ var a2Match = a2.FirstOrDefault(t => t[propertyName].Equals(a1Dev[propertyName]));// t.Value("uid") == tmplDev.Value("uid"));
+ if (a2Match != null)
+ {
+ var mergedItem = Merge(a1Dev, a2Match);// Merge(JObject.FromObject(a1Dev), JObject.FromObject(a2Match));
+ result.Add(mergedItem);
+ }
+ else
+ result.Add(a1Dev);
+ }
+ }
+ return result;
+ }
+
+
+ ///
+ /// Helper for using with JTokens. Converts to JObject
+ ///
+ static JObject Merge(JToken t1, JToken t2)
+ {
+ return Merge(JObject.FromObject(t1), JObject.FromObject(t2));
+ }
+
+ ///
+ /// Merge b ONTO a
+ ///
+ ///
+ ///
+ static JObject Merge(JObject o1, JObject o2)
+ {
+ foreach (var o2Prop in o2)
+ {
+ var o1Value = o1[o2Prop.Key];
+ if (o1Value == null)
+ o1.Add(o2Prop.Key, o2Prop.Value);
+ else
+ {
+ JToken replacement = null;
+ if (o2Prop.Value.HasValues && o1Value.HasValues) // Drill down
+ replacement = Merge(JObject.FromObject(o1Value), JObject.FromObject(o2Prop.Value));
+ else
+ replacement = o2Prop.Value;
+ o1[o2Prop.Key].Replace(replacement);
+ }
+ }
+ return o1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs
index d149bbc..753dbc3 100644
--- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs
+++ b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplArrayLookupChild.cs
@@ -67,7 +67,7 @@ namespace PepperDash.Core.JsonToSimpl
});
if (item == null)
{
- Debug.Console(1, "Child[{0}] Array '{1}' '{2}={3}' not found: ", Key,
+ Debug.Console(1, "JSON Child[{0}] Array '{1}' '{2}={3}' not found: ", Key,
PathPrefix, SearchPropertyName, SearchPropertyValue);
this.LinkedToObject = false;
return false;
@@ -75,17 +75,17 @@ namespace PepperDash.Core.JsonToSimpl
this.LinkedToObject = true;
ArrayIndex = array.IndexOf(item);
- Debug.Console(1, "Child[{0}] Found array match at index {1}", Key, ArrayIndex);
+ Debug.Console(1, "JSON Child[{0}] Found array match at index {1}", Key, ArrayIndex);
return true;
}
catch (Exception e)
{
- Debug.Console(1, "Child[{0}] Array '{1}' lookup error: '{2}={3}'\r{4}", Key,
+ Debug.Console(1, "JSON Child[{0}] Array '{1}' lookup error: '{2}={3}'\r{4}", Key,
PathPrefix, SearchPropertyName, SearchPropertyValue, e);
}
}
else
- Debug.Console(1, "Child[{0}] Path '{1}' is not an array", Key, PathPrefix);
+ Debug.Console(1, "JSON Child[{0}] Path '{1}' is not an array", Key, PathPrefix);
return false;
}
diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs
index caf5ad4..cbed005 100644
--- a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs
+++ b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplChildObjectBase.cs
@@ -170,7 +170,7 @@ namespace PepperDash.Core.JsonToSimpl
bool Process(string path, out string response)
{
path = GetFullPath(path);
- Debug.Console(1, "Child[{0}] Processing {1}", Key, path);
+ Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path);
response = "";
if (Master == null)
{
@@ -266,7 +266,7 @@ namespace PepperDash.Core.JsonToSimpl
var path = GetFullPath(keyPath);
try
{
- Debug.Console(1, "Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
+ Debug.Console(1, "JSON Child[{0}] Queueing value on master {1}='{2}'", Key, path, valueToSave);
//var token = Master.JsonObject.SelectToken(path);
//if (token != null) // The path exists in the file
@@ -274,7 +274,7 @@ namespace PepperDash.Core.JsonToSimpl
}
catch (Exception e)
{
- Debug.Console(1, "Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
+ Debug.Console(1, "JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
}
}
diff --git a/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplPortalFileMaster.cs b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplPortalFileMaster.cs
new file mode 100644
index 0000000..be06091
--- /dev/null
+++ b/Pepperdash Core/Pepperdash Core/JsonToSimpl/JsonToSimplPortalFileMaster.cs
@@ -0,0 +1,219 @@
+using System;
+//using System.IO;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Crestron.SimplSharp;
+using Crestron.SimplSharp.CrestronIO;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+using PepperDash.Core.Config;
+
+namespace PepperDash.Core.JsonToSimpl
+{
+ public class JsonToSimplPortalFileMaster : JsonToSimplMaster
+ {
+ ///
+ /// Sets the filepath as well as registers this with the Global.Masters list
+ ///
+ public string PortalFilepath { get; private set; }
+
+ string ActualFilePath;
+
+ /*****************************************************************************************/
+ /** Privates **/
+
+ // To prevent multiple same-file access
+ object StringBuilderLock = new object();
+ static object FileLock = new object();
+
+ /*****************************************************************************************/
+
+ ///
+ /// SIMPL+ default constructor.
+ ///
+ public JsonToSimplPortalFileMaster()
+ {
+ }
+
+ ///
+ /// Read, evaluate and udpate status
+ ///
+ public void EvaluateFile(string portalFilepath)
+ {
+ PortalFilepath = portalFilepath;
+
+ OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange);
+ if (string.IsNullOrEmpty(PortalFilepath))
+ {
+ CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set");
+ return;
+ }
+
+ // Resolve possible wildcarded filename
+
+ // If the portal file is xyz.json, then
+ // the file we want to check for first will be called xyz.local.json
+ var localFilepath = Path.ChangeExtension(PortalFilepath, "local.json");
+ Debug.Console(0, this, "Checking for local file {0}", localFilepath);
+ var actualLocalFile = GetActualFileInfoFromPath(localFilepath);
+
+ if (actualLocalFile != null)
+ {
+ ActualFilePath = actualLocalFile.FullName;
+ }
+ // If the local file does not exist, then read the portal file xyz.json
+ // and create the local.
+ else
+ {
+ Debug.Console(1, this, "Local JSON file not found {0}\rLoading portal JSON file", localFilepath);
+ var actualPortalFile = GetActualFileInfoFromPath(portalFilepath);
+ if (actualPortalFile != null)
+ {
+ var newLocalPath = Path.ChangeExtension(actualPortalFile.FullName, "local.json");
+ // got the portal file, hand off to the merge / save method
+ PortalConfigReader.ReadAndMergeFileIfNecessary(actualPortalFile.FullName, newLocalPath);
+ ActualFilePath = newLocalPath;
+ }
+ else
+ {
+ var msg = string.Format("Portal JSON file not found: {0}", PortalFilepath);
+ Debug.Console(1, this, msg);
+ ErrorLog.Error(msg);
+ return;
+ }
+ }
+
+ // At this point we should have a local file. Do it.
+ Debug.Console(1, "Reading local JSON file {0}", ActualFilePath);
+
+ string json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
+
+ try
+ {
+ JsonObject = JObject.Parse(json);
+ foreach (var child in Children)
+ child.ProcessAll();
+ OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange);
+ }
+ catch (Exception e)
+ {
+ var msg = string.Format("JSON parsing failed:\r{0}", e);
+ CrestronConsole.PrintLine(msg);
+ ErrorLog.Error(msg);
+ return;
+ }
+ }
+
+ ///
+ /// Returns the FileInfo object for a given path, with possible wildcards
+ ///
+ ///
+ ///
+ FileInfo GetActualFileInfoFromPath(string path)
+ {
+ var dir = Path.GetDirectoryName(path);
+ var localFilename = Path.GetFileName(path);
+ var directory = new DirectoryInfo(dir);
+ // search the directory for the file w/ wildcards
+ return directory.GetFiles(localFilename).FirstOrDefault();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void setDebugLevel(int level)
+ {
+ Debug.SetDebugLevel(level);
+ }
+
+ ///
+ ///
+ ///
+ public override void Save()
+ {
+ // this code is duplicated in the other masters!!!!!!!!!!!!!
+ UnsavedValues = new Dictionary();
+ // Make each child update their values into master object
+ foreach (var child in Children)
+ {
+ Debug.Console(1, "Master [{0}] checking child [{1}] for updates to save", UniqueID, child.Key);
+ child.UpdateInputsForMaster();
+ }
+
+ if (UnsavedValues == null || UnsavedValues.Count == 0)
+ {
+ Debug.Console(1, "Master [{0}] No updated values to save. Skipping", UniqueID);
+ return;
+ }
+ lock (FileLock)
+ {
+ Debug.Console(1, "Saving");
+ foreach (var path in UnsavedValues.Keys)
+ {
+ var tokenToReplace = JsonObject.SelectToken(path);
+ if (tokenToReplace != null)
+ {// It's found
+ tokenToReplace.Replace(UnsavedValues[path]);
+ Debug.Console(1, "JSON Master[{0}] Updating '{1}'", UniqueID, path);
+ }
+ else // No token. Let's make one
+ {
+ //http://stackoverflow.com/questions/17455052/how-to-set-the-value-of-a-json-path-using-json-net
+ Debug.Console(1, "JSON Master[{0}] Cannot write value onto missing property: '{1}'", UniqueID, path);
+
+// JContainer jpart = JsonObject;
+// // walk down the path and find where it goes
+//#warning Does not handle arrays.
+// foreach (var part in path.Split('.'))
+// {
+
+// var openPos = part.IndexOf('[');
+// if (openPos > -1)
+// {
+// openPos++; // move to number
+// var closePos = part.IndexOf(']');
+// var arrayName = part.Substring(0, openPos - 1); // get the name
+// var index = Convert.ToInt32(part.Substring(openPos, closePos - openPos));
+
+// // Check if the array itself exists and add the item if so
+// if (jpart[arrayName] != null)
+// {
+// var arrayObj = jpart[arrayName] as JArray;
+// var item = arrayObj[index];
+// if (item == null)
+// arrayObj.Add(new JObject());
+// }
+
+// Debug.Console(0, "IGNORING MISSING ARRAY VALUE FOR NOW");
+// continue;
+// }
+// // Build the
+// if (jpart[part] == null)
+// jpart.Add(new JProperty(part, new JObject()));
+// jpart = jpart[part] as JContainer;
+// }
+// jpart.Replace(UnsavedValues[path]);
+ }
+ }
+ using (StreamWriter sw = new StreamWriter(ActualFilePath))
+ {
+ try
+ {
+ sw.Write(JsonObject.ToString());
+ sw.Flush();
+ }
+ catch (Exception e)
+ {
+ string err = string.Format("Error writing JSON file:\r{0}", e);
+ Debug.Console(0, err);
+ ErrorLog.Warn(err);
+ return;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs b/Pepperdash Core/Pepperdash Core/Logging/Debug.cs
index 5ac4c58..fbaca1e 100644
--- a/Pepperdash Core/Pepperdash Core/Logging/Debug.cs
+++ b/Pepperdash Core/Pepperdash Core/Logging/Debug.cs
@@ -39,7 +39,7 @@ namespace PepperDash.Core
///
static bool ExcludeAllMode;
- static bool ExcludeNoKeyMessages;
+ //static bool ExcludeNoKeyMessages;
static Dictionary IncludedExcludedKeys;
@@ -141,14 +141,14 @@ namespace PepperDash.Core
IncludedExcludedKeys.Clear();
ExcludeAllMode = true;
}
- else if (lkey == "+nokey")
- {
- ExcludeNoKeyMessages = false;
- }
- else if (lkey == "-nokey")
- {
- ExcludeNoKeyMessages = true;
- }
+ //else if (lkey == "+nokey")
+ //{
+ // ExcludeNoKeyMessages = false;
+ //}
+ //else if (lkey == "-nokey")
+ //{
+ // ExcludeNoKeyMessages = true;
+ //}
else
{
string key = null; ;
diff --git a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj
index ffa77ab..38052fc 100644
--- a/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj
+++ b/Pepperdash Core/Pepperdash Core/PepperDash_Core.csproj
@@ -74,7 +74,9 @@
+
+