Skip to content

Commit

Permalink
Support removing method handlers. (#281)
Browse files Browse the repository at this point in the history
  • Loading branch information
tmds authored May 8, 2024
1 parent b1f995a commit 62e6218
Show file tree
Hide file tree
Showing 6 changed files with 363 additions and 56 deletions.
15 changes: 11 additions & 4 deletions src/Tmds.DBus.Protocol/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,12 +249,19 @@ public async ValueTask<IDisposable> AddMatchAsync<T>(MatchRule rule, MessageValu
}

public void AddMethodHandler(IMethodHandler methodHandler)
=> AddMethodHandlers([ methodHandler ]);
=> UpdateMethodHandlers((dictionary, handler) => dictionary.AddMethodHandler(handler), methodHandler);

public void AddMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers)
{
GetConnection().AddMethodHandlers(methodHandlers);
}
=> UpdateMethodHandlers((dictionary, handlers) => dictionary.AddMethodHandlers(handlers), methodHandlers);

public void RemoveMethodHandler(string path)
=> UpdateMethodHandlers((dictionary, path) => dictionary.RemoveMethodHandler(path), path);

public void RemoveMethodHandlers(IEnumerable<string> paths)
=> UpdateMethodHandlers((dictionary, paths) => dictionary.RemoveMethodHandlers(paths), paths);

private void UpdateMethodHandlers<T>(Action<IMethodHandlerDictionary, T> update, T state)
=> GetConnection().UpdateMethodHandlers(update, state);

private static Connection CreateConnection(ref Connection? field, string? address)
{
Expand Down
9 changes: 2 additions & 7 deletions src/Tmds.DBus.Protocol/DBusConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -490,16 +490,11 @@ private void EmitOnSynchronizationContextHelper(Observer observer, Synchronizati
_currentSynchronizationContext = null;
}

public void AddMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers)
public void UpdateMethodHandlers<T>(Action<IMethodHandlerDictionary, T> update, T state)
{
lock (_gate)
{
if (_state == ConnectionState.Disconnected)
{
return;
}

_pathNodes.AddMethodHandlers(methodHandlers);
update(_pathNodes, state);
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/Tmds.DBus.Protocol/IMethodHandlerDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Tmds.DBus.Protocol;

interface IMethodHandlerDictionary
{
void AddMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers);
void AddMethodHandler(IMethodHandler methodHandler);
void RemoveMethodHandler(string path);
void RemoveMethodHandlers(IEnumerable<string> paths);
}
137 changes: 93 additions & 44 deletions src/Tmds.DBus.Protocol/PathNodeDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,19 @@ public void CopyChildNamesTo(MethodContext methodContext)
}
}

sealed class PathNodeDictionary : Dictionary<string, PathNode>
sealed class PathNodeDictionary : IMethodHandlerDictionary
{
private readonly Dictionary<string, PathNode> _dictionary = new();

public bool TryGetValue(string path, [NotNullWhen(true)]out PathNode? pathNode)
=> _dictionary.TryGetValue(path, out pathNode);

// For tests:
public PathNode this[string path]
=> _dictionary[path];
public int Count
=> _dictionary.Count;

public void AddMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers)
{
if (methodHandlers is null)
Expand All @@ -89,22 +100,8 @@ public void AddMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers)
for (int i = 0; i < methodHandlers.Count; i++)
{
IMethodHandler methodHandler = methodHandlers[i] ?? throw new ArgumentNullException("methodHandler");
string path = methodHandler.Path ?? throw new ArgumentNullException(nameof(methodHandler.Path));

// Validate the path starts with '/' and has no empty sections.
// GetParentPath relies on this.
if (path[0] != '/' || path.IndexOf("//", StringComparison.Ordinal) != -1)
{
throw new FormatException($"The path '{path}' is not valid.");
}

PathNode node = GetOrCreateNode(path);

if (node.MethodHandler is not null)
{
throw new InvalidOperationException($"A method handler is already registered for the path '{path}'.");
}
node.MethodHandler = methodHandler;
AddMethodHandler(methodHandler);

registeredCount++;
}
Expand All @@ -121,20 +118,20 @@ public void AddMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers)
private PathNode GetOrCreateNode(string path)
{
#if NET6_0_OR_GREATER
ref PathNode? node = ref CollectionsMarshal.GetValueRefOrAddDefault(this, path, out bool exists);
ref PathNode? node = ref CollectionsMarshal.GetValueRefOrAddDefault(_dictionary, path, out bool exists);
if (exists)
{
return node!;
}
PathNode newNode = new PathNode();
node = newNode;
#else
if (this.TryGetValue(path, out PathNode? node))
if (_dictionary.TryGetValue(path, out PathNode? node))
{
return node;
}
PathNode newNode = new PathNode();
Add(path, newNode);
_dictionary.Add(path, newNode);
#endif
string? parentPath = GetParentPath(path);
if (parentPath is not null)
Expand Down Expand Up @@ -178,7 +175,7 @@ private void RemoveMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers,
for (int i = 0; i < count; i++)
{
string path = methodHandlers[i].Path;
if (this.Remove(path, out PathNode? node))
if (_dictionary.Remove(path, out PathNode? node))
{
nodes[j++] = (path, node);
node.MethodHandler = null;
Expand Down Expand Up @@ -206,52 +203,104 @@ private void RemoveMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers,
for (int i = 0; i < count; i++)
{
var node = nodes[i];
this[node.Path] = node.Node;
_dictionary[node.Path] = node.Node;
}
}

void RemoveFromParent(string path, PathNode node)
private void RemoveFromParent(string path, PathNode node)
{
PathNode? parent = node.Parent;
if (parent is null)
{
PathNode? parent = node.Parent;
if (parent is null)
return;
}
Debug.Assert(parent.ChildNameCount >= 1, "node is expected to be a known child");
if (parent.ChildNameCount == 1) // We're the only child.
{
if (parent.MethodHandler is not null)
{
return;
// Parent is still needed for the MethodHandler.
parent.ClearChildNames();
}
Debug.Assert(parent.ChildNameCount >= 1, "node is expected to be a known child");
if (parent.ChildNameCount == 1) // We're the only child.
else
{
if (parent.MethodHandler is not null)
{
// Parent is still needed for the MethodHandler.
parent.ClearChildNames();
}
else
{
// Suppress netstandard2.0 nullability warnings around NetstandardExtensions.Remove.
#if NETSTANDARD2_0
#pragma warning disable CS8620
#pragma warning disable CS8604
#endif

// Parent is no longer needed.
string parentPath = GetParentPath(path)!;
Debug.Assert(parentPath is not null);
this.Remove(parentPath, out PathNode? parentNode);
Debug.Assert(parentNode is not null);
RemoveFromParent(parentPath, parentNode);
// Parent is no longer needed.
string parentPath = GetParentPath(path)!;
Debug.Assert(parentPath is not null);
_dictionary.Remove(parentPath, out PathNode? parentNode);
Debug.Assert(parentNode is not null);
RemoveFromParent(parentPath, parentNode);
#if NETSTANDARD2_0
#pragma warning restore CS8620
#pragma warning restore CS8604
#endif
}
}
}
else
{
string childName = GetChildName(path);
parent.RemoveChildName(childName);
}
}

public void AddMethodHandler(IMethodHandler methodHandler)
{
string path = methodHandler.Path ?? throw new ArgumentNullException(nameof(methodHandler.Path));

// Validate the path starts with '/' and has no empty sections.
// GetParentPath relies on this.
if (path[0] != '/' || path.IndexOf("//", StringComparison.Ordinal) != -1)
{
throw new FormatException($"The path '{path}' is not valid.");
}

PathNode node = GetOrCreateNode(path);

if (node.MethodHandler is not null)
{
throw new InvalidOperationException($"A method handler is already registered for the path '{path}'.");
}
node.MethodHandler = methodHandler;
}

public void RemoveMethodHandler(string path)
{
if (path is null)
{
throw new ArgumentNullException(nameof(path));
}
if (_dictionary.Remove(path, out PathNode? node))
{
if (node.ChildNameCount > 0)
{
// Node is still needed for its children.
node.MethodHandler = null;
_dictionary.Add(path, node);
}
else
{
string childName = GetChildName(path);
parent.RemoveChildName(childName);
RemoveFromParent(path, node);
}
}
}

public void RemoveMethodHandlers(IEnumerable<string> paths)
{
if (paths is null)
{
throw new ArgumentNullException(nameof(paths));
}
foreach (var path in paths)
{
RemoveMethodHandler(path);
}
}

private static readonly RemoveKeyComparer RemoveKeyComparerInstance = new();

sealed class RemoveKeyComparer : IComparer<(string Path, PathNode Node)>
Expand Down
Loading

0 comments on commit 62e6218

Please sign in to comment.