refactor: change folder structure

This commit is contained in:
Andrew Welker
2023-02-06 17:30:23 -07:00
parent b1e3b68834
commit 6e7edd1b73
80 changed files with 11679 additions and 11714 deletions

View File

@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
namespace PepperDash.Core.JsonToSimpl
{
/// <summary>
/// Constants for Simpl modules
/// </summary>
public class JsonToSimplConstants
{
/// <summary>
///
/// </summary>
public const ushort BoolValueChange = 1;
/// <summary>
///
/// </summary>
public const ushort JsonIsValidBoolChange = 2;
/// <summary>
/// Reports the if the device is 3-series compatible
/// </summary>
public const ushort ProgramCompatibility3SeriesChange = 3;
/// <summary>
/// Reports the if the device is 4-series compatible
/// </summary>
public const ushort ProgramCompatibility4SeriesChange = 4;
/// <summary>
/// Reports the device platform enum value
/// </summary>
public const ushort DevicePlatformValueChange = 5;
/// <summary>
///
/// </summary>
public const ushort UshortValueChange = 101;
/// <summary>
///
/// </summary>
public const ushort StringValueChange = 201;
/// <summary>
///
/// </summary>
public const ushort FullPathToArrayChange = 202;
/// <summary>
///
/// </summary>
public const ushort ActualFilePathChange = 203;
/// <summary>
///
/// </summary>
public const ushort FilenameResolvedChange = 204;
/// <summary>
///
/// </summary>
public const ushort FilePathResolvedChange = 205;
/// <summary>
/// Reports the root directory change
/// </summary>
public const ushort RootDirectoryChange = 206;
/// <summary>
/// Reports the room ID change
/// </summary>
public const ushort RoomIdChange = 207;
/// <summary>
/// Reports the room name change
/// </summary>
public const ushort RoomNameChange = 208;
}
/// <summary>
/// S+ values delegate
/// </summary>
public delegate void SPlusValuesDelegate();
/// <summary>
/// S+ values wrapper
/// </summary>
public class SPlusValueWrapper
{
/// <summary>
///
/// </summary>
public SPlusType ValueType { get; private set; }
/// <summary>
///
/// </summary>
public ushort Index { get; private set; }
/// <summary>
///
/// </summary>
public ushort BoolUShortValue { get; set; }
/// <summary>
///
/// </summary>
public string StringValue { get; set; }
/// <summary>
///
/// </summary>
public SPlusValueWrapper() {}
/// <summary>
///
/// </summary>
/// <param name="type"></param>
/// <param name="index"></param>
public SPlusValueWrapper(SPlusType type, ushort index)
{
ValueType = type;
Index = index;
}
}
/// <summary>
/// S+ types enum
/// </summary>
public enum SPlusType
{
/// <summary>
/// Digital
/// </summary>
Digital,
/// <summary>
/// Analog
/// </summary>
Analog,
/// <summary>
/// String
/// </summary>
String
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
//using PepperDash.Core;
namespace PepperDash.Core.JsonToSimpl
{
/// <summary>
/// The global class to manage all the instances of JsonToSimplMaster
/// </summary>
public class J2SGlobal
{
static List<JsonToSimplMaster> Masters = new List<JsonToSimplMaster>();
/// <summary>
/// Adds a file master. If the master's key or filename is equivalent to any existing
/// master, this will fail
/// </summary>
/// <param name="master">New master to add</param>
///
public static void AddMaster(JsonToSimplMaster master)
{
if (master == null)
throw new ArgumentNullException("master");
if (string.IsNullOrEmpty(master.UniqueID))
throw new InvalidOperationException("JSON Master cannot be added with a null UniqueId");
Debug.Console(1, "JSON Global adding master {0}", master.UniqueID);
if (Masters.Contains(master)) return;
var existing = Masters.FirstOrDefault(m =>
m.UniqueID.Equals(master.UniqueID, StringComparison.OrdinalIgnoreCase));
if (existing == null)
{
Masters.Add(master);
}
else
{
var msg = string.Format("Cannot add JSON Master with unique ID '{0}'.\rID is already in use on another master.", master.UniqueID);
CrestronConsole.PrintLine(msg);
ErrorLog.Warn(msg);
}
}
/// <summary>
/// Gets a master by its key. Case-insensitive
/// </summary>
public static JsonToSimplMaster GetMasterByFile(string file)
{
return Masters.FirstOrDefault(m => m.UniqueID.Equals(file, StringComparison.OrdinalIgnoreCase));
}
}
}

View File

@@ -0,0 +1,166 @@
extern alias Full;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Full.Newtonsoft.Json;
using Full.Newtonsoft.Json.Linq;
namespace PepperDash.Core.JsonToSimpl
{
/// <summary>
/// Used to interact with an array of values with the S+ modules
/// </summary>
public class JsonToSimplArrayLookupChild : JsonToSimplChildObjectBase
{
/// <summary>
///
/// </summary>
public string SearchPropertyName { get; set; }
/// <summary>
///
/// </summary>
public string SearchPropertyValue { get; set; }
int ArrayIndex;
/// <summary>
/// For gt2.4.1 array lookups
/// </summary>
/// <param name="file"></param>
/// <param name="key"></param>
/// <param name="pathPrefix"></param>
/// <param name="pathSuffix"></param>
/// <param name="searchPropertyName"></param>
/// <param name="searchPropertyValue"></param>
public void Initialize(string file, string key, string pathPrefix, string pathSuffix,
string searchPropertyName, string searchPropertyValue)
{
base.Initialize(file, key, pathPrefix, pathSuffix);
SearchPropertyName = searchPropertyName;
SearchPropertyValue = searchPropertyValue;
}
/// <summary>
/// For newer >=2.4.1 array lookups.
/// </summary>
/// <param name="file"></param>
/// <param name="key"></param>
/// <param name="pathPrefix"></param>
/// <param name="pathAppend"></param>
/// <param name="pathSuffix"></param>
/// <param name="searchPropertyName"></param>
/// <param name="searchPropertyValue"></param>
public void InitializeWithAppend(string file, string key, string pathPrefix, string pathAppend,
string pathSuffix, string searchPropertyName, string searchPropertyValue)
{
string pathPrefixWithAppend = (pathPrefix != null ? pathPrefix : "") + GetPathAppend(pathAppend);
base.Initialize(file, key, pathPrefixWithAppend, pathSuffix);
SearchPropertyName = searchPropertyName;
SearchPropertyValue = searchPropertyValue;
}
//PathPrefix+ArrayName+[x]+path+PathSuffix
/// <summary>
///
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
protected override string GetFullPath(string path)
{
return string.Format("{0}[{1}].{2}{3}",
PathPrefix == null ? "" : PathPrefix,
ArrayIndex,
path,
PathSuffix == null ? "" : PathSuffix);
}
/// <summary>
/// Process all values
/// </summary>
public override void ProcessAll()
{
if (FindInArray())
base.ProcessAll();
}
/// <summary>
/// Provides the path append for GetFullPath
/// </summary>
/// <returns></returns>
string GetPathAppend(string a)
{
if (string.IsNullOrEmpty(a))
{
return "";
}
if (a.StartsWith("."))
{
return a;
}
else
{
return "." + a;
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
bool FindInArray()
{
if (Master == null)
throw new InvalidOperationException("Cannot do operations before master is linked");
if (Master.JsonObject == null)
throw new InvalidOperationException("Cannot do operations before master JSON has read");
if (PathPrefix == null)
throw new InvalidOperationException("Cannot do operations before PathPrefix is set");
var token = Master.JsonObject.SelectToken(PathPrefix);
if (token is JArray)
{
var array = token as JArray;
try
{
var item = array.FirstOrDefault(o =>
{
var prop = o[SearchPropertyName];
return prop != null && prop.Value<string>()
.Equals(SearchPropertyValue, StringComparison.OrdinalIgnoreCase);
});
if (item == null)
{
Debug.Console(1, "JSON Child[{0}] Array '{1}' '{2}={3}' not found: ", Key,
PathPrefix, SearchPropertyName, SearchPropertyValue);
this.LinkedToObject = false;
return false;
}
this.LinkedToObject = true;
ArrayIndex = array.IndexOf(item);
OnStringChange(string.Format("{0}[{1}]", PathPrefix, ArrayIndex), 0, JsonToSimplConstants.FullPathToArrayChange);
Debug.Console(1, "JSON Child[{0}] Found array match at index {1}", Key, ArrayIndex);
return true;
}
catch (Exception e)
{
Debug.Console(1, "JSON Child[{0}] Array '{1}' lookup error: '{2}={3}'\r{4}", Key,
PathPrefix, SearchPropertyName, SearchPropertyValue, e);
}
}
else
{
Debug.Console(1, "JSON Child[{0}] Path '{1}' is not an array", Key, PathPrefix);
}
return false;
}
}
}

View File

@@ -0,0 +1,408 @@
extern alias Full;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Full.Newtonsoft.Json;
using Full.Newtonsoft.Json.Linq;
namespace PepperDash.Core.JsonToSimpl
{
/// <summary>
/// Base class for JSON objects
/// </summary>
public abstract class JsonToSimplChildObjectBase : IKeyed
{
/// <summary>
/// Notifies of bool change
/// </summary>
public event EventHandler<BoolChangeEventArgs> BoolChange;
/// <summary>
/// Notifies of ushort change
/// </summary>
public event EventHandler<UshrtChangeEventArgs> UShortChange;
/// <summary>
/// Notifies of string change
/// </summary>
public event EventHandler<StringChangeEventArgs> StringChange;
/// <summary>
/// Delegate to get all values
/// </summary>
public SPlusValuesDelegate GetAllValuesDelegate { get; set; }
/// <summary>
/// Use a callback to reduce task switch/threading
/// </summary>
public SPlusValuesDelegate SetAllPathsDelegate { get; set; }
/// <summary>
/// Unique identifier for instance
/// </summary>
public string Key { get; protected set; }
/// <summary>
/// This will be prepended to all paths to allow path swapping or for more organized
/// sub-paths
/// </summary>
public string PathPrefix { get; protected set; }
/// <summary>
/// This is added to the end of all paths
/// </summary>
public string PathSuffix { get; protected set; }
/// <summary>
/// Indicates if the instance is linked to an object
/// </summary>
public bool LinkedToObject { get; protected set; }
/// <summary>
/// Reference to Master instance
/// </summary>
protected JsonToSimplMaster Master;
/// <summary>
/// Paths to boolean values in JSON structure
/// </summary>
protected Dictionary<ushort, string> BoolPaths = new Dictionary<ushort, string>();
/// <summary>
/// Paths to numeric values in JSON structure
/// </summary>
protected Dictionary<ushort, string> UshortPaths = new Dictionary<ushort, string>();
/// <summary>
/// Paths to string values in JSON structure
/// </summary>
protected Dictionary<ushort, string> StringPaths = new Dictionary<ushort, string>();
/// <summary>
/// Call this before doing anything else
/// </summary>
/// <param name="masterUniqueId"></param>
/// <param name="key"></param>
/// <param name="pathPrefix"></param>
/// <param name="pathSuffix"></param>
public void Initialize(string masterUniqueId, string key, string pathPrefix, string pathSuffix)
{
Key = key;
PathPrefix = pathPrefix;
PathSuffix = pathSuffix;
Master = J2SGlobal.GetMasterByFile(masterUniqueId);
if (Master != null)
Master.AddChild(this);
else
Debug.Console(1, "JSON Child [{0}] cannot link to master {1}", key, masterUniqueId);
}
/// <summary>
/// Sets the path prefix for the object
/// </summary>
/// <param name="pathPrefix"></param>
public void SetPathPrefix(string pathPrefix)
{
PathPrefix = pathPrefix;
}
/// <summary>
/// Set the JPath to evaluate for a given bool out index.
/// </summary>
public void SetBoolPath(ushort index, string path)
{
Debug.Console(1, "JSON Child[{0}] SetBoolPath {1}={2}", Key, index, path);
if (path == null || path.Trim() == string.Empty) return;
BoolPaths[index] = path;
}
/// <summary>
/// Set the JPath for a ushort out index.
/// </summary>
public void SetUshortPath(ushort index, string path)
{
Debug.Console(1, "JSON Child[{0}] SetUshortPath {1}={2}", Key, index, path);
if (path == null || path.Trim() == string.Empty) return;
UshortPaths[index] = path;
}
/// <summary>
/// Set the JPath for a string output index.
/// </summary>
public void SetStringPath(ushort index, string path)
{
Debug.Console(1, "JSON Child[{0}] SetStringPath {1}={2}", Key, index, path);
if (path == null || path.Trim() == string.Empty) return;
StringPaths[index] = path;
}
/// <summary>
/// Evalutates all outputs with defined paths. called by S+ when paths are ready to process
/// and by Master when file is read.
/// </summary>
public virtual void ProcessAll()
{
if (!LinkedToObject)
{
Debug.Console(1, this, "Not linked to object in file. Skipping");
return;
}
if (SetAllPathsDelegate == null)
{
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring ProcessAll");
return;
}
SetAllPathsDelegate();
foreach (var kvp in BoolPaths)
ProcessBoolPath(kvp.Key);
foreach (var kvp in UshortPaths)
ProcessUshortPath(kvp.Key);
foreach (var kvp in StringPaths)
ProcessStringPath(kvp.Key);
}
/// <summary>
/// Processes a bool property, converting to bool, firing off a BoolChange event
/// </summary>
void ProcessBoolPath(ushort index)
{
string response;
if (Process(BoolPaths[index], out response))
OnBoolChange(response.Equals("true", StringComparison.OrdinalIgnoreCase),
index, JsonToSimplConstants.BoolValueChange);
else { }
// OnBoolChange(false, index, JsonToSimplConstants.BoolValueChange);
}
// Processes the path to a ushort, converting to ushort if able, twos complement if necessary, firing off UshrtChange event
void ProcessUshortPath(ushort index) {
string response;
if (Process(UshortPaths[index], out response)) {
ushort val;
try { val = Convert.ToInt32(response) < 0 ? (ushort)(Convert.ToInt16(response) + 65536) : Convert.ToUInt16(response); }
catch { val = 0; }
OnUShortChange(val, index, JsonToSimplConstants.UshortValueChange);
}
else { }
// OnUShortChange(0, index, JsonToSimplConstants.UshortValueChange);
}
// Processes the path to a string property and fires of a StringChange event.
void ProcessStringPath(ushort index)
{
string response;
if (Process(StringPaths[index], out response))
OnStringChange(response, index, JsonToSimplConstants.StringValueChange);
else { }
// OnStringChange("", index, JsonToSimplConstants.StringValueChange);
}
/// <summary>
/// Processes the given path.
/// </summary>
/// <param name="path">JPath formatted path to the desired property</param>
/// <param name="response">The string value of the property, or a default value if it
/// doesn't exist</param>
/// <returns> This will return false in the case that EvaulateAllOnJsonChange
/// is false and the path does not evaluate to a property in the incoming JSON. </returns>
bool Process(string path, out string response)
{
path = GetFullPath(path);
Debug.Console(1, "JSON Child[{0}] Processing {1}", Key, path);
response = "";
if (Master == null)
{
Debug.Console(1, "JSONChild[{0}] cannot process without Master attached", Key);
return false;
}
if (Master.JsonObject != null && path != string.Empty)
{
bool isCount = false;
path = path.Trim();
if (path.EndsWith(".Count"))
{
path = path.Remove(path.Length - 6, 6);
isCount = true;
}
try // Catch a strange cast error on a bad path
{
var t = Master.JsonObject.SelectToken(path);
if (t != null)
{
// return the count of children objects - if any
if (isCount)
response = (t.HasValues ? t.Children().Count() : 0).ToString();
else
response = t.Value<string>();
Debug.Console(1, " ='{0}'", response);
return true;
}
}
catch
{
response = "";
}
}
// If the path isn't found, return this to determine whether to pass out the non-value or not.
return false;
}
//************************************************************************************************
// Save-related functions
/// <summary>
/// Called from Master to read inputs and update their values in master JObject
/// Callback should hit one of the following four methods
/// </summary>
public void UpdateInputsForMaster()
{
if (!LinkedToObject)
{
Debug.Console(1, this, "Not linked to object in file. Skipping");
return;
}
if (SetAllPathsDelegate == null)
{
Debug.Console(1, this, "No SetAllPathsDelegate set. Ignoring UpdateInputsForMaster");
return;
}
SetAllPathsDelegate();
var del = GetAllValuesDelegate;
if (del != null)
GetAllValuesDelegate();
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="theValue"></param>
public void USetBoolValue(ushort key, ushort theValue)
{
SetBoolValue(key, theValue == 1);
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="theValue"></param>
public void SetBoolValue(ushort key, bool theValue)
{
if (BoolPaths.ContainsKey(key))
SetValueOnMaster(BoolPaths[key], new JValue(theValue));
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="theValue"></param>
public void SetUShortValue(ushort key, ushort theValue)
{
if (UshortPaths.ContainsKey(key))
SetValueOnMaster(UshortPaths[key], new JValue(theValue));
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="theValue"></param>
public void SetStringValue(ushort key, string theValue)
{
if (StringPaths.ContainsKey(key))
SetValueOnMaster(StringPaths[key], new JValue(theValue));
}
/// <summary>
///
/// </summary>
/// <param name="keyPath"></param>
/// <param name="valueToSave"></param>
public void SetValueOnMaster(string keyPath, JValue valueToSave)
{
var path = GetFullPath(keyPath);
try
{
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
Master.AddUnsavedValue(path, valueToSave);
}
catch (Exception e)
{
Debug.Console(1, "JSON Child[{0}] Failed setting value for path '{1}'\r{2}", Key, path, e);
}
}
/// <summary>
/// Called during Process(...) to get the path to a given property. By default,
/// returns PathPrefix+path+PathSuffix. Override to change the way path is built.
/// </summary>
protected virtual string GetFullPath(string path)
{
return (PathPrefix != null ? PathPrefix : "") +
path + (PathSuffix != null ? PathSuffix : "");
}
// Helpers for events
//******************************************************************************************
/// <summary>
/// Event helper
/// </summary>
/// <param name="state"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnBoolChange(bool state, ushort index, ushort type)
{
var handler = BoolChange;
if (handler != null)
{
var args = new BoolChangeEventArgs(state, type);
args.Index = index;
BoolChange(this, args);
}
}
//******************************************************************************************
/// <summary>
/// Event helper
/// </summary>
/// <param name="state"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnUShortChange(ushort state, ushort index, ushort type)
{
var handler = UShortChange;
if (handler != null)
{
var args = new UshrtChangeEventArgs(state, type);
args.Index = index;
UShortChange(this, args);
}
}
/// <summary>
/// Event helper
/// </summary>
/// <param name="value"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnStringChange(string value, ushort index, ushort type)
{
var handler = StringChange;
if (handler != null)
{
var args = new StringChangeEventArgs(value, type);
args.Index = index;
StringChange(this, args);
}
}
}
}

View File

@@ -0,0 +1,292 @@
extern alias Full;
using System;
//using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Full.Newtonsoft.Json;
using Full.Newtonsoft.Json.Linq;
namespace PepperDash.Core.JsonToSimpl
{
/// <summary>
/// Represents a JSON file that can be read and written to
/// </summary>
public class JsonToSimplFileMaster : JsonToSimplMaster
{
/// <summary>
/// Sets the filepath as well as registers this with the Global.Masters list
/// </summary>
public string Filepath { get; private set; }
/// <summary>
/// Filepath to the actual file that will be read (Portal or local)
/// </summary>
public string ActualFilePath { get; private set; }
/// <summary>
///
/// </summary>
public string Filename { get; private set; }
/// <summary>
///
/// </summary>
public string FilePathName { get; private set; }
/*****************************************************************************************/
/** Privates **/
// The JSON file in JObject form
// For gathering the incoming data
object StringBuilderLock = new object();
// To prevent multiple same-file access
static object FileLock = new object();
/*****************************************************************************************/
/// <summary>
/// SIMPL+ default constructor.
/// </summary>
public JsonToSimplFileMaster()
{
}
/// <summary>
/// Read, evaluate and udpate status
/// </summary>
public void EvaluateFile(string filepath)
{
try
{
OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange);
var dirSeparator = Path.DirectorySeparatorChar;
var dirSeparatorAlt = Path.AltDirectorySeparatorChar;
var series = CrestronEnvironment.ProgramCompatibility;
var is3Series = (eCrestronSeries.Series3 == (series & eCrestronSeries.Series3));
OnBoolChange(is3Series, 0,
JsonToSimplConstants.ProgramCompatibility3SeriesChange);
var is4Series = (eCrestronSeries.Series4 == (series & eCrestronSeries.Series4));
OnBoolChange(is4Series, 0,
JsonToSimplConstants.ProgramCompatibility4SeriesChange);
var isServer = CrestronEnvironment.DevicePlatform == eDevicePlatform.Server;
OnBoolChange(isServer, 0,
JsonToSimplConstants.DevicePlatformValueChange);
// get the roomID
var roomId = Crestron.SimplSharp.InitialParametersClass.RoomId;
if (!string.IsNullOrEmpty(roomId))
{
OnStringChange(roomId, 0, JsonToSimplConstants.RoomIdChange);
}
// get the roomName
var roomName = Crestron.SimplSharp.InitialParametersClass.RoomName;
if (!string.IsNullOrEmpty(roomName))
{
OnStringChange(roomName, 0, JsonToSimplConstants.RoomNameChange);
}
var rootDirectory = Directory.GetApplicationRootDirectory();
OnStringChange(rootDirectory, 0, JsonToSimplConstants.RootDirectoryChange);
var splusPath = string.Empty;
if (Regex.IsMatch(filepath, @"user", RegexOptions.IgnoreCase))
{
if (is4Series)
splusPath = Regex.Replace(filepath, "user", "user", RegexOptions.IgnoreCase);
else if (isServer)
splusPath = Regex.Replace(filepath, "user", "User", RegexOptions.IgnoreCase);
else
splusPath = filepath;
}
filepath = splusPath.Replace(dirSeparatorAlt, dirSeparator);
Filepath = string.Format("{1}{0}{2}", dirSeparator, rootDirectory,
filepath.TrimStart(dirSeparator, dirSeparatorAlt));
OnStringChange(string.Format("Attempting to evaluate {0}", Filepath), 0, JsonToSimplConstants.StringValueChange);
if (string.IsNullOrEmpty(Filepath))
{
OnStringChange(string.Format("Cannot evaluate file. JSON file path not set"), 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine("Cannot evaluate file. JSON file path not set");
return;
}
// get file directory and name to search
var fileDirectory = Path.GetDirectoryName(Filepath);
var fileName = Path.GetFileName(Filepath);
OnStringChange(string.Format("Checking '{0}' for '{1}'", fileDirectory, fileName), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "Checking '{0}' for '{1}'", fileDirectory, fileName);
if (Directory.Exists(fileDirectory))
{
// get the directory info
var directoryInfo = new DirectoryInfo(fileDirectory);
// get the file to be read
var actualFile = directoryInfo.GetFiles(fileName).FirstOrDefault();
if (actualFile == null)
{
var msg = string.Format("JSON file not found: {0}", Filepath);
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(msg);
ErrorLog.Error(msg);
return;
}
// \xSE\xR\PDT000-Template_Main_Config-Combined_DSP_v00.02.json
// \USER\PDT000-Template_Main_Config-Combined_DSP_v00.02.json
ActualFilePath = actualFile.FullName;
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
OnStringChange(string.Format("Actual JSON file is {0}", ActualFilePath), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "Actual JSON file is {0}", ActualFilePath);
Filename = actualFile.Name;
OnStringChange(Filename, 0, JsonToSimplConstants.FilenameResolvedChange);
OnStringChange(string.Format("JSON Filename is {0}", Filename), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "JSON Filename is {0}", Filename);
FilePathName = string.Format(@"{0}{1}", actualFile.DirectoryName, dirSeparator);
OnStringChange(string.Format(@"{0}", actualFile.DirectoryName), 0, JsonToSimplConstants.FilePathResolvedChange);
OnStringChange(string.Format(@"JSON File Path is {0}", actualFile.DirectoryName), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "JSON File Path is {0}", FilePathName);
var json = File.ReadToEnd(ActualFilePath, System.Text.Encoding.ASCII);
JsonObject = JObject.Parse(json);
foreach (var child in Children)
child.ProcessAll();
OnBoolChange(true, 0, JsonToSimplConstants.JsonIsValidBoolChange);
}
else
{
OnStringChange(string.Format("'{0}' not found", fileDirectory), 0, JsonToSimplConstants.StringValueChange);
Debug.Console(1, "'{0}' not found", fileDirectory);
}
}
catch (Exception e)
{
var msg = string.Format("EvaluateFile Exception: Message\r{0}", e.Message);
OnStringChange(msg, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(msg);
ErrorLog.Error(msg);
var stackTrace = string.Format("EvaluateFile: Stack Trace\r{0}", e.StackTrace);
OnStringChange(stackTrace, 0, JsonToSimplConstants.StringValueChange);
CrestronConsole.PrintLine(stackTrace);
ErrorLog.Error(stackTrace);
}
}
/// <summary>
/// Sets the debug level
/// </summary>
/// <param name="level"></param>
public void setDebugLevel(int level)
{
Debug.SetDebugLevel(level);
}
/// <summary>
/// Saves the values to the file
/// </summary>
public override void Save()
{
// this code is duplicated in the other masters!!!!!!!!!!!!!
UnsavedValues = new Dictionary<string, JValue>();
// 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;
}
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
extern alias Full;
using System;
//using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Full.Newtonsoft.Json;
using Full.Newtonsoft.Json.Linq;
namespace PepperDash.Core.JsonToSimpl
{
/// <summary>
///
/// </summary>
public class JsonToSimplFixedPathObject : JsonToSimplChildObjectBase
{
/// <summary>
/// Constructor
/// </summary>
public JsonToSimplFixedPathObject()
{
this.LinkedToObject = true;
}
}
}

View File

@@ -0,0 +1,120 @@
extern alias Full;
using System;
using System.Collections.Generic;
using Crestron.SimplSharp;
using Full.Newtonsoft.Json.Linq;
namespace PepperDash.Core.JsonToSimpl
{
/// <summary>
/// Generic Master
/// </summary>
public class JsonToSimplGenericMaster : JsonToSimplMaster
{
/*****************************************************************************************/
/** Privates **/
// The JSON file in JObject form
// For gathering the incoming data
object StringBuilderLock = new object();
// To prevent multiple same-file access
static object WriteLock = new object();
/// <summary>
/// Callback action for saving
/// </summary>
public Action<string> SaveCallback { get; set; }
/*****************************************************************************************/
/// <summary>
/// SIMPL+ default constructor.
/// </summary>
public JsonToSimplGenericMaster()
{
}
/// <summary>
/// Loads in JSON and triggers evaluation on all children
/// </summary>
/// <param name="json"></param>
public void LoadWithJson(string json)
{
OnBoolChange(false, 0, JsonToSimplConstants.JsonIsValidBoolChange);
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);
}
}
/// <summary>
/// Loads JSON into JsonObject, but does not trigger evaluation by children
/// </summary>
/// <param name="json"></param>
public void SetJsonWithoutEvaluating(string json)
{
try
{
JsonObject = JObject.Parse(json);
}
catch (Exception e)
{
Debug.Console(0, this, "JSON parsing failed:\r{0}", e);
}
}
/// <summary>
///
/// </summary>
public override void Save()
{
// this code is duplicated in the other masters!!!!!!!!!!!!!
UnsavedValues = new Dictionary<string, JValue>();
// Make each child update their values into master object
foreach (var child in Children)
{
Debug.Console(1, this, "Master. checking child [{0}] for updates to save", child.Key);
child.UpdateInputsForMaster();
}
if (UnsavedValues == null || UnsavedValues.Count == 0)
{
Debug.Console(1, this, "Master. No updated values to save. Skipping");
return;
}
lock (WriteLock)
{
Debug.Console(1, this, "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, this, "Master Updating '{0}'", path);
}
else // No token. Let's make one
{
Debug.Console(1, "Master Cannot write value onto missing property: '{0}'", path);
}
}
}
if (SaveCallback != null)
SaveCallback(JsonObject.ToString());
else
Debug.Console(0, this, "WARNING: No save callback defined.");
}
}
}

View File

@@ -0,0 +1,243 @@
extern alias Full;
using System;
//using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Full.Newtonsoft.Json;
using Full.Newtonsoft.Json.Linq;
namespace PepperDash.Core.JsonToSimpl
{
/// <summary>
/// Abstract base class for JsonToSimpl interactions
/// </summary>
public abstract class JsonToSimplMaster : IKeyed
{
/// <summary>
/// Notifies of bool change
/// </summary>
public event EventHandler<BoolChangeEventArgs> BoolChange;
/// <summary>
/// Notifies of ushort change
/// </summary>
public event EventHandler<UshrtChangeEventArgs> UshrtChange;
/// <summary>
/// Notifies of string change
/// </summary>
public event EventHandler<StringChangeEventArgs> StringChange;
/// <summary>
/// A collection of associated child modules
/// </summary>
protected List<JsonToSimplChildObjectBase> Children = new List<JsonToSimplChildObjectBase>();
/*****************************************************************************************/
/// <summary>
/// Mirrors the Unique ID for now.
/// </summary>
public string Key { get { return UniqueID; } }
/// <summary>
/// A unique ID
/// </summary>
public string UniqueID { get; protected set; }
/// <summary>
/// Merely for use in debug messages
/// </summary>
public string DebugName
{
get { return _DebugName; }
set { if (DebugName == null) _DebugName = ""; else _DebugName = value; }
}
string _DebugName = "";
/// <summary>
/// This will be prepended to all paths to allow path swapping or for more organized
/// sub-paths
/// </summary>
public string PathPrefix { get; set; }
/// <summary>
/// This is added to the end of all paths
/// </summary>
public string PathSuffix { get; set; }
/// <summary>
/// Enables debugging output to the console. Certain error messages will be logged to the
/// system's error log regardless of this setting
/// </summary>
public bool DebugOn { get; set; }
/// <summary>
/// Ushort helper for Debug property
/// </summary>
public ushort UDebug
{
get { return (ushort)(DebugOn ? 1 : 0); }
set
{
DebugOn = (value == 1);
CrestronConsole.PrintLine("JsonToSimpl debug={0}", DebugOn);
}
}
/// <summary>
///
/// </summary>
public JObject JsonObject { get; protected set; }
/*****************************************************************************************/
/** Privates **/
// The JSON file in JObject form
// For gathering the incoming data
protected Dictionary<string, JValue> UnsavedValues = new Dictionary<string, JValue>();
/*****************************************************************************************/
/// <summary>
/// SIMPL+ default constructor.
/// </summary>
public JsonToSimplMaster()
{
}
/// <summary>
/// Sets up class - overriding methods should always call this.
/// </summary>
/// <param name="uniqueId"></param>
public virtual void Initialize(string uniqueId)
{
UniqueID = uniqueId;
J2SGlobal.AddMaster(this); // Should not re-add
}
/// <summary>
/// Adds a child "module" to this master
/// </summary>
/// <param name="child"></param>
public void AddChild(JsonToSimplChildObjectBase child)
{
if (!Children.Contains(child))
{
Children.Add(child);
}
}
/// <summary>
/// Called from the child to add changed or new values for saving
/// </summary>
public void AddUnsavedValue(string path, JValue value)
{
if (UnsavedValues.ContainsKey(path))
{
Debug.Console(0, "Master[{0}] WARNING - Attempt to add duplicate value for path '{1}'.\r Ingoring. Please ensure that path does not exist on multiple modules.", UniqueID, path);
}
else
UnsavedValues.Add(path, value);
//Debug.Console(0, "Master[{0}] Unsaved size={1}", UniqueID, UnsavedValues.Count);
}
/// <summary>
/// Saves the file
/// </summary>
public abstract void Save();
/// <summary>
///
/// </summary>
public static class JsonFixes
{
/// <summary>
/// Deserializes a string into a JObject
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
public static JObject ParseObject(string json)
{
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
{
var startDepth = reader.Depth;
var obj = JObject.Load(reader);
if (startDepth != reader.Depth)
throw new JsonSerializationException("Unenclosed json found");
return obj;
}
}
/// <summary>
/// Deserializes a string into a JArray
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
public static JArray ParseArray(string json)
{
using (var reader = new JsonTextReader(new System.IO.StringReader(json)))
{
var startDepth = reader.Depth;
var obj = JArray.Load(reader);
if (startDepth != reader.Depth)
throw new JsonSerializationException("Unenclosed json found");
return obj;
}
}
}
/// <summary>
/// Helper event
/// </summary>
/// <param name="state"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnBoolChange(bool state, ushort index, ushort type)
{
if (BoolChange != null)
{
var args = new BoolChangeEventArgs(state, type);
args.Index = index;
BoolChange(this, args);
}
}
/// <summary>
/// Helper event
/// </summary>
/// <param name="state"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnUshrtChange(ushort state, ushort index, ushort type)
{
if (UshrtChange != null)
{
var args = new UshrtChangeEventArgs(state, type);
args.Index = index;
UshrtChange(this, args);
}
}
/// <summary>
/// Helper event
/// </summary>
/// <param name="value"></param>
/// <param name="index"></param>
/// <param name="type"></param>
protected void OnStringChange(string value, ushort index, ushort type)
{
if (StringChange != null)
{
var args = new StringChangeEventArgs(value, type);
args.Index = index;
StringChange(this, args);
}
}
}
}

View File

@@ -0,0 +1,197 @@
extern alias Full;
using System;
//using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Crestron.SimplSharp;
using Crestron.SimplSharp.CrestronIO;
using Full.Newtonsoft.Json;
using Full.Newtonsoft.Json.Linq;
using PepperDash.Core.Config;
namespace PepperDash.Core.JsonToSimpl
{
/// <summary>
/// Portal File Master
/// </summary>
public class JsonToSimplPortalFileMaster : JsonToSimplMaster
{
/// <summary>
/// Sets the filepath as well as registers this with the Global.Masters list
/// </summary>
public string PortalFilepath { get; private set; }
/// <summary>
/// File path of the actual file being read (Portal or local)
/// </summary>
public string ActualFilePath { get; private set; }
/*****************************************************************************************/
/** Privates **/
// To prevent multiple same-file access
object StringBuilderLock = new object();
static object FileLock = new object();
/*****************************************************************************************/
/// <summary>
/// SIMPL+ default constructor.
/// </summary>
public JsonToSimplPortalFileMaster()
{
}
/// <summary>
/// Read, evaluate and udpate status
/// </summary>
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;
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
}
// 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;
OnStringChange(ActualFilePath, 0, JsonToSimplConstants.ActualFilePathChange);
}
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;
}
}
/// <summary>
/// Returns the FileInfo object for a given path, with possible wildcards
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
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();
}
/// <summary>
///
/// </summary>
/// <param name="level"></param>
public void setDebugLevel(int level)
{
Debug.SetDebugLevel(level);
}
/// <summary>
///
/// </summary>
public override void Save()
{
// this code is duplicated in the other masters!!!!!!!!!!!!!
UnsavedValues = new Dictionary<string, JValue>();
// 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);
}
}
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;
}
}
}
}
}
}