Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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