diff --git a/QSharp.sln b/QSharp.sln index 448ac76..5daef5a 100644 --- a/QSharp.sln +++ b/QSharp.sln @@ -1,23 +1,60 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QSharp", "QSharp\QSharp.csproj", "{51CD2579-79C9-49A9-B68D-93CBE878BAA1}" +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30128.74 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QSharp", "QSharp\QSharp.csproj", "{51CD2579-79C9-49A9-B68D-93CBE878BAA1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QSharpDemo", "QSharpDemo\QSharpDemo.csproj", "{8518D042-D511-4DBB-8406-5B11B2777ED2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QSharpDemo", "QSharpDemo\QSharpDemo.csproj", "{8518D042-D511-4DBB-8406-5B11B2777ED2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|ARM.ActiveCfg = Debug|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|ARM.Build.0 = Debug|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|x64.ActiveCfg = Debug|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|x64.Build.0 = Debug|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|x86.ActiveCfg = Debug|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|x86.Build.0 = Debug|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|Any CPU.ActiveCfg = Release|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|Any CPU.Build.0 = Release|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|ARM.ActiveCfg = Release|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|ARM.Build.0 = Release|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|x64.ActiveCfg = Release|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|x64.Build.0 = Release|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|x86.ActiveCfg = Release|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|x86.Build.0 = Release|Any CPU {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|ARM.ActiveCfg = Debug|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|ARM.Build.0 = Debug|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|x64.ActiveCfg = Debug|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|x64.Build.0 = Debug|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|x86.ActiveCfg = Debug|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|x86.Build.0 = Debug|Any CPU {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|Any CPU.ActiveCfg = Release|Any CPU {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|Any CPU.Build.0 = Release|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|ARM.ActiveCfg = Release|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|ARM.Build.0 = Release|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|x64.ActiveCfg = Release|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|x64.Build.0 = Release|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|x86.ActiveCfg = Release|Any CPU + {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E5BF7535-FB56-4373-89F2-B0ABBD010092} EndGlobalSection EndGlobal diff --git a/QSharp/Events/QEventArgs.cs b/QSharp/Events/QEventArgs.cs new file mode 100644 index 0000000..b67d198 --- /dev/null +++ b/QSharp/Events/QEventArgs.cs @@ -0,0 +1,72 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; + +namespace QSharp +{ + public class QServerFoundArgs : EventArgs + { + public QServer server { get; set; } + } + public class QWorkspacesUpdatedArgs : EventArgs + { + public List Workspaces { get; set; } + } + + public class QWorkspaceUpdatedArgs : EventArgs + { + + } + + public class QWorkspaceSettingsUpdatedArgs : EventArgs + { + public string settingsType { get; set; } + + } + + public class QWorkspaceLightDashboardUpdatedArgs : EventArgs + { + + } + + public class QWorkspaceConnectedArgs : EventArgs + { + + } + + public class QWorkspaceDisconnectedArgs : EventArgs + { + + } + + public class QWorkspaceConnectionErrorArgs : EventArgs + { + public string status { get; set; } + } + public class QQLabPreferencesUpdatedArgs : EventArgs + { + public string key { get; set; } + + } + + public class QCueUpdatedArgs : EventArgs + { + + } + + public class QCueListsUpdatedArgs : EventArgs + { + public JToken data { get; set; } + } + + public class QCueNeedsUpdatedArgs : EventArgs + { + public string cueID { get; set; } + } + + public class QCueListChangedPlaybackPositionArgs : EventArgs + { + public string cueID { get; set; } + } +} + diff --git a/QSharp/Events/QEventHandlers.cs b/QSharp/Events/QEventHandlers.cs new file mode 100644 index 0000000..cf6157f --- /dev/null +++ b/QSharp/Events/QEventHandlers.cs @@ -0,0 +1,21 @@ + +namespace QSharp +{ + public delegate void QServerFoundHandler(object source, QServerFoundArgs args); + + public delegate void QWorkspacesUpdatedHandler(object source, QWorkspacesUpdatedArgs args); + + public delegate void QWorkspaceUpdatedHandler(object source, QWorkspaceUpdatedArgs args); + public delegate void QWorkspaceSettingsUpdatedHandler(object source, QWorkspaceSettingsUpdatedArgs args); + public delegate void QWorkspaceLightDashboardUpdatedHandler(object source, QWorkspaceLightDashboardUpdatedArgs args); + public delegate void QWorkspaceConnectedHandler(object source, QWorkspaceConnectedArgs args); + public delegate void QWorkspaceDisconnectedHandler(object source, QWorkspaceDisconnectedArgs args); + public delegate void QWorkspaceConnectionErrorHandler(object source, QWorkspaceConnectionErrorArgs args); + public delegate void QQLabPreferencesUpdatedHandler(object source, QQLabPreferencesUpdatedArgs args); + + + public delegate void QCueUpdatedHandler(object source, QCueUpdatedArgs args); + public delegate void QCueListsUpdatesHandler(object source, QCueListsUpdatedArgs args); + public delegate void QCueNeedsUpdatedHandler(object source, QCueNeedsUpdatedArgs args); + public delegate void QCueListChangedPlaybackPositionHandler(object source, QCueListChangedPlaybackPositionArgs args); +} diff --git a/QSharp/QBrowser.cs b/QSharp/QBrowser.cs index 0239017..f4243f1 100644 --- a/QSharp/QBrowser.cs +++ b/QSharp/QBrowser.cs @@ -6,49 +6,50 @@ namespace QSharp { public class QBrowser { - const string UDPServiceType = "_qlab._udp.local."; - const string TCPServiceType = "_qlab._tcp.local."; - const int udpPort = 53000; + ZeroconfResolver.ResolverListener zeroconfTCPBrowser; - IObservable netServices; - ZeroconfResolver.ResolverListener netServiceTCPBrowser; + public ObservableCollection servers = new ObservableCollection(); + + public event QServerFoundHandler ServerFound; - ObservableCollection servers = new ObservableCollection(); public QBrowser() { Console.WriteLine("QBrowser Init"); - netServiceTCPBrowser = ZeroconfResolver.CreateListener(TCPServiceType); - netServiceTCPBrowser.ServiceFound += ServerFound; - netServiceTCPBrowser.ServiceLost += ServerLost; + zeroconfTCPBrowser = ZeroconfResolver.CreateListener(QBonjour.TCPService); + zeroconfTCPBrowser.ServiceFound += ZeroconfHostFound; + zeroconfTCPBrowser.ServiceLost += ZeroconfHostLost; } - private void ServerLost(object sender, IZeroconfHost e) + private void ZeroconfHostLost(object sender, IZeroconfHost e) { Console.WriteLine($"Lost {e.DisplayName} : {e.IPAddress}"); } - private void ServerFound(object sender, IZeroconfHost e) + private void ZeroconfHostFound(object sender, IZeroconfHost e) { foreach(var service in e.Services) { - if (service.Key.Equals(TCPServiceType)) + if (service.Key.Equals(QBonjour.TCPService)) { Console.WriteLine($"Found {e.DisplayName} : {e.IPAddress} : {service.Value.Port}"); - QServer server = getServerFromAddress(e.IPAddress); + QServer server = serverForAddress(e.IPAddress); if(server == null) { Console.WriteLine("New Server Found so adding"); - QServer serverToAdd = new QServer(e.IPAddress, service.Value.Port, e.DisplayName); + QServer serverToAdd = new QServer(e.IPAddress, service.Value.Port); + serverToAdd.name = e.DisplayName; + serverToAdd.zeroconfHost = e; servers.Add(serverToAdd); serverToAdd.refreshWorkspaces(); + OnServerFound(serverToAdd); } else { @@ -60,9 +61,8 @@ namespace QSharp } - public QServer getServerFromAddress(string address) + public QServer serverForAddress(string address) { - QServer foundServer = null; foreach(var server in servers) { if (server.host.Equals(address)) @@ -71,7 +71,27 @@ namespace QSharp } } - return foundServer; + return null; + } + + public QServer serverForIZeroconfHost(IZeroconfHost zeroconfHost) + { + foreach (var server in servers) + { + if (server.zeroconfHost == zeroconfHost) + { + return server; + } + } + + return null; + } + + protected virtual void OnServerFound(QServer server) + { + if (ServerFound != null) + ServerFound(this, new QServerFoundArgs { server = server }); + } } } \ No newline at end of file diff --git a/QSharp/QClient.cs b/QSharp/QClient.cs index 42eecd6..a206123 100644 --- a/QSharp/QClient.cs +++ b/QSharp/QClient.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using SharpOSC; namespace QSharp @@ -9,62 +11,208 @@ namespace QSharp { TCPClient tcpClient; - public class WorkspacesUpdatedArgs : EventArgs - { - public List Workspaces { get; set; } - } + public event QWorkspacesUpdatedHandler WorkspacesUpdated; + + public event QCueUpdatedHandler CueUpdated; + public event QCueListsUpdatesHandler CueListsUpdated; + public event QCueNeedsUpdatedHandler CueNeedsUpdated; + public event QCueListChangedPlaybackPositionHandler CueListChangedPlaybackPosition; - public delegate void WorkspacesUpdatedHandler(object source, WorkspacesUpdatedArgs args); - public event WorkspacesUpdatedHandler WorkspacesUpdated; + public event QWorkspaceUpdatedHandler WorkspaceUpdated; + public event QWorkspaceSettingsUpdatedHandler WorkspaceSettingsUpdated; + + public event QWorkspaceLightDashboardUpdatedHandler WorkspaceLightDashboardUpdated; + public event QQLabPreferencesUpdatedHandler QLabPreferencesUpdated; + + public event QWorkspaceDisconnectedHandler WorkspaceDisconnected; + public event QWorkspaceConnectedHandler WorkspaceConnected; + public event QWorkspaceConnectionErrorHandler WorkspaceConnectionError; public QClient(string host, int port) { tcpClient = new TCPClient(host, port); - tcpClient.Connect(); - Console.WriteLine($"QClient setup {host}:{port}"); - tcpClient.MessageReceived += ResponseReceived; + Console.WriteLine($"[QClient] setup connection to: <{host}:{port}>"); + tcpClient.MessageReceived += ProcessMessage; } - private void ResponseReceived(object source, MessageEventArgs args) + + public bool IsConnnected { get { return tcpClient.IsConnected; } } + + public bool connect() { - OscMessage msg = args.Message; - foreach (var obj in msg.Arguments) - { - QResponse response = JsonConvert.DeserializeObject(obj.ToString()); - - switch (response.getReplyType()) - { - case "WORKSPACES": - Console.WriteLine("WORKSPACES RECEIVED"); - OnWorkspacesUpdated(response); - break; - default: - break; - } - - } + return tcpClient.Connect(); } + public void disconnect() + { + tcpClient.Close(); + } + + public void sendMessage(string address, params object[] args) { tcpClient.Send(new OscMessage(address, args)); - Console.WriteLine($"QClient send message {address}"); + // Console.WriteLine($"QClient send message {address}"); } - protected virtual void OnWorkspacesUpdated(QResponse response) + private void ProcessMessage(object source, MessageEventArgs args) + { + + QMessage message = new QMessage(args.Message); + + if (message.IsReply) + { + JToken data = message.response; + //special case, want to update cue properties + if (message.IsReplyFromCue) + { + //todo check data type? + } + else if (message.IsReplyFromCueLists) + { + OnCueListsUpdated(message.response); + } + else if (message.IsWorkspacesInfo) + { + OnWorkspacesUpdated(message); + } + else if (message.IsConnect) + { + if (message.response.ToString() == "ok") + OnWorkspaceConnected(); + else + OnWorkspaceConnectionError(message.response.ToString()); + } + else + { + Console.WriteLine($"[client] unhandled reply message: {message.address}"); + } + } + else if(message.IsUpdate) { + if (message.IsCueUpdate) + { + OnCueNeedsUpdated(message.cueID); + } + else if (message.IsPlaybackPositionUpdate) + { + OnCueListChangedPlaybackPosition(message.cueID); + } + else if (message.IsWorkspaceUpdate) + { + OnWorkspaceUpdated(); + } + else if (message.IsWorkspaceSettingsUpdate) + { + string settingsType = message.AddressParts.Last(); + if (settingsType == null) + return; + OnWorkspaceSettingsUpdated(settingsType); + } + else if (message.IsLightDashboardUpdate) + { + //need to do checks for 4.2 or newer + OnWorkspaceLightDashboardUpdated(); + } + else if ( message.IsPreferencesUpdate) + { + //need to do checks for 4.2 or newer + + string key = message.AddressParts.Last(); + if (key == null) + return; + OnQLabPreferencesUpdated(key); + } + else if (message.IsDisconnect) + { + Console.WriteLine($"[client] disconnect message received: {message.address}"); + OnWorkspaceDisconnected(); + } + else + { + Console.WriteLine($"[client] unhandled update message: {message.address}"); + } + } + else + { + Console.WriteLine($"[client] unhandled message: {message.address}"); + } + } + + protected virtual void OnCueNeedsUpdated(string cueID) + { + if (CueNeedsUpdated != null) + CueNeedsUpdated(this, new QCueNeedsUpdatedArgs { cueID = cueID }); + } + + protected virtual void OnCueListsUpdated(JToken response) + { + if (CueListsUpdated != null) + CueListsUpdated(this, new QCueListsUpdatedArgs { data = response }); + } + + protected virtual void OnCueListChangedPlaybackPosition(string cueID) + { + if (CueListChangedPlaybackPosition != null) + CueListChangedPlaybackPosition(this, new QCueListChangedPlaybackPositionArgs { cueID = cueID }); + } + + protected virtual void OnWorkspaceUpdated() + { + if (WorkspaceUpdated != null) + WorkspaceUpdated(this, new QWorkspaceUpdatedArgs()); + } + + protected virtual void OnWorkspaceSettingsUpdated(string settingsType) + { + if (WorkspaceSettingsUpdated != null) + WorkspaceSettingsUpdated(this, new QWorkspaceSettingsUpdatedArgs { settingsType = settingsType}); + } + + protected virtual void OnWorkspaceLightDashboardUpdated() + { + if (WorkspaceLightDashboardUpdated != null) + WorkspaceLightDashboardUpdated(this, new QWorkspaceLightDashboardUpdatedArgs()); + } + + protected virtual void OnQLabPreferencesUpdated(string key) + { + if (QLabPreferencesUpdated != null) + QLabPreferencesUpdated(this, new QQLabPreferencesUpdatedArgs { key = key }); + } + + protected virtual void OnWorkspaceDisconnected() + { + if (WorkspaceDisconnected != null) + WorkspaceDisconnected(this, new QWorkspaceDisconnectedArgs()); + } + + protected virtual void OnWorkspaceConnected() + { + if (WorkspaceConnected != null) + WorkspaceConnected(this, new QWorkspaceConnectedArgs()); + } + + protected virtual void OnWorkspaceConnectionError(string status) + { + if (WorkspaceConnectionError != null) + WorkspaceConnectionError(this, new QWorkspaceConnectionErrorArgs { status = status}); + } + + protected virtual void OnWorkspacesUpdated(QMessage message) { if(WorkspacesUpdated != null) { List workspaces = new List(); - foreach (var item in response.data) + foreach (var item in message.response) { QWorkspaceInfo workspacefound = JsonConvert.DeserializeObject(item.ToString()); workspaces.Add(workspacefound); - Console.WriteLine(workspacefound.displayName); } - WorkspacesUpdated(this, new WorkspacesUpdatedArgs { Workspaces = workspaces }); + WorkspacesUpdated(this, new QWorkspacesUpdatedArgs { Workspaces = workspaces }); } } + + } } diff --git a/QSharp/QCue.cs b/QSharp/QCue.cs index 5007b05..d50e933 100644 --- a/QSharp/QCue.cs +++ b/QSharp/QCue.cs @@ -1,19 +1,682 @@ -using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; namespace QSharp { public class QCue { - Dictionary cueData = new Dictionary(); + + public QWorkspace workspace; + public Dictionary cueData; + public List childCues; + public Dictionary childCuesUIDMap; + + public int sortIndex; + + private bool needsSortChildCues;//? + private bool needsNotifyCueUpdated;//? + + public bool ignoreUpdates; + + public QCue() { + init(); } - public object propertyForKey(string key) + public QCue(QWorkspace workspace) { - if (cueData.ContainsKey(key)) - return cueData[key]; + init(); + this.workspace = workspace; + } + + public QCue(JToken dict, QWorkspace workspace) + { + init(); + this.workspace = workspace; + + JToken children = dict[QOSCKey.Cues]; + if(children != null && children.Type == JTokenType.Array) + { + //Console.WriteLine($"[cue] new cue being created with {children.Count()} childCues"); + foreach (var aChildDict in children) + { + string uid = (string)aChildDict[QOSCKey.UID]; + //Console.WriteLine($"[cue] childDict with uid: {uid} being processed"); + if (uid == null) + continue; + + QCue child = new QCue(aChildDict, workspace); + + childCues.Add(child); + childCuesUIDMap.Add(uid, child); + } + } + + updatePropertiesWithDictionary(dict); + } + + public void init() + { + cueData = new Dictionary { + { QOSCKey.Flagged, false }, + { QOSCKey.Armed, false }, + { QOSCKey.PreWait, false }, + { QOSCKey.PercentPreWaitElapsed, false }, + { QOSCKey.PercentActionElapsed, false }, + { QOSCKey.PostWait, false }, + { QOSCKey.PercentPostWaitElapsed, false }, + { QOSCKey.IsPanicking, false }, + { QOSCKey.IsTailingOut, false }, + { QOSCKey.IsRunning, false }, + { QOSCKey.IsLoaded, false }, + { QOSCKey.IsPaused, false }, + { QOSCKey.IsBroken, false }, + { QOSCKey.IsOverridden, false }, + { QOSCKey.ContinueMode, false } + }; + //TODO init cueData with some keys + + childCues = new List(); + childCuesUIDMap = new Dictionary(); + } + + public string description + { + get + { + return $"(Cue: {this}) name: {name} [id:{uid} number:{number} type: {type}"; + } + } + + //isEqual? + //hash? + //compare? + public bool isEqualToCue(QCue cue) { throw new NotImplementedException(); } + public void setWorkspace(QWorkspace workspace) + { + if(this.workspace != workspace) + { + this.workspace = workspace; + } + } + public void addChildCue(QCue cue) + { + string uid = propertyForKey(QOSCKey.UID).ToString(); + addChildCue(cue, uid); + } + public void addChildCue(QCue cue, string uid) + { + if (uid.Length == 0) + return; + childCues.Add(cue); + childCuesUIDMap.Add(uid, cue); + //some sorting of the childCues needs to be done? + //reset sorting index + } + public void removeChildCue(QCue cue) { throw new NotImplementedException(); } + public void removeAllChildCues() { throw new NotImplementedException(); } + public void removeChildCuesWithIDs(List uids) { throw new NotImplementedException(); } + + //copied: yes + //implemented: no + #region Class Methods + public string iconForType(string type) { throw new NotImplementedException(); } + public bool cueTypeIsAudio(string type) { throw new NotImplementedException(); } + public bool cueTypeIsVideo(string type) { throw new NotImplementedException(); } + public bool cueTypeIsGroup(string type) { throw new NotImplementedException(); } + public bool cueTypeIsCueList(string type) { throw new NotImplementedException(); } + public List fadeModeTitles() { throw new NotImplementedException(); } + #endregion + + //KVC Compliance... do I need this? + + //copied: yes + //implemented: not all + #region Convenience Accessors + public List cues + { + get + { + List cues = (List)propertyForKey(QOSCKey.Cues); + if (cues == null) + return new List(); + return cues; + } + } + public string parentID + { + get + { + return propertyForKey(QOSCKey.Parent).ToString(); + } + } + public string playbackPositionID() { throw new NotImplementedException(); } + public string name + { + get + { + return propertyForKey(QOSCKey.Name).ToString(); + } + } + public string number + { + get + { + return propertyForKey(QOSCKey.Number).ToString(); + } + } + public string uid + { + get + { + return propertyForKey(QOSCKey.UID).ToString(); + } + } + public string listName + { + get + { + return propertyForKey(QOSCKey.ListName).ToString(); + } + } + public string type + { + get + { + return propertyForKey(QOSCKey.Type).ToString(); + } + } + public string notes + { + get + { + return propertyForKey(QOSCKey.Notes).ToString(); + } + } + + public bool IsFlagged + { + get + { + return (bool)propertyForKey(QOSCKey.Flagged); + } + } + public bool IsOverridden + { + get + { + return (bool)propertyForKey("OSCNameKey"); + } + } + public bool IsBroken + { + get + { + return (bool)propertyForKey("OSCNameKey"); + } + } + public bool IsRunning + { + get + { + return (bool)propertyForKey("OSCNameKey"); + } + } + public bool IsTailingOut + { + get + { + return (bool)propertyForKey("OSCNameKey"); + } + } + public bool IsPanicking + { + get + { + return (bool)propertyForKey("OSCNameKey"); + } + } + public bool IsGroup + { + get + { + if (type.Equals(QCueType.Group)) + return true; + if (type.Equals(QCueType.CueList)) + return true; + if (type.Equals(QCueType.Cart)) + return true; + return false; + } + } + public bool IsCueList + { + get + { + if ( type.Equals(QCueType.CueList)) + return true; + return false; + } + } + public string displayName + { + get + { + string number = this.number; + if (number.Length > 0) + return $"{number} \u00b7 {nonEmptyName}"; + else + return nonEmptyName; + } + } + public string nonEmptyName + { + get + { + //TODO + string nonEmptyName; + if (name != null && !name.Equals("")) + nonEmptyName = name; + else if (listName != null && !listName.Equals("")) + nonEmptyName = listName; + else + nonEmptyName = $"Untitled {type} Cue"; + + return nonEmptyName; + } + } + public string workspaceName{ get { return workspace.name; } } + public double currentDuration + { + + get + { + //try v4 current duration key first + if (propertyForKey(QOSCKey.CurrentDuration) != null) + return (double)propertyForKey(QOSCKey.CurrentDuration); + else + return (double)propertyForKey(QOSCKey.Duration); + } + } + public string audioFadeModeName + { + get + { + //TODO + if (type.Equals(QCueType.Fade)) + return ""; + else + return null; + } + } + public string geoFadeModeName + { + get + { + //TODO + if (type.Equals(QCueType.Fade)) + return ""; + else + return null; + } + } + public string surfaceName{ get { return propertyForKey("surfaceName").ToString(); } } + public string patchName + { + get + { + //TODO + return (string)propertyForKey("patchDecription"); + } + } + + //TODO: THIS IS WHERE THINGS START TO GET TRICKY! + + //TODO add object color method + public string color { get { return propertyForKey(QOSCKey.ColorName).ToString(); } } + + //TODO add quaternion property and setter + + public Size surfaceSize() { throw new NotImplementedException(); } + public Size cueSize() { throw new NotImplementedException(); } + public List availableSurfaceName() { throw new NotImplementedException(); } + public List propertyKeys + { + get + { + //TODO add null check? + return cueData.Keys.ToList(); + } + } + + public bool hasChildren + { + get + { + return cues.Count > 0; + } + } + + public QCue firstCue + { + get + { + return cues.First(); + } + } + + public QCue lastCue + { + get + { + return cues.Last(); + } + } + + + public void setIgnoreUpdates(bool ignoreUpdates) + { + if(this.ignoreUpdates != ignoreUpdates) + { + this.ignoreUpdates = ignoreUpdates; + + if (!this.ignoreUpdates) + { + //TODO: update local with QLab + } + } + } + #endregion + + //Copied: Yes + //Implemented: Not all + #region Mutators + public void setCues(List cues) + { + + } + public void setName() { } + public void setNumber() { } + public void setUid() { } + public void setListName() { } + public void setType() { } + public void setFlagged() { } + #endregion + + //Copied: Yes + //Implemented: Not all + #region Update Methods + public bool updatePropertiesWithDictionary(JToken dict) + { + return updatePropertiesWithDictionary(dict, true); + } + public bool updatePropertiesWithDictionary(JToken dict, bool notify) + { + bool cueUpdated = false; + + //TODO + JObject dictObj = (JObject)dict; + foreach (var obj in dictObj) + { + //Console.WriteLine($"[cue] found property {obj.Key} "); + JToken value = obj.Value; + if (obj.Key.Equals(QOSCKey.Cues)) + { + if (value.Type != JTokenType.Array) + continue; + updateChildCuesWithPropertiesArray(value, false); + } + else + { + bool didSetProptery = setProperty(value, obj.Key, false); + if (didSetProptery) + cueUpdated = true; + } + } + + if (!cueUpdated) + return false; + if (notify) + Console.WriteLine("should notify cue updated."); + //something about notifying cueUpdated ? OnCueUpdated event handler maybe? + + return true; + } + + //enqueue updated notification? + public void updateChildCuesWithPropertiesArray(JToken value, bool removeUnused) + { + if (!workspace.connected) + return; + + List previousUids = null; + if (removeUnused) + previousUids = allChildCueUids(); + + int index = 0; + + foreach (var dict in value) + { + string uid = (string)dict[QOSCKey.UID]; + if (uid == null) + continue; + + if (removeUnused) + previousUids.Remove(uid); + + QCue child = cueWithID(uid, false); + + if (child != null) + { + bool didUpdateProperties = updatePropertiesWithDictionary(dict); + if (didUpdateProperties) + needsNotifyCueUpdated = true; + + if (child.sortIndex != index) + { + child.sortIndex = index; + + needsSortChildCues = true; + needsNotifyCueUpdated = true; + } + } + else + { + child = new QCue(dict, workspace); + child.sortIndex = index; + addChildCue(child, uid); + needsNotifyCueUpdated = true; + + } + index++; + } + + + //TODO: TEST?! + } + #endregion + + + + //Methods Copied: Yes + //Methods Implemented: Not all + #region Children Cues + public List allChildCueUids() + { + //TODO + return new List(); + } + public QCue cueAtIndex(int index) + { + //TODO + return new QCue(); + } + public QCue cueWithID(string uid) + { + return cueWithID(uid, true); + } + public QCue cueWithID(string uid, bool includeChildren) + { + //Console.WriteLine($"[cue] searching for cue with id: {uid} - includeChildren: {includeChildren}"); + if(childCuesUIDMap.ContainsKey(uid)) + return childCuesUIDMap[uid]; + + if (!includeChildren) + return null; + + foreach(var cue in cues) + { + if (!cue.IsGroup) + continue; + QCue childCue = cue.cueWithID(uid, includeChildren); + if (childCue != null) + return childCue; + } + return null; } + public QCue cueWithNumber(string number) + { + //TODO + return new QCue(); + } + public object propertyForKey(string key) + { + //TODO: implerment special key checks color, surfaceName, patchDescription, + if (key.Equals(QOSCKey.Cues)) + { + return childCues; + } + if (cueData.ContainsKey(key)) + { + return cueData[key]; + } + return null; + } + + public bool setProperty(object value, string key) + { + return setProperty( value, key, workspace.defaultSendUpdatesOSC); + } + + public bool setProperty(object value, string key, bool osc) + { + //TODO: + object existingValue = null; + if(cueData.ContainsKey(key)) + existingValue = cueData[key]; + + if(existingValue != null) + { + if (existingValue == value || existingValue.Equals(value)) + { + //Console.WriteLine($"[cue] skipping set property: {key} with type: {value.GetType()} and value: {value}"); + return false; + } + } + + //Console.WriteLine($"[cue] attempting set property: {key} with type: {value.GetType()} and value: {value}"); + + + + if (workspace.connectedToQLab3 && key.Equals(QOSCKey.Type) && existingValue.Equals(QCueType.CueList) && value.Equals(QCueType.Group)) + { + return false; + } + + + if (key.Equals(QOSCKey.Cues)) + { + if (value.GetType() != typeof(List)) + return false; + + + Dictionary newChildCuesUIDMap = new Dictionary(); + int index = 0; + string uid = null; + + foreach(var aCue in (List)value) + { + uid = aCue.uid; + if (uid == null) + continue; + aCue.sortIndex = index; + newChildCuesUIDMap.Add(uid, aCue); + index++; + } + childCues = (List)value; + childCuesUIDMap = newChildCuesUIDMap; + + } + else if(key.Equals(QOSCKey.PlaybackPositionId)) + { + if (!IsCueList) + return false; + if (value != null && value.GetType() != typeof(string)) + return false; + + if (value.Equals("none")) + value = null; + if (value != null) + cueData[key] = value; + else + cueData.Remove(key); + + } + else + { + if (value != null) + cueData[key] = value; + else + cueData.Remove(key); + } + + //Console.WriteLine($"[cue] set property: {key} with type: {value.GetType()} and value: {value}"); + //check for type key and update icon? do I want to do this here? + return true; + } + + #endregion + + //Methods Copied: Yes + //Methods Implemented: Yes + #region Actions + public void start() { workspace.startCue(this); } + public void stop() { workspace.stopCue(this); } + public void pause() { workspace.pauseCue(this); } + public void reset() { workspace.resetCue(this); } + public void load() { workspace.loadCue(this); } + public void resume() { workspace.resumeCue(this); } + public void hardStop() { workspace.hardStopCue(this); } + public void hardPause() { workspace.hardPauseCue(this); } + public void togglePause() { workspace.togglePauseCue(this); } + public void preview() { workspace.previewCue(this); } + public void panic() { workspace.panicCue(this); } + #endregion + + #region Printing + public void Print() + { + if (IsCueList) + { + if(cues != null && cues.Count() > 0) + { + Console.WriteLine(displayName); + foreach(var cue in cues) + { + cue.Print(); + } + } + } + else + { + Console.WriteLine($"{displayName} : {color}"); + } + } + #endregion } } diff --git a/QSharp/QDefines.cs b/QSharp/QDefines.cs new file mode 100644 index 0000000..db12720 --- /dev/null +++ b/QSharp/QDefines.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace QSharp +{ + static class QBonjour + { + public const string TCPService = "_qlab._tcp.local."; + public const string UDPService = "_qlab._udp.local."; + } + static class QCueType + { + public const string Cue = "Cue"; + public const string CueList = "Cue List"; + public const string Cart = "Cart"; + public const string Group = "Group"; + public const string Audio = ""; + public const string Mic = "Mic"; + public const string Video = "Video"; + public const string Camera = "Camera"; + public const string Text = "Text"; + public const string Light = "Light"; + public const string Fade = "Fade"; + public const string Network = "Network"; + public const string MIDI = "MIDI"; + public const string MIDIFile = "MIDI File"; + public const string Timecode = "Timecode"; + public const string Start = "Start"; + public const string Stop = "Stop"; + public const string Pause = "Paus"; + public const string Load = "Load"; + public const string Reset = "Reset"; + public const string Devamp = "Devamp"; + public const string Goto = "GoTo"; + public const string Target = "Target"; + public const string Arm = "Arm"; + public const string Disarm = "Disarm"; + public const string Wait = "Wait"; + public const string Memo = "Memo"; + public const string Script = "Script"; + public const string Stagetracker = "Stagetracker"; + + //v3 + public const string OSC = "OSC"; + public const string Titles = "Titles"; + + } + + static class QOSCKey + { + public const string UID = "uniqueID"; + public const string Type = "type"; + public const string Parent = "parent"; + public const string Name = "name"; + public const string Number = "number"; + public const string Notes = "notes"; + public const string FileTarget = "fileTarget"; + public const string CueTargetNumber = "cueTargetNumber"; + public const string CurrentCueTarget = "currentCueTarget"; + public const string ColorName = "colorName"; + public const string Flagged = "flagged"; + public const string Armed = "armed"; + public const string ContinueMode = "continueMode"; + public const string PreWait = "preWait"; + public const string PostWait = "postWait"; + public const string CurrentDuration = "currentDuration"; + public const string PercentPreWaitElapsed = "percentPreWaitElapsed"; + public const string PercentPostWaitElapsed = "percentPostWaitElapsed"; + public const string PercentActionElapsed = "percentActionElapsed"; + public const string PreWaitElapsed = "preWaitElapsed"; + public const string PostWaitElapsed = "postWaitElapsed"; + public const string ActionElapsed = "actionElapsed"; + public const string GroupMode = "mode"; + public const string CartPosition = "cartPosition"; + public const string CartRows = "cartRows"; + public const string CartColumns = "cartColumns"; + public const string HasFileTargets = "hasFileTargets"; + public const string HasCueTargets = "hasCueTargets"; + public const string AllowsEditingDuration = "allowsEditingDuration"; + public const string IsPanicking = "isPanicking"; + public const string IsTailingOut = "isTailingOut"; + public const string IsRunning = "isRunning"; + public const string IsLoaded = "isLoaded"; + public const string IsPaused = "isPaused"; + public const string IsBroken = "isBroken"; + public const string IsOverridden = "isOverridden"; + public const string TranslationX = "translationX"; + public const string TranslationY = "translationY"; + public const string ScaleX = "scaleX"; + public const string ScaleY = "scaleY"; + public const string OriginX = "originX"; + public const string OriginY = "originY"; + public const string Quaternion = "quaternion"; + public const string SurfaceSize = "surfaceSize"; + public const string CueSize = "cueSize"; + public const string PreserveAspectRatio = "preserveAspectRatio"; + public const string Layer = "layer"; + public const string Patch = "patch"; + public const string PatchList = "patchList"; + public const string SurfaceList = "surfaceList"; + public const string Cues = "cues"; + public const string ListName = "listName"; + public const string SurfaceID = "surfaceID"; + public const string FullSurface = "fullSurface"; + public const string Opacity = "opacity"; + public const string RotationZ = "rotationZ"; + public const string RotationY = "rotationY"; + public const string RotationX = "rotationX"; + public const string PlaybackPositionId = "playbackPositionId"; + public const string StartNextCueWhenSliceEnds = "startNextCueWhenSliceEnds"; + public const string StopTargetWhenSliceEnds = "stopTargetWhenSliceEnds"; + public const string SliderLevel = "sliderLevel"; + + // v3 + public const string Duration = "duration"; + public const string FullScreen = "fullScreen"; + } + + static class QIdentifiers + { + // Identifiers for "fake" cues + public const string RootCue = "__root__"; + public const string ActiveCues = "__active__"; + } + + + //these should really be enums + static class QFadeMode + { + public const int Absolute = 0; + public const int Relative = 1; + } + + static class QContinueMode + { + public const int NoContinue = 0; + public const int AutoContinue = 1; + public const int AutoFollow = 2; + } +} diff --git a/QSharp/QMessage.cs b/QSharp/QMessage.cs new file mode 100644 index 0000000..0fe2954 --- /dev/null +++ b/QSharp/QMessage.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using Newtonsoft.Json.Linq; +using SharpOSC; + +namespace QSharp +{ + public class QMessage + { + + private OscMessage OSCMessage; + + public QMessage(OscMessage oscMessage) + { + OSCMessage = oscMessage; + } + + public string description + { + get + { + //string join probably won't work might need to cast argument objects to strings first + return $"address: {address}, arguments: {string.Join(" - ", arguments)}"; + } + } + + #region Message Type Checks + public bool IsReply { get { return OSCMessage.Address.StartsWith("/reply"); } } + + // /reply/cue_id/1/action + public bool IsReplyFromCue { get{ return OSCMessage.Address.StartsWith("/reply/cue_id"); } } + public bool IsReplyFromCueLists { get { return IsReply && OSCMessage.Address.EndsWith("cueLists"); } } + + public bool IsUpdate { get{ return OSCMessage.Address.StartsWith("/update"); } } + + + public bool IsWorkspaceUpdate { + get { + // /update/workspace/{workspace_id} + string[] parts = AddressParts; + return (parts.Length == 3 && parts[1].Equals("workspace")); + } + } + + public bool IsWorkspacesInfo { + get { + // /workspaces + string[] parts = AddressParts; + return (parts.Length == 2 && parts[1] == "workspaces"); + } + } + + + public bool IsWorkspaceSettingsUpdate { + get { + // /update/workspace/{workspace_id}/settings/{settings_controller} + string[] parts = AddressParts; + return (parts.Length == 5 && parts[1].Equals("workspace") && parts[3].Equals("settings")); + } + } + + + public bool IsLightDashboardUpdate { + get { + // /update/workspace/{workspace_id}/dashboard + string[] parts = AddressParts; + return (parts.Length == 4 && parts[1].Equals("workspace") && parts[3].Equals("dashboard")); + } + } + + + public bool IsCueUpdate { + get { + // /update/workspace/{workspace_id}/cue_id/{cue_id} + string[] parts = AddressParts; + return (parts.Length == 5 && parts[1].Equals("workspace") && parts[3].Equals("cue_id")); + } + } + + + public bool IsPlaybackPositionUpdate { + get { + // /update/workspace/{workspace_id}/cueList/{cue_list_id}/playbackPosition {cue_id} + string[] parts = AddressParts; + return (parts.Length == 6 && address.EndsWith("/playbakPosition")); + } + } + + + public bool IsPreferencesUpdate { + get { + string[] parts = AddressParts; + return (parts.Length == 4 && parts[3].Equals("preferences")); + } + } + + public bool IsDisconnect { + get { + string[] parts = AddressParts; + return (parts.Length == 4 && parts[3].Equals("disconnect")); + } + } + + public bool IsConnect + { + get + { + string[] parts = AddressParts; + return (parts.Length == 4 && parts[3].Equals("connect")); + } + } + #endregion + + + public string address { get { return OSCMessage.Address; } } + + public string replyAddress { get{ return IsReply ? address.Substring("/reply".Length) : address; } } + + public string addressWithoutWorkspace + { + get + { + //TODO + return ""; + } + } + + public string[] AddressParts { get { return address.Split("/",StringSplitOptions.RemoveEmptyEntries); } } + + public JToken response + { + get + { + JObject responseObj = JObject.Parse((string)arguments[0]); + + //TODO + return responseObj["data"]; + } + } + + public List arguments { get { return OSCMessage.Arguments; } } + + public string cueID + { + get + { + if (IsCueUpdate) + { + return AddressParts[4]; + }else if (IsPlaybackPositionUpdate) + { + //TODO: check string cast + return arguments.Count > 0 ? (string)arguments[0] : null; + }else if (IsReplyFromCue) + { + return AddressParts[2]; + } + else + { + return null; + } + } + } + + + } +} diff --git a/QSharp/QResponse.cs b/QSharp/QResponse.cs deleted file mode 100644 index 0b3fdeb..0000000 --- a/QSharp/QResponse.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Text.RegularExpressions; - -namespace QSharp -{ - public class QResponse - { - private const string WORKSPACES_REGEX = @"/workspaces"; - public string status { get; set; } - public string address { get; set; } - public object[] data { get; set; } - - public string getReplyType() - { - Console.WriteLine($"QRESPONSE - Address to match: '{address}'"); - Match m = Regex.Match(address, WORKSPACES_REGEX); - if (m.Success) - return "WORKSPACES"; - return ""; - } - } -} diff --git a/QSharp/QServer.cs b/QSharp/QServer.cs index 7c68bab..6e37e8c 100644 --- a/QSharp/QServer.cs +++ b/QSharp/QServer.cs @@ -1,53 +1,67 @@ //General information about a QLab Workspace +using System.Collections.Concurrent; using System.Collections.Generic; +using Zeroconf; namespace QSharp { public class QServer { - private QClient qClient; + private QClient client; public string host; public int port; public string name; + public IZeroconfHost zeroconfHost; List workspaces = new List(); - public QServer(string host, int port, string name) + public QServer(string host, int port) { this.host = host; this.port = port; - this.name = name; - qClient = new QClient(host, port); - qClient.WorkspacesUpdated += OnWorkspacesUpdated; + client = new QClient(host, port); + client.WorkspacesUpdated += OnWorkspacesUpdated; } - private void OnWorkspacesUpdated(object source, QClient.WorkspacesUpdatedArgs args) - { - foreach(var workspace in args.Workspaces) - { - QWorkspace existingWorkspace = workspaceWithID(workspace.uniqueID); - if(existingWorkspace == null) - { - QWorkspace workspaceToAdd = new QWorkspace(workspace, this); - workspaces.Add(workspaceToAdd); - } - } - } + public string description { get { return $"{name} - {host} - {port}"; } } + public void refreshWorkspaces() { - qClient.sendMessage("/workspaces"); + if (!client.connect()) + { + System.Console.WriteLine($"Error: QServer unable to connect to QLab Server: {host}:{port}"); + return; + } + client.sendMessage("/workspaces"); } public QWorkspace workspaceWithID(string uniqueID) { foreach(var workspace in workspaces) { - if (workspace.getUniqueID().Equals(uniqueID)) + if (workspace.uniqueID.Equals(uniqueID)) return workspace; } return null; } + + #region EventHandlers + private void OnWorkspacesUpdated(object source, QWorkspacesUpdatedArgs args) + { + foreach (var workspace in args.Workspaces) + { + QWorkspace existingWorkspace = workspaceWithID(workspace.uniqueID); + if (existingWorkspace == null) + { + QWorkspace workspaceToAdd = new QWorkspace(workspace, this); + workspaces.Add(workspaceToAdd); + workspaceToAdd.connectWithPasscode("1234"); + } + } + } + #endregion + } } diff --git a/QSharp/QWorkspace.cs b/QSharp/QWorkspace.cs index ef19599..5c10733 100644 --- a/QSharp/QWorkspace.cs +++ b/QSharp/QWorkspace.cs @@ -1,4 +1,9 @@ //TODO: connect methods, cue property fetch methods, everything else +using System; +using System.Collections.Generic; +using System.Linq; +using System.Timers; + namespace QSharp { public class QWorkspace @@ -7,11 +12,20 @@ namespace QSharp private QServer server; private QClient client; - private string name; - private string uniqueID; - private bool connected; + public string name; + public string uniqueID; + public bool connected; private bool hasPasscode; - private string oscPrefix; + private string passcode; + + private QCue root; + + private Timer heartbeatTimer; + private int heartbeatAttempts; + + public bool defaultSendUpdatesOSC; + + public string version; private void Init() { @@ -20,26 +34,42 @@ namespace QSharp connected = false; hasPasscode = false; + defaultSendUpdatesOSC = false; + + root = new QCue(this); + root.setProperty(QIdentifiers.RootCue, QOSCKey.UID, false); + root.setProperty("Cue Lists", QOSCKey.Name, false); + root.setProperty(QCueType.CueList, QOSCKey.Type, false); //TODO: add version + version = "3.0.0"; + } public QWorkspace(QWorkspaceInfo workspaceInfo, QServer server) { - if(workspaceInfo.uniqueID.Length > 0) - { - uniqueID = workspaceInfo.uniqueID; - oscPrefix = workspacePrefix(); - } + Init(); + + if(workspaceInfo.uniqueID.Length > 0) + uniqueID = workspaceInfo.uniqueID; + + if (workspaceInfo.version.Length > 0) + version = workspaceInfo.version; - //TODO add version if it has one if no version then it is QLab 3? updateWithWorkspaceInfo(workspaceInfo); client = new QClient(server.host, server.port); - this.server = server; + + client.WorkspaceConnected += OnWorkspaceConnection; + client.WorkspaceConnectionError += OnWorkspaceConnectionError; + client.WorkspaceDisconnected += OnWorkspaceDisconnected; + client.CueListsUpdated += OnCueListsUpdated; + this.server = server; + Console.WriteLine($"[workspace] <{name}> initizalied for server: {server.name} "); } + //updateWithDictionary public bool updateWithWorkspaceInfo(QWorkspaceInfo workspaceInfo) { bool didUpdate = false; if(workspaceInfo.displayName.Length > 0 && !workspaceInfo.displayName.Equals(this.name)) @@ -55,61 +85,315 @@ namespace QSharp return didUpdate; } - + public string description { get { return $"{name} : {uniqueID}"; } } - + public string nameWithoutPathExtension { get { return name; } } //TODO - public string getUniqueID() + public string serverName { get { return server.name; } } + + public string fullName { get { return $"{name} ({server.name})"; } } + + public QCue firstCue { get { return firstCueList.firstCue; } } + public QCue firstCueList { get { return root.firstCue; } } + public string fullNameWithCueList(QCue cueList) { return ""; } + //TODO other convenience methods + + public bool connectedToQLab3 { get { return false; } } //TODO implement the version system + + #region Connection/reconnection + + public void connectWithPasscode(string passcode) { - return uniqueID; + //TODO + + if (!client.connect()) + { + System.Console.WriteLine($"[workspace] *** Error: couldn't connect to server"); + return; + } + + System.Console.WriteLine("[workspace] connecting..."); + client.sendMessage($"{workspacePrefix}/connect",passcode); + } - #region OSC address helpers - private string addressForCue(QCue cue, string action) + private void finishConnection() { - return $"{oscPrefix}/cue_id/{cue.propertyForKey("uniqueId")}/{action}"; + //TODO + System.Console.WriteLine($"[workspace] connected to <{name}> running on QLab version <{version}>"); + connected = true; + startReceivingUpdates(); + //fetchQLabVersion(); + fetchCueLists(); + } - private string addressForWildcardNumber(string number, string action) { - return $"{oscPrefix}/cue/{number}/{action}"; + public void reconnect() + { + if (connected) + return; + connectWithPasscode(passcode); + + //todo } - private string workspacePrefix() + public void disconnect() { - return $"/workspace/{uniqueID}"; + //TODO + Console.WriteLine($"[workspace] disconnect: {name}"); + stopHeartbeat(); + stopReceivingUpdates(); + disconnectFromWorkspace(); + + connected = false; + + client.disconnect(); + //root.removeAllChildCues(); + + } + + public void temporarilyDisconnect() + { + //TODO + } + + #endregion + + #region Cues + public QCue cueWithId(string uid) + { + return root.cueWithID(uid); + } + + public QCue cueWithNumber(string number) + { + return root.cueWithNumber(number); } #endregion #region Workspace Methods - public void disconnectFromWorkspace() { client.sendMessage($"{oscPrefix}/disconnect"); } - public void startReceivingUpdates() { client.sendMessage($"{oscPrefix}/updates", 1); } - public void stopReceivingUpdates() { client.sendMessage($"{oscPrefix}/updates", 0); } - public void enableAlwaysReply() { client.sendMessage($"{oscPrefix}/alwaysReply", 1); } - public void disableAlwaysReply() { client.sendMessage($"{oscPrefix}/alwaysReply", 0); } + public void disconnectFromWorkspace() { client.sendMessage($"{workspacePrefix}/disconnect"); } + public void startReceivingUpdates() { client.sendMessage($"{workspacePrefix}/updates", 1); } + public void stopReceivingUpdates() { client.sendMessage($"{workspacePrefix}/updates", 0); } + public void enableAlwaysReply() { client.sendMessage($"{workspacePrefix}/alwaysReply", 1); } + public void disableAlwaysReply() { client.sendMessage($"{workspacePrefix}/alwaysReply", 0); } + public void fetchQLabVersion() { client.sendMessage($"{workspacePrefix}/version"); } //TODO: EventHandler for this + public void fetchCueLists() { client.sendMessage($"{workspacePrefix}/cueLists"); } //TODO: EventHandler for CueListUpdated + public void fetchPlaybackPositionForCue(QCue cue) { client.sendMessage(addressForCue(cue, QOSCKey.PlaybackPositionId)); } //EventHandler for this? can I use the CueListPlaybackPosition one? + public void go() { client.sendMessage($"{workspacePrefix}/go"); } + public void save() { client.sendMessage($"{workspacePrefix}/save"); } + public void undo() { client.sendMessage($"{workspacePrefix}/undo"); } + public void redo() { client.sendMessage($"{workspacePrefix}/redo"); } + public void resetAll() { client.sendMessage($"{workspacePrefix}/reset"); } + public void pauseAll() { client.sendMessage($"{workspacePrefix}/pause"); } + public void resumeAll() { client.sendMessage($"{workspacePrefix}/resume"); } + public void stopAll() { client.sendMessage($"{workspacePrefix}/stop"); } + public void panicAll() { client.sendMessage($"{workspacePrefix}/panic"); } + #endregion - public void fetchQLabVersion() { + #region Heartbeat + //TODO + public void startHeartbeat() + { + clearHeartbeatTimeout(); + sendHeartbeat(); + } + + public void stopHeartbeat() + { + clearHeartbeatTimeout(); + heartbeatAttempts = -1; + } + + public void clearHeartbeatTimeout() + { + heartbeatTimer.Stop(); + heartbeatTimer = null; + heartbeatAttempts = 0; //TODO } - public void fetchCueLists() + public void sendHeartbeat() + { + client.sendMessage("/thump"); + //TODO + } + + public void heartbeatTimeout(object sender, ElapsedEventArgs e) { //TODO } - public void fetchPlaybackPositionForCue() + + + #endregion + + #region Cue Actions + public void startCue(QCue cue) { client.sendMessage(addressForCue(cue, "start")); } + public void stopCue(QCue cue) { client.sendMessage(addressForCue(cue, "stop")); } + public void pauseCue(QCue cue) { client.sendMessage(addressForCue(cue, "pause")); } //TODO: immediately update local for snappier whatever + public void loadCue(QCue cue) { client.sendMessage(addressForCue(cue, "load")); } + public void resetCue(QCue cue) { client.sendMessage(addressForCue(cue, "reset")); } + public void deleteCue(QCue cue) { client.sendMessage(addressForCue(cue, "")); } + public void resumeCue(QCue cue) { client.sendMessage(addressForCue(cue, "resume")); } + public void hardStopCue(QCue cue) { client.sendMessage(addressForCue(cue, "hardStop")); } + public void hardPauseCue(QCue cue) { client.sendMessage(addressForCue(cue, "hardPause")); } //TODO: immediately update local for snappier whatever + public void togglePauseCue(QCue cue) { client.sendMessage(addressForCue(cue, "togglePause")); } + public void previewCue(QCue cue) { client.sendMessage(addressForCue(cue, "preview")); } + public void panicCue(QCue cue) { client.sendMessage(addressForCue(cue, "panic")); } //TODO: immediately update local for snappier whatever + #endregion + + #region Cue Getters/Setters + //TODO + #endregion + + #region Property Fetching + //TODO + #endregion + + #region OSC address helpers + public void sendMessage(string address, params object[] args) { - //TODO + //check for workspace prefix? + client.sendMessage(address, args); } - public void go() { client.sendMessage($"{oscPrefix}/go"); } - public void save() { client.sendMessage($"{oscPrefix}/save"); } - public void undo() { client.sendMessage($"{oscPrefix}/undo"); } - public void redo() { client.sendMessage($"{oscPrefix}/redo"); } - public void resetAll() { client.sendMessage($"{oscPrefix}/reset"); } - public void pauseAll() { client.sendMessage($"{oscPrefix}/pause"); } - public void resumeAll() { client.sendMessage($"{oscPrefix}/resume"); } - public void stopAll() { client.sendMessage($"{oscPrefix}/stop"); } - public void panicAll() { client.sendMessage($"{oscPrefix}/panic"); } + private string addressForCue(QCue cue, string action) + { + return $"{workspacePrefix}/cue_id/{cue.propertyForKey("uniqueId")}/{action}"; + } + + private string addressForWildcardNumber(string number, string action) { + return $"{workspacePrefix}/cue/{number}/{action}"; + } + + private string workspacePrefix + { + get { return $"/workspace/{uniqueID}"; } + + } + #endregion + + public string workspaceID + { + get + { + return uniqueID; + } + } + + #region Event Handling + private void OnWorkspaceConnectionError(object source, QWorkspaceConnectionErrorArgs args) + { + if (args.status.Equals("badpass")) + Console.WriteLine($"[workspace] *** Error: Password for workspace {name} was incorrect!"); + else + Console.WriteLine($"[workspace] *** Error: Unable to connect to workspace: {name} on server: {server.name}"); + + } + + private void OnWorkspaceConnection(object source, QWorkspaceConnectedArgs args) + { + finishConnection(); + Console.WriteLine($"[workspace] Connection finished this is the first cue in this workspace {root.firstCue.displayName}"); + } + + private void OnWorkspaceDisconnected(object source, QWorkspaceDisconnectedArgs args) + { + //this might not be called with TCP? + Console.WriteLine($"[workspace] *** Workspace has indicated it is disconnecting"); + } + + private void OnCueListsUpdated(object source, QCueListsUpdatedArgs args) + { + bool rootCueUpdated = false; + List currentCueLists = new List(args.data.Count()); + int index = 0; + + foreach (var aCueList in args.data) + { + string uid = aCueList[QOSCKey.UID].ToString(); + if (uid == null) + continue; + QCue cueList = root.cueWithID(uid, false); + + if(cueList != null) + { + if(cueList.sortIndex != index) + { + cueList.sortIndex = index; + rootCueUpdated = true; + } + + } + else + { + cueList = new QCue(aCueList, this); + cueList.sortIndex = index; + if (connectedToQLab3) + cueList.setProperty(QCueType.CueList, QOSCKey.Type); + rootCueUpdated = true; + } + currentCueLists.Add(cueList); + index++; + } + + QCue activeCuesList = root.cueWithID(QIdentifiers.ActiveCues, false); + if(activeCuesList != null) + { + if (activeCuesList.sortIndex != index) + { + activeCuesList.setProperty(QIdentifiers.ActiveCues, QOSCKey.UID, false); + activeCuesList.setProperty("Active Cues", QOSCKey.Name, false); + activeCuesList.setProperty(QCueType.CueList, QOSCKey.Type, false); + + rootCueUpdated = true; + } + } + else + { + activeCuesList = new QCue(this); + activeCuesList.setProperty(QIdentifiers.ActiveCues, QOSCKey.UID, false); + activeCuesList.setProperty("Active Cues", QOSCKey.Name, false); + activeCuesList.setProperty(QCueType.CueList, QOSCKey.Type, false); + rootCueUpdated = true; + } + currentCueLists.Add(activeCuesList); + + if (root.cues.Count() != currentCueLists.Count()) + rootCueUpdated = true; + + root.setProperty(currentCueLists, QOSCKey.Cues); + + if (rootCueUpdated) + { + //add Event handled? use CueUpdated one? + } + + Console.WriteLine($"[workspace] cueLists finished processing. root updated? {rootCueUpdated}"); + + Print(); + } + + + //OnWorkspaceUpdated + //OnWorkspaceSettingsUpdated + //LightDashboardUpdated + //PreferencesUpdated + //CueNeedsUpdated + //CueUpdated + //ClientShouldDisconnect + #endregion + + + #region Printing + public void Print() + { + foreach(var cueList in root.cues) + { + cueList.Print(); + } + } #endregion } } diff --git a/QSharp/SharpOSC/TCPClient.cs b/QSharp/SharpOSC/TCPClient.cs index e16df28..6c7c27d 100644 --- a/QSharp/SharpOSC/TCPClient.cs +++ b/QSharp/SharpOSC/TCPClient.cs @@ -46,12 +46,22 @@ namespace SharpOSC _address = address; } - public void Connect() + public bool Connect() { - client = new TcpClient(Address, Port); - Console.WriteLine($"TCPClient - connect called for <{Address}:{Port}>"); - Thread receivingThread = new Thread(ReceiveLoop); - receivingThread.Start(); + try + { + client = new TcpClient(Address, Port); + Thread receivingThread = new Thread(ReceiveLoop); + receivingThread.Start(); + //Console.WriteLine($"TCPClient - connected to <{Address}:{Port}>"); + return true; + } + catch (Exception e) + { + //Console.WriteLine(e.Message); + return false; + } + } public void Send(byte[] message) @@ -67,6 +77,14 @@ namespace SharpOSC Send(data); } + public bool IsConnected + { + get + { + return client.Connected; + } + } + public void ReceiveLoop() { while (client.Connected) @@ -109,7 +127,7 @@ namespace SharpOSC } } catch(Exception e) { - //Log.Debug("TCPSENDER - Receive Exception: " + e.ToString()); + //Console.WriteLine("TCPSENDER - Receive Exception: " + e.ToString()); } } diff --git a/QSharpDemo/Program.cs b/QSharpDemo/Program.cs index 797012d..7592f8e 100644 --- a/QSharpDemo/Program.cs +++ b/QSharpDemo/Program.cs @@ -8,11 +8,9 @@ namespace qControlDemo { static void Main(string[] args) { + Console.OutputEncoding = System.Text.Encoding.UTF8; Console.WriteLine("qControlDemo Started"); QBrowser browser = new QBrowser(); - - - Thread.Sleep(100000000); } } diff --git a/QSharpDemo/QSharpDemo.csproj b/QSharpDemo/QSharpDemo.csproj index 283fc7c..d73e9d0 100644 --- a/QSharpDemo/QSharpDemo.csproj +++ b/QSharpDemo/QSharpDemo.csproj @@ -5,6 +5,10 @@ netcoreapp3.1 + + + +