feat: refactor messaging interfaces and remove subscription support from IMobileControlMessengerWithSubscriptions

This commit is contained in:
Neil Dorin 2026-06-26 15:53:51 -06:00
parent b945ecb470
commit aae41b5947
10 changed files with 60 additions and 121 deletions

View file

@ -27,5 +27,11 @@ namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
/// </summary> /// </summary>
/// <param name="appServerController"></param> /// <param name="appServerController"></param>
void RegisterWithAppServer(IMobileControl appServerController); void RegisterWithAppServer(IMobileControl appServerController);
/// <summary>
/// Unsubscribe a client from this messenger
/// </summary>
/// <param name="clientId">Client ID to remove from subscription list</param>
void UnsubscribeClient(string clientId);
} }
} }

View file

@ -1,23 +1,14 @@
using System;
using PepperDash.Core; using PepperDash.Core;
namespace PepperDash.Essentials.Core.DeviceTypeInterfaces namespace PepperDash.Essentials.Core.DeviceTypeInterfaces
{ {
/// <summary> /// <summary>
/// Defines the contract for IMobileControlMessenger /// Obsolete. Use <see cref="IMobileControlMessenger"/> directly.
/// Subscriptions are now always enabled in MessengerBase.
/// </summary> /// </summary>
[Obsolete("Use IMobileControlMessenger directly. Subscriptions are always enabled.")]
public interface IMobileControlMessengerWithSubscriptions : IMobileControlMessenger public interface IMobileControlMessengerWithSubscriptions : IMobileControlMessenger
{ {
/// <summary>
/// Unsubscribe a client from this messenger
/// </summary>
/// <param name="clientId"></param>
void UnsubscribeClient(string clientId);
/// <summary>
/// Register this messenger with the AppServerController
/// </summary>
/// <param name="appServerController">parent for this messenger</param>
/// <param name="enableMessengerSubscriptions">Enable messenger subscriptions</param>
void RegisterWithAppServer(IMobileControl appServerController, bool enableMessengerSubscriptions);
} }
} }

View file

@ -30,10 +30,10 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
base.RegisterActions(); base.RegisterActions();
AddAction("/fullStatus", (id, content) => PostCallHistory()); AddAction("/fullStatus", (id, content) => PostCallHistory(id));
AddAction("/callHistoryStatus", (id, content) => PostCallHistory()); AddAction("/callHistoryStatus", (id, content) => PostCallHistory(id));
AddAction("/getCallHistory", (id, content) => PostCallHistory()); AddAction("/getCallHistory", (id, content) => PostCallHistory(id));
} }
private void CallHistory_RecentCallsListHasChanged(object sender, EventArgs e) private void CallHistory_RecentCallsListHasChanged(object sender, EventArgs e)
@ -57,7 +57,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
} }
} }
private void PostCallHistory() private void PostCallHistory(string id = null)
{ {
try try
{ {
@ -67,7 +67,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
PostStatusMessage(new IHasCallHistoryStateMessage PostStatusMessage(new IHasCallHistoryStateMessage
{ {
RecentCalls = recents, RecentCalls = recents,
}); }, id);
} }
} }
catch (Exception ex) catch (Exception ex)

View file

@ -40,8 +40,8 @@ namespace PepperDash.Essentials.AppServer.Messengers
_cameraCodec.CameraSelected += CameraCodec_CameraSelected; _cameraCodec.CameraSelected += CameraCodec_CameraSelected;
AddAction("/fullStatus", (id, content) => SendFullStatus()); AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/codecCamerasStatus", (id, content) => SendFullStatus()); AddAction("/codecCamerasStatus", (id, content) => SendFullStatus(id));
AddAction("/cameraSelect", (id, content) => AddAction("/cameraSelect", (id, content) =>
{ {
@ -327,7 +327,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
} }
} }
private void SendFullStatus() private void SendFullStatus(string id = null)
{ {
try try
{ {
@ -344,7 +344,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
SelectedCamera = GetSelectedCamera(_cameraCodec) SelectedCamera = GetSelectedCamera(_cameraCodec)
}, },
Presets = GetCurrentPresets() Presets = GetCurrentPresets()
}); }, id);
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -28,12 +28,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
SendFullStatus(); SendFullStatus();
} }
private void SendFullStatus() private void SendFullStatus(string id = null)
{ {
PostStatusMessage(new IHasCodecLayoutsStateMessage PostStatusMessage(new IHasCodecLayoutsStateMessage
{ {
CurrentLayout = _layouts.LocalLayoutFeedback.StringValue CurrentLayout = _layouts.LocalLayoutFeedback.StringValue
}); }, id);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -41,9 +41,9 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
base.RegisterActions(); base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus()); AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/layoutStatus", (id, content) => SendFullStatus()); AddAction("/layoutStatus", (id, content) => SendFullStatus(id));
AddAction("/cameraRemoteView", (id, content) => _layouts.LocalLayoutToggle()); AddAction("/cameraRemoteView", (id, content) => _layouts.LocalLayoutToggle());
AddAction("/cameraLayout", (id, content) => _layouts.LocalLayoutToggle()); AddAction("/cameraLayout", (id, content) => _layouts.LocalLayoutToggle());

View file

@ -28,9 +28,9 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
base.RegisterActions(); base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus()); AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/cameraSelfViewStatus", (id, content) => SendFullStatus()); AddAction("/cameraSelfViewStatus", (id, content) => SendFullStatus(id));
AddAction("/cameraSelfView", (id, content) => _selfView.SelfViewModeToggle()); AddAction("/cameraSelfView", (id, content) => _selfView.SelfViewModeToggle());
@ -42,12 +42,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
PostCameraSelfView(); PostCameraSelfView();
} }
private void SendFullStatus() private void SendFullStatus(string id = null)
{ {
PostCameraSelfView(); PostCameraSelfView(id);
} }
private void PostCameraSelfView() private void PostCameraSelfView(string id = null)
{ {
try try
{ {
@ -55,7 +55,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
CameraSelfViewIsOn = _selfView.SelfviewIsOnFeedback.BoolValue, CameraSelfViewIsOn = _selfView.SelfviewIsOnFeedback.BoolValue,
ShowSelfViewByDefault = _selfView.ShowSelfViewByDefault ShowSelfViewByDefault = _selfView.ShowSelfViewByDefault
}); }, id);
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -30,9 +30,9 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
base.RegisterActions(); base.RegisterActions();
AddAction("/fullStatus", (id, content) => SendFullStatus()); AddAction("/fullStatus", (id, content) => SendFullStatus(id));
AddAction("/directoryStatus", (id, content) => SendFullStatus()); AddAction("/directoryStatus", (id, content) => SendFullStatus(id));
AddAction("/getDirectory", (id, content) => GetDirectoryRoot()); AddAction("/getDirectory", (id, content) => GetDirectoryRoot());
@ -49,7 +49,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
AddAction("/directorySearch", (id, content) => AddAction("/directorySearch", (id, content) =>
{ {
var msg = content.ToObject<MobileControlSimpleContent<string>>(); var msg = content.ToObject<MobileControlSimpleContent<string>>();
GetDirectory(msg.Value); _directory.SearchDirectory(msg.Value);
}); });
AddAction("/directoryBack", (id, content) => GetPreviousDirectory()); AddAction("/directoryBack", (id, content) => GetPreviousDirectory());
@ -133,7 +133,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
_directory.GetDirectoryParentFolderContents(); _directory.GetDirectoryParentFolderContents();
} }
private void SendFullStatus() private void SendFullStatus(string id = null)
{ {
PostStatusMessage(new IHasDirectoryStateMessage PostStatusMessage(new IHasDirectoryStateMessage
{ {
@ -144,7 +144,7 @@ namespace PepperDash.Essentials.AppServer.Messengers
HasDirectorySearch = true, HasDirectorySearch = true,
DirectorySelectedFolderName = _directory.CurrentDirectoryResult?.CurrentDirectoryResults?.Count > 0 ? _directory.CurrentDirectoryResult.CurrentDirectoryResults[0].Name : null, DirectorySelectedFolderName = _directory.CurrentDirectoryResult?.CurrentDirectoryResults?.Count > 0 ? _directory.CurrentDirectoryResult.CurrentDirectoryResults[0].Name : null,
DirectorySelectedFolderIsNotRoot = _directory.CurrentDirectoryResultIsNotDirectoryRoot?.BoolValue DirectorySelectedFolderIsNotRoot = _directory.CurrentDirectoryResultIsNotDirectoryRoot?.BoolValue
}); }, id);
} }
} }

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Crestron.SimplSharp.Net;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PepperDash.Core; using PepperDash.Core;
using PepperDash.Core.Logging; using PepperDash.Core.Logging;
@ -13,18 +12,13 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <summary> /// <summary>
/// Provides a messaging bridge /// Provides a messaging bridge
/// </summary> /// </summary>
public abstract class MessengerBase : EssentialsDevice, IMobileControlMessengerWithSubscriptions public abstract class MessengerBase : EssentialsDevice, IMobileControlMessenger
{ {
/// <summary> /// <summary>
/// The device this messenger is associated with /// The device this messenger is associated with
/// </summary> /// </summary>
protected IKeyName _device; protected IKeyName _device;
/// <summary>
/// Enable subscriptions
/// </summary>
protected bool enableMessengerSubscriptions;
/// <summary> /// <summary>
/// List of clients subscribed to this messenger /// List of clients subscribed to this messenger
/// </summary> /// </summary>
@ -100,21 +94,6 @@ namespace PepperDash.Essentials.AppServer.Messengers
RegisterActions(); RegisterActions();
} }
/// <summary>
/// Register this messenger with appserver controller
/// </summary>
/// <param name="appServerController">Parent controller for this messenger</param>
/// <param name="enableMessengerSubscriptions">Enable subscriptions</param>
public void RegisterWithAppServer(IMobileControl appServerController, bool enableMessengerSubscriptions)
{
this.enableMessengerSubscriptions = enableMessengerSubscriptions;
AppServerController = appServerController ?? throw new ArgumentNullException("appServerController");
AppServerController.AddAction(this, HandleMessage);
RegisterActions();
}
private void HandleMessage(string path, string id, JToken content) private void HandleMessage(string path, string id, JToken content)
{ {
// replace base path with empty string. Should leave something like /fullStatus // replace base path with empty string. Should leave something like /fullStatus
@ -125,6 +104,12 @@ namespace PepperDash.Essentials.AppServer.Messengers
return; return;
} }
// Auto-subscribe the client on any incoming action
if (!string.IsNullOrEmpty(id))
{
SubscribeClient(id);
}
this.LogDebug("Executing action for path {path}", path); this.LogDebug("Executing action for path {path}", path);
action(id, content); action(id, content);
@ -176,21 +161,15 @@ namespace PepperDash.Essentials.AppServer.Messengers
} }
/// <summary> /// <summary>
/// Add client to the susbscription list for unsolicited feedback /// Add client to the subscription list for unsolicited feedback
/// </summary> /// </summary>
/// <param name="clientId">Client ID to add</param> /// <param name="clientId">Client ID to add</param>
protected void SubscribeClient(string clientId) private void SubscribeClient(string clientId)
{ {
if (!enableMessengerSubscriptions)
{
return;
}
lock (_subscriberLock) lock (_subscriberLock)
{ {
if (!subscriberIds.Add(clientId)) if (!subscriberIds.Add(clientId))
{ {
this.LogVerbose("Client {clientId} already subscribed", clientId);
return; return;
} }
} }
@ -204,24 +183,14 @@ namespace PepperDash.Essentials.AppServer.Messengers
/// <param name="clientId">Client ID to remove</param> /// <param name="clientId">Client ID to remove</param>
public void UnsubscribeClient(string clientId) public void UnsubscribeClient(string clientId)
{ {
if (!enableMessengerSubscriptions)
{
return;
}
bool wasSubscribed; bool wasSubscribed;
lock (_subscriberLock) lock (_subscriberLock)
{ {
wasSubscribed = subscriberIds.Contains(clientId); wasSubscribed = subscriberIds.Remove(clientId);
if (wasSubscribed)
{
subscriberIds.Remove(clientId);
}
} }
if (!wasSubscribed) if (!wasSubscribed)
{ {
this.LogVerbose("Client with ID {clientId} is not subscribed", clientId);
return; return;
} }
@ -301,35 +270,26 @@ namespace PepperDash.Essentials.AppServer.Messengers
{ {
try try
{ {
// Allow for legacy method to continue without subscriptions var messageType = !string.IsNullOrEmpty(type) ? type : MessagePath;
if (!enableMessengerSubscriptions)
// If clientId is provided, send directly to that client
if (!string.IsNullOrEmpty(clientId))
{ {
AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = clientId, Content = content }); AppServerController?.SendMessageObject(new MobileControlMessage { Type = messageType, ClientId = clientId, Content = content });
return; return;
} }
// handle subscription feedback // Unsolicited feedback: send to all subscribers
// If client is null or empty, this message is unsolicited feedback. Iterate through the subscriber list and send to all interested parties List<string> subscriberSnapshot;
if (string.IsNullOrEmpty(clientId)) lock (_subscriberLock)
{ {
// Create a snapshot of subscribers to avoid collection modification during iteration subscriberSnapshot = new List<string>(subscriberIds);
List<string> subscriberSnapshot;
lock (_subscriberLock)
{
subscriberSnapshot = new List<string>(subscriberIds);
}
foreach (var client in subscriberSnapshot)
{
AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = client, Content = content });
}
return;
} }
SubscribeClient(clientId); foreach (var client in subscriberSnapshot)
{
AppServerController?.SendMessageObject(new MobileControlMessage { Type = !string.IsNullOrEmpty(type) ? type : MessagePath, ClientId = clientId, Content = content }); AppServerController?.SendMessageObject(new MobileControlMessage { Type = messageType, ClientId = client, Content = content });
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -38,13 +38,6 @@ namespace PepperDash.Essentials
/// </summary> /// </summary>
[JsonProperty("enableApiServer")] [JsonProperty("enableApiServer")]
public bool EnableApiServer { get; set; } = true; public bool EnableApiServer { get; set; } = true;
/// <summary>
/// Enable subscriptions for Messengers.
/// Defaults to true for v3.x+
/// </summary>
[JsonProperty("enableMessengerSubscriptions")]
public bool EnableMessengerSubscriptions { get; set; } = true;
} }
/// <summary> /// <summary>

View file

@ -59,14 +59,14 @@ namespace PepperDash.Essentials
new Dictionary<string, IMobileControlMessenger>(); new Dictionary<string, IMobileControlMessenger>();
/// <summary> /// <summary>
/// Get the custom messengers with subscriptions /// Get the custom messengers
/// </summary> /// </summary>
public ReadOnlyDictionary<string, IMobileControlMessengerWithSubscriptions> Messengers => new ReadOnlyDictionary<string, IMobileControlMessengerWithSubscriptions>(_messengers.Values.OfType<IMobileControlMessengerWithSubscriptions>().ToDictionary(k => k.Key, v => v)); public ReadOnlyDictionary<string, IMobileControlMessenger> Messengers => new ReadOnlyDictionary<string, IMobileControlMessenger>(_messengers);
/// <summary> /// <summary>
/// Get the default messengers /// Get the default messengers
/// </summary> /// </summary>
public ReadOnlyDictionary<string, IMobileControlMessengerWithSubscriptions> DefaultMessengers => new ReadOnlyDictionary<string, IMobileControlMessengerWithSubscriptions>(_defaultMessengers.Values.OfType<IMobileControlMessengerWithSubscriptions>().ToDictionary(k => k.Key, v => v)); public ReadOnlyDictionary<string, IMobileControlMessenger> DefaultMessengers => new ReadOnlyDictionary<string, IMobileControlMessenger>(_defaultMessengers);
private readonly GenericQueue _transmitToServerQueue; private readonly GenericQueue _transmitToServerQueue;
@ -534,23 +534,12 @@ namespace PepperDash.Essentials
messenger.MessagePath messenger.MessagePath
); );
if (messenger is IMobileControlMessengerWithSubscriptions subMessenger)
{
subMessenger.RegisterWithAppServer(this, Config.EnableMessengerSubscriptions);
return;
}
messenger.RegisterWithAppServer(this); messenger.RegisterWithAppServer(this);
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void Initialize() protected override void Initialize()
{ {
if (!Config.EnableMessengerSubscriptions)
{
this.LogWarning("Messenger subscriptions disabled. add \"enableMessengerSubscriptions\": true to config for {key} to enable.", Key);
}
foreach (var messenger in _messengers) foreach (var messenger in _messengers)
{ {
try try