diff --git a/Dalamud.Test/Rpc/DalamudUriTests.cs b/Dalamud.Test/Rpc/DalamudUriTests.cs deleted file mode 100644 index b371a5698..000000000 --- a/Dalamud.Test/Rpc/DalamudUriTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Linq; - -using Dalamud.Networking.Rpc.Model; - -using Xunit; - -namespace Dalamud.Test.Rpc -{ - public class DalamudUriTests - { - [Theory] - [InlineData("https://www.google.com/", false)] - [InlineData("dalamud://PluginInstaller/Dalamud.FindAnything", true)] - public void ValidatesScheme(string uri, bool valid) - { - Action act = () => { _ = DalamudUri.FromUri(uri); }; - - var ex = Record.Exception(act); - if (valid) - { - Assert.Null(ex); - } - else - { - Assert.NotNull(ex); - Assert.IsType(ex); - } - } - - [Theory] - [InlineData("dalamud://PluginInstaller/Dalamud.FindAnything", "plugininstaller")] - [InlineData("dalamud://Plugin/Dalamud.FindAnything/OpenWindow", "plugin")] - [InlineData("dalamud://Test", "test")] - public void ExtractsNamespace(string uri, string expectedNamespace) - { - var dalamudUri = DalamudUri.FromUri(uri); - Assert.Equal(expectedNamespace, dalamudUri.Namespace); - } - - [Theory] - [InlineData("dalamud://foo/bar/baz/qux/?cow=moo", "/bar/baz/qux/")] - [InlineData("dalamud://foo/bar/baz/qux?cow=moo", "/bar/baz/qux")] - [InlineData("dalamud://foo/bar/baz", "/bar/baz")] - [InlineData("dalamud://foo/bar", "/bar")] - [InlineData("dalamud://foo/bar/", "/bar/")] - [InlineData("dalamud://foo/", "/")] - public void ExtractsPath(string uri, string expectedPath) - { - var dalamudUri = DalamudUri.FromUri(uri); - Assert.Equal(expectedPath, dalamudUri.Path); - } - - [Theory] - [InlineData("dalamud://foo/bar/baz/qux/?cow=moo#frag", "/bar/baz/qux/?cow=moo#frag")] - [InlineData("dalamud://foo/bar/baz/qux/?cow=moo", "/bar/baz/qux/?cow=moo")] - [InlineData("dalamud://foo/bar/baz/qux?cow=moo", "/bar/baz/qux?cow=moo")] - [InlineData("dalamud://foo/bar/baz", "/bar/baz")] - [InlineData("dalamud://foo/bar?cow=moo", "/bar?cow=moo")] - [InlineData("dalamud://foo/bar", "/bar")] - [InlineData("dalamud://foo/bar/?cow=moo", "/bar/?cow=moo")] - [InlineData("dalamud://foo/bar/", "/bar/")] - [InlineData("dalamud://foo/?cow=moo#chicken", "/?cow=moo#chicken")] - [InlineData("dalamud://foo/?cow=moo", "/?cow=moo")] - [InlineData("dalamud://foo/", "/")] - public void ExtractsData(string uri, string expectedData) - { - var dalamudUri = DalamudUri.FromUri(uri); - - Assert.Equal(expectedData, dalamudUri.Data); - } - - [Theory] - [InlineData("dalamud://foo/bar", 0)] - [InlineData("dalamud://foo/bar?cow=moo", 1)] - [InlineData("dalamud://foo/bar?cow=moo&wolf=awoo", 2)] - [InlineData("dalamud://foo/bar?cow=moo&wolf=awoo&cat", 3)] - public void ExtractsQueryParams(string uri, int queryCount) - { - var dalamudUri = DalamudUri.FromUri(uri); - Assert.Equal(queryCount, dalamudUri.QueryParams.Count); - } - - [Theory] - [InlineData("dalamud://foo/bar/baz/qux/meh/?foo=bar", 5, true)] - [InlineData("dalamud://foo/bar/baz/qux/meh/", 5, true)] - [InlineData("dalamud://foo/bar/baz/qux/meh", 5)] - [InlineData("dalamud://foo/bar/baz/qux", 4)] - [InlineData("dalamud://foo/bar/baz", 3)] - [InlineData("dalamud://foo/bar/", 2)] - [InlineData("dalamud://foo/bar", 2)] - public void ExtractsSegments(string uri, int segmentCount, bool finalSegmentEndsWithSlash = false) - { - var dalamudUri = DalamudUri.FromUri(uri); - var segments = dalamudUri.Segments; - - // First segment must always be `/` - Assert.Equal("/", segments[0]); - - Assert.Equal(segmentCount, segments.Length); - - if (finalSegmentEndsWithSlash) - { - Assert.EndsWith("/", segments.Last()); - } - } - } -} diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 9685b92ac..a2538ebd4 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -79,7 +79,6 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Dalamud/Networking/Rpc/Model/DalamudUri.cs b/Dalamud/Networking/Rpc/Model/DalamudUri.cs deleted file mode 100644 index 852478762..000000000 --- a/Dalamud/Networking/Rpc/Model/DalamudUri.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Web; - -namespace Dalamud.Networking.Rpc.Model; - -/// -/// A Dalamud Uri, in the format: -/// dalamud://{NAMESPACE}/{ARBITRARY} -/// -public record DalamudUri -{ - private readonly Uri rawUri; - - private DalamudUri(Uri uri) - { - if (uri.Scheme != "dalamud") - { - throw new ArgumentOutOfRangeException(nameof(uri), "URI must be of scheme dalamud."); - } - - this.rawUri = uri; - } - - /// - /// Gets the namespace that this URI should be routed to. Generally a high level component like "PluginInstaller". - /// - public string Namespace => this.rawUri.Authority; - - /// - /// Gets the raw (untargeted) path and query params for this URI. - /// - public string Data => - this.rawUri.GetComponents(UriComponents.PathAndQuery | UriComponents.Fragment, UriFormat.UriEscaped); - - /// - /// Gets the raw (untargeted) path for this URI. - /// - public string Path => this.rawUri.AbsolutePath; - - /// - /// Gets a list of segments based on the provided Data element. - /// - public string[] Segments => this.GetDataSegments(); - - /// - /// Gets the raw query parameters for this URI, if any. - /// - public string Query => this.rawUri.Query; - - /// - /// Gets the query params (as a parsed NameValueCollection) in this URI. - /// - public NameValueCollection QueryParams => HttpUtility.ParseQueryString(this.Query); - - /// - /// Gets the fragment (if one is specified) in this URI. - /// - public string Fragment => this.rawUri.Fragment; - - /// - public override string ToString() => this.rawUri.ToString(); - - /// - /// Build a DalamudURI from a given URI. - /// - /// The URI to convert to a Dalamud URI. - /// Returns a DalamudUri. - public static DalamudUri FromUri(Uri uri) - { - return new DalamudUri(uri); - } - - /// - /// Build a DalamudURI from a URI in string format. - /// - /// The URI to convert to a Dalamud URI. - /// Returns a DalamudUri. - public static DalamudUri FromUri(string uri) => FromUri(new Uri(uri)); - - private string[] GetDataSegments() - { - // reimplementation of the System.URI#Segments, under MIT license. - var path = this.Path; - - var segments = new List(); - var current = 0; - while (current < path.Length) - { - var next = path.IndexOf('/', current); - if (next == -1) - { - next = path.Length - 1; - } - - segments.Add(path.Substring(current, (next - current) + 1)); - current = next + 1; - } - - return segments.ToArray(); - } -} diff --git a/Dalamud/Networking/Rpc/RpcConnection.cs b/Dalamud/Networking/Rpc/RpcConnection.cs deleted file mode 100644 index 5288948eb..000000000 --- a/Dalamud/Networking/Rpc/RpcConnection.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -using Dalamud.Networking.Rpc.Service; - -using Serilog; - -using StreamJsonRpc; - -namespace Dalamud.Networking.Rpc; - -/// -/// A single RPC client session connected via a stream (named pipe or Unix socket). -/// -internal class RpcConnection : IDisposable -{ - private readonly Stream stream; - private readonly RpcServiceRegistry registry; - private readonly CancellationTokenSource cts = new(); - - /// - /// Initializes a new instance of the class. - /// - /// The stream that this connection will handle. - /// A registry of RPC services. - public RpcConnection(Stream stream, RpcServiceRegistry registry) - { - this.Id = Guid.CreateVersion7(); - this.stream = stream; - this.registry = registry; - - var formatter = new JsonMessageFormatter(); - var handler = new HeaderDelimitedMessageHandler(stream, stream, formatter); - - this.Rpc = new JsonRpc(handler); - this.Rpc.AllowModificationWhileListening = true; - this.Rpc.Disconnected += this.OnDisconnected; - this.registry.Attach(this.Rpc); - - this.Rpc.StartListening(); - } - - /// - /// Gets the GUID for this connection. - /// - public Guid Id { get; } - - /// - /// Gets the JsonRpc instance for this connection. - /// - public JsonRpc Rpc { get; } - - /// - /// Gets a task that's called on RPC completion. - /// - public Task Completion => this.Rpc.Completion; - - /// - public void Dispose() - { - if (!this.cts.IsCancellationRequested) - { - this.cts.Cancel(); - } - - try - { - this.Rpc.Dispose(); - } - catch (Exception ex) - { - Log.Debug(ex, "Error disposing JsonRpc for client {Id}", this.Id); - } - - try - { - this.stream.Dispose(); - } - catch (Exception ex) - { - Log.Debug(ex, "Error disposing stream for client {Id}", this.Id); - } - - this.cts.Dispose(); - GC.SuppressFinalize(this); - } - - private void OnDisconnected(object? sender, JsonRpcDisconnectedEventArgs e) - { - Log.Debug("RPC client {Id} disconnected: {Reason}", this.Id, e.Description); - this.registry.Detach(this.Rpc); - this.Dispose(); - } -} diff --git a/Dalamud/Networking/Rpc/RpcHostService.cs b/Dalamud/Networking/Rpc/RpcHostService.cs deleted file mode 100644 index bbe9dc8eb..000000000 --- a/Dalamud/Networking/Rpc/RpcHostService.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; - -using Dalamud.Logging.Internal; -using Dalamud.Networking.Rpc.Transport; - -namespace Dalamud.Networking.Rpc; - -/// -/// The Dalamud service repsonsible for hosting the RPC. -/// -[ServiceManager.EarlyLoadedService] -internal class RpcHostService : IServiceType, IInternalDisposableService -{ - private readonly ModuleLog log = new("RPC"); - private readonly RpcServiceRegistry registry = new(); - private readonly List transports = []; - - /// - /// Initializes a new instance of the class. - /// - [ServiceManager.ServiceConstructor] - public RpcHostService() - { - this.StartUnixTransport(); - - if (this.transports.Count == 0) - { - this.log.Warning("No RPC hosts could be started on this platform"); - } - } - - /// - /// Gets all active RPC transports. - /// - public IReadOnlyList Transports => this.transports; - - /// - /// Add a new service Object to the RPC host. - /// - /// The object to add. - public void AddService(object service) => this.registry.AddService(service); - - /// - /// Add a new standalone method to the RPC host. - /// - /// The method name to add. - /// The handler to add. - public void AddMethod(string name, Delegate handler) => this.registry.AddMethod(name, handler); - - /// - public void DisposeService() - { - foreach (var host in this.transports) - { - host.Dispose(); - } - - this.transports.Clear(); - } - - /// - public async Task InvokeClientAsync(Guid clientId, string method, params object[] arguments) - { - var clients = this.transports.SelectMany(t => t.Connections).ToImmutableDictionary(); - - if (!clients.TryGetValue(clientId, out var session)) - throw new KeyNotFoundException($"No client {clientId}"); - - return await session.Rpc.InvokeAsync(method, arguments).ConfigureAwait(false); - } - - /// - public async Task BroadcastNotifyAsync(string method, params object[] arguments) - { - await foreach (var transport in this.transports.ToAsyncEnumerable().ConfigureAwait(false)) - { - await transport.BroadcastNotifyAsync(method, arguments).ConfigureAwait(false); - } - } - - private void StartUnixTransport() - { - var transport = new UnixRpcTransport(this.registry); - this.transports.Add(transport); - transport.Start(); - this.log.Information("RpcHostService listening to UNIX socket: {Socket}", transport.SocketPath); - } -} diff --git a/Dalamud/Networking/Rpc/RpcServiceRegistry.cs b/Dalamud/Networking/Rpc/RpcServiceRegistry.cs deleted file mode 100644 index 6daea14bf..000000000 --- a/Dalamud/Networking/Rpc/RpcServiceRegistry.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Collections.Generic; -using System.Threading; - -using StreamJsonRpc; - -namespace Dalamud.Networking.Rpc; - -/// -/// Thread-safe registry of local RPC target objects that are exposed to every connected JsonRpc session. -/// New sessions get all previously registered targets; newly added targets are attached to all active sessions. -/// -internal class RpcServiceRegistry -{ - private readonly Lock sync = new(); - private readonly List targets = []; - private readonly List<(string Name, Delegate Handler)> methods = []; - private readonly List activeRpcs = []; - - /// - /// Registers a new local RPC target object. Its public JSON-RPC methods become callable by clients. - /// Adds to the registry and attaches it to all active RPC sessions. - /// - /// The service instance containing JSON-RPC callable methods to expose. - public void AddService(object service) - { - lock (this.sync) - { - this.targets.Add(service); - foreach (var rpc in this.activeRpcs) - { - rpc.AddLocalRpcTarget(service); - } - } - } - - /// - /// Registers a new standalone JSON-RPC method. - /// - /// The name of the method to add. - /// The handler to add. - public void AddMethod(string name, Delegate handler) - { - lock (this.sync) - { - this.methods.Add((name, handler)); - foreach (var rpc in this.activeRpcs) - { - rpc.AddLocalRpcMethod(name, handler); - } - } - } - - /// - /// Attaches a JsonRpc instance to the registry so it receives all existing service targets. - /// - /// The JsonRpc instance to attach and populate with current targets. - internal void Attach(JsonRpc rpc) - { - lock (this.sync) - { - this.activeRpcs.Add(rpc); - foreach (var t in this.targets) - { - rpc.AddLocalRpcTarget(t); - } - - foreach (var m in this.methods) - { - rpc.AddLocalRpcMethod(m.Name, m.Handler); - } - } - } - - /// - /// Detaches a JsonRpc instance from the registry (e.g. when a client disconnects). - /// - /// The JsonRpc instance being detached. - internal void Detach(JsonRpc rpc) - { - lock (this.sync) - { - this.activeRpcs.Remove(rpc); - } - } -} diff --git a/Dalamud/Networking/Rpc/Service/ClientHelloService.cs b/Dalamud/Networking/Rpc/Service/ClientHelloService.cs deleted file mode 100644 index ae8319f21..000000000 --- a/Dalamud/Networking/Rpc/Service/ClientHelloService.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System.Diagnostics; -using System.Threading.Tasks; - -using Dalamud.Data; -using Dalamud.Game; -using Dalamud.Game.ClientState; -using Dalamud.Utility; - -using Lumina.Excel.Sheets; - -namespace Dalamud.Networking.Rpc.Service; - -/// -/// A minimal service to respond with information about this client. -/// -[ServiceManager.EarlyLoadedService] -internal sealed class ClientHelloService : IInternalDisposableService -{ - /// - /// Initializes a new instance of the class. - /// - /// Injected host service. - [ServiceManager.ServiceConstructor] - public ClientHelloService(RpcHostService rpcHostService) - { - rpcHostService.AddMethod("hello", this.HandleHello); - } - - /// - /// Handle a hello request. - /// - /// . - /// Respond with information. - public async Task HandleHello(ClientHelloRequest request) - { - var dalamud = await Service.GetAsync(); - - return new ClientHelloResponse - { - ApiVersion = "1.0", - DalamudVersion = Versioning.GetScmVersion(), - GameVersion = dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown", - ProcessId = Environment.ProcessId, - ProcessStartTime = new DateTimeOffset(Process.GetCurrentProcess().StartTime).ToUnixTimeSeconds(), - ClientState = await this.GetClientIdentifier(), - }; - } - - /// - public void DisposeService() - { - } - - private async Task GetClientIdentifier() - { - var framework = await Service.GetAsync(); - var clientState = await Service.GetAsync(); - var dataManager = await Service.GetAsync(); - - var clientIdentifier = $"FFXIV Process ${Environment.ProcessId}"; - - await framework.RunOnFrameworkThread(() => - { - if (clientState.IsLoggedIn) - { - var player = clientState.LocalPlayer; - if (player != null) - { - var world = dataManager.GetExcelSheet().GetRow(player.HomeWorld.RowId); - clientIdentifier = $"Logged in as {player.Name.TextValue} @ {world.Name.ExtractText()}"; - } - } - else - { - clientIdentifier = "On login screen"; - } - }); - - return clientIdentifier; - } -} - -/// -/// A request from a client to say hello. -/// -internal record ClientHelloRequest -{ - /// - /// Gets the API version this client is expecting. - /// - public string ApiVersion { get; init; } = string.Empty; - - /// - /// Gets the user agent of the client. - /// - public string UserAgent { get; init; } = string.Empty; -} - -/// -/// A response from Dalamud to a hello request. -/// -internal record ClientHelloResponse -{ - /// - /// Gets the API version this server has offered. - /// - public string? ApiVersion { get; init; } - - /// - /// Gets the current Dalamud version. - /// - public string? DalamudVersion { get; init; } - - /// - /// Gets the current game version. - /// - public string? GameVersion { get; init; } - - /// - /// Gets the process ID of this client. - /// - public int? ProcessId { get; init; } - - /// - /// Gets the time this process started. - /// - public long? ProcessStartTime { get; init; } - - /// - /// Gets a state for this client for user display. - /// - public string? ClientState { get; init; } -} diff --git a/Dalamud/Networking/Rpc/Service/LinkHandlerService.cs b/Dalamud/Networking/Rpc/Service/LinkHandlerService.cs deleted file mode 100644 index 9fa311ede..000000000 --- a/Dalamud/Networking/Rpc/Service/LinkHandlerService.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; - -using Dalamud.Logging.Internal; -using Dalamud.Networking.Rpc.Model; -using Dalamud.Utility; - -namespace Dalamud.Networking.Rpc.Service; - -/// -/// A service responsible for handling Dalamud URIs and dispatching them accordingly. -/// -[ServiceManager.EarlyLoadedService] -internal class LinkHandlerService : IInternalDisposableService -{ - private readonly ModuleLog log = new("LinkHandler"); - - // key: namespace (e.g. "plugin" or "PluginInstaller") -> list of handlers - private readonly ConcurrentDictionary>> handlers - = new(StringComparer.OrdinalIgnoreCase); - - /// - /// Initializes a new instance of the class. - /// - /// The injected RPC host service. - [ServiceManager.ServiceConstructor] - public LinkHandlerService(RpcHostService rpcHostService) - { - rpcHostService.AddMethod("handleLink", this.HandleLinkCall); - } - - /// - public void DisposeService() - { - } - - /// - /// Register a handler for a namespace. All URIs with this namespace will be dispatched to the handler. - /// - /// The namespace to use for this subscription. - /// The command handler. - public void Register(string ns, Action handler) - { - if (string.IsNullOrWhiteSpace(ns)) - throw new ArgumentNullException(nameof(ns)); - - var list = this.handlers.GetOrAdd(ns, _ => []); - lock (list) - { - list.Add(handler); - } - - this.log.Verbose("Registered handler for {Namespace}", ns); - } - - /// - /// Unregister a handler. - /// - /// The namespace to use for this subscription. - /// The command handler. - public void Unregister(string ns, Action handler) - { - if (string.IsNullOrWhiteSpace(ns)) - return; - - if (!this.handlers.TryGetValue(ns, out var list)) - return; - - list.RemoveAll(x => x == handler); - - if (list.Count == 0) - this.handlers.TryRemove(ns, out _); - - this.log.Verbose("Unregistered handler for {Namespace}", ns); - } - - /// - /// Dispatch a URI to matching handlers. - /// - /// The URI to parse and dispatch. - public void Dispatch(DalamudUri uri) - { - this.log.Information("Received URI: {Uri}", uri.ToString()); - - var ns = uri.Namespace; - if (!this.handlers.TryGetValue(ns, out var actions)) - return; - - foreach (var h in actions) - { - h.InvokeSafely(uri); - } - } - - /// - /// The RPC-invokable link handler. - /// - /// A plain-text URI to parse. - public void HandleLinkCall(string uri) - { - if (string.IsNullOrWhiteSpace(uri)) - return; - - var du = DalamudUri.FromUri(uri); - this.Dispatch(du); - } -} diff --git a/Dalamud/Networking/Rpc/Service/Links/DebugLinkHandler.cs b/Dalamud/Networking/Rpc/Service/Links/DebugLinkHandler.cs deleted file mode 100644 index 269617fc0..000000000 --- a/Dalamud/Networking/Rpc/Service/Links/DebugLinkHandler.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Dalamud.Game.Gui.Toast; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.ImGuiNotification.Internal; -using Dalamud.Networking.Rpc.Model; - -namespace Dalamud.Networking.Rpc.Service.Links; - -#if DEBUG - -/// -/// A debug controller for link handling. -/// -[ServiceManager.EarlyLoadedService] -internal sealed class DebugLinkHandler : IInternalDisposableService -{ - private readonly LinkHandlerService linkHandlerService; - - /// - /// Initializes a new instance of the class. - /// - /// Injected LinkHandler. - [ServiceManager.ServiceConstructor] - public DebugLinkHandler(LinkHandlerService linkHandler) - { - this.linkHandlerService = linkHandler; - - this.linkHandlerService.Register("debug", this.HandleLink); - } - - /// - public void DisposeService() - { - this.linkHandlerService.Unregister("debug", this.HandleLink); - } - - private void HandleLink(DalamudUri uri) - { - var action = uri.Path.Split("/").GetValue(1)?.ToString(); - switch (action) - { - case "toast": - this.ShowToast(uri); - break; - case "notification": - this.ShowNotification(uri); - break; - } - } - - private void ShowToast(DalamudUri uri) - { - var message = uri.QueryParams.Get("message") ?? "Hello, world!"; - Service.Get().ShowNormal(message); - } - - private void ShowNotification(DalamudUri uri) - { - Service.Get().AddNotification( - new Notification - { - Title = uri.QueryParams.Get("title"), - Content = uri.QueryParams.Get("content") ?? "Hello, world!", - }); - } -} - -#endif diff --git a/Dalamud/Networking/Rpc/Service/Links/PluginLinkHandler.cs b/Dalamud/Networking/Rpc/Service/Links/PluginLinkHandler.cs deleted file mode 100644 index 3b7f18437..000000000 --- a/Dalamud/Networking/Rpc/Service/Links/PluginLinkHandler.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Linq; - -using Dalamud.Console; -using Dalamud.IoC; -using Dalamud.IoC.Internal; -using Dalamud.Networking.Rpc.Model; -using Dalamud.Plugin.Internal.Types; -using Dalamud.Plugin.Services; - -#pragma warning disable DAL_RPC - -namespace Dalamud.Networking.Rpc.Service.Links; - -/// -[PluginInterface] -[ServiceManager.ScopedService] -[ResolveVia] -public class PluginLinkHandler : IInternalDisposableService, IPluginLinkHandler -{ - private readonly LinkHandlerService linkHandler; - private readonly LocalPlugin localPlugin; - - /// - /// Initializes a new instance of the class. - /// - /// The plugin to bind this service to. - /// The central link handler. - internal PluginLinkHandler(LocalPlugin localPlugin, LinkHandlerService linkHandler) - { - this.linkHandler = linkHandler; - this.localPlugin = localPlugin; - - this.linkHandler.Register("plugin", this.HandleUri); - } - - /// - public event IPluginLinkHandler.PluginUriReceived? OnUriReceived; - - /// - public void DisposeService() - { - this.OnUriReceived = null; - this.linkHandler.Unregister("plugin", this.HandleUri); - } - - private void HandleUri(DalamudUri uri) - { - var target = uri.Path.Split("/").ElementAtOrDefault(1); - var thisPlugin = ConsoleManagerPluginUtil.GetSanitizedNamespaceName(this.localPlugin.InternalName); - if (target == null || !string.Equals(target, thisPlugin, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - this.OnUriReceived?.Invoke(uri); - } -} diff --git a/Dalamud/Networking/Rpc/Transport/IRpcTransport.cs b/Dalamud/Networking/Rpc/Transport/IRpcTransport.cs deleted file mode 100644 index ad7578eb4..000000000 --- a/Dalamud/Networking/Rpc/Transport/IRpcTransport.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Dalamud.Networking.Rpc.Transport; - -/// -/// Interface for RPC host implementations (named pipes or Unix sockets). -/// -internal interface IRpcTransport : IDisposable -{ - /// - /// Gets a list of active RPC connections. - /// - IReadOnlyDictionary Connections { get; } - - /// Starts accepting client connections. - void Start(); - - /// Invoke an RPC request on a specific client expecting a result. - /// The client ID to invoke. - /// The method to invoke. - /// Any arguments to invoke. - /// An optional return based on the specified RPC. - /// The expected response type. - Task InvokeClientAsync(Guid clientId, string method, params object[] arguments); - - /// Send a notification to all connected clients (no response expected). - /// The method name to broadcast. - /// The arguments to broadcast. - /// Returns a Task when completed. - Task BroadcastNotifyAsync(string method, params object[] arguments); -} diff --git a/Dalamud/Networking/Rpc/Transport/UnixRpcTransport.cs b/Dalamud/Networking/Rpc/Transport/UnixRpcTransport.cs deleted file mode 100644 index 17da51444..000000000 --- a/Dalamud/Networking/Rpc/Transport/UnixRpcTransport.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -using Dalamud.Logging.Internal; -using Dalamud.Utility; - -namespace Dalamud.Networking.Rpc.Transport; - -/// -/// Simple multi-client JSON-RPC Unix socket host using StreamJsonRpc. -/// -internal class UnixRpcTransport : IRpcTransport -{ - private readonly ModuleLog log = new("RPC/Transport/UnixSocket"); - - private readonly RpcServiceRegistry registry; - private readonly CancellationTokenSource cts = new(); - private readonly ConcurrentDictionary sessions = new(); - private readonly string? cleanupSocketDirectory; - - private Task? acceptLoopTask; - private Socket? listenSocket; - - /// - /// Initializes a new instance of the class. - /// - /// The RPC service registry to use. - /// The Unix socket directory to use. If null, defaults to Dalamud home directory. - /// The name of the socket to create. - public UnixRpcTransport(RpcServiceRegistry registry, string? socketDirectory = null, string? socketName = null) - { - this.registry = registry; - socketName ??= $"DalamudRPC.{Environment.ProcessId}.sock"; - - if (!socketDirectory.IsNullOrEmpty()) - { - this.SocketPath = Path.Combine(socketDirectory, socketName); - } - else - { - socketDirectory = Service.Get().StartInfo.TempDirectory; - - if (socketDirectory == null) - { - this.SocketPath = Path.Combine(Path.GetTempPath(), socketName); - this.log.Warning("Temp dir was not set in StartInfo; using system temp for unix socket."); - } - else - { - this.SocketPath = Path.Combine(socketDirectory, socketName); - this.cleanupSocketDirectory = socketDirectory; - } - } - } - - /// - /// Gets the path of the Unix socket this RPC host is using. - /// - public string SocketPath { get; } - - /// - public IReadOnlyDictionary Connections => this.sessions; - - /// Starts accepting client connections. - public void Start() - { - if (this.acceptLoopTask != null) return; - - // Make the directory for the socket if it doesn't exist - var socketDir = Path.GetDirectoryName(this.SocketPath); - if (!string.IsNullOrEmpty(socketDir) && !Directory.Exists(socketDir)) - { - this.log.Error("Directory for unix socket does not exist: {Path}", socketDir); - return; - } - - // Delete existing socket for this PID, if it exists. - if (File.Exists(this.SocketPath)) - { - try - { - File.Delete(this.SocketPath); - } - catch (Exception ex) - { - this.log.Warning(ex, "Failed to delete existing socket file: {Path}", this.SocketPath); - } - } - - this.acceptLoopTask = Task.Factory.StartNew(this.AcceptLoopAsync, TaskCreationOptions.LongRunning); - } - - /// Invoke an RPC request on a specific client expecting a result. - /// The client ID to invoke. - /// The method to invoke. - /// Any arguments to invoke. - /// An optional return based on the specified RPC. - /// The expected response type. - public Task InvokeClientAsync(Guid clientId, string method, params object[] arguments) - { - if (!this.sessions.TryGetValue(clientId, out var session)) - throw new KeyNotFoundException($"No client {clientId}"); - - return session.Rpc.InvokeAsync(method, arguments); - } - - /// Send a notification to all connected clients (no response expected). - /// The method name to broadcast. - /// The arguments to broadcast. - /// Returns a Task when completed. - public Task BroadcastNotifyAsync(string method, params object[] arguments) - { - var list = this.sessions.Values; - var tasks = new List(list.Count); - foreach (var s in list) - { - tasks.Add(s.Rpc.NotifyAsync(method, arguments)); - } - - return Task.WhenAll(tasks); - } - - /// - public void Dispose() - { - this.cts.Cancel(); - this.acceptLoopTask?.Wait(1000); - - foreach (var kv in this.sessions) - { - kv.Value.Dispose(); - } - - this.sessions.Clear(); - - this.listenSocket?.Dispose(); - - if (File.Exists(this.SocketPath)) - { - try - { - File.Delete(this.SocketPath); - } - catch (Exception ex) - { - this.log.Warning(ex, "Failed to delete socket file on dispose: {Path}", this.SocketPath); - } - } - - this.cts.Dispose(); - this.log.Information("UnixRpcHost disposed ({Socket})", this.SocketPath); - GC.SuppressFinalize(this); - } - - private async Task AcceptLoopAsync() - { - var token = this.cts.Token; - - try - { - var endpoint = new UnixDomainSocketEndPoint(this.SocketPath); - this.listenSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); - this.listenSocket.Bind(endpoint); - this.listenSocket.Listen(128); - - while (!token.IsCancellationRequested) - { - Socket? clientSocket = null; - try - { - clientSocket = await this.listenSocket.AcceptAsync(token).ConfigureAwait(false); - - var stream = new NetworkStream(clientSocket, ownsSocket: true); - var session = new RpcConnection(stream, this.registry); - this.sessions.TryAdd(session.Id, session); - - this.log.Debug("RPC connection created: {Id}", session.Id); - - _ = session.Completion.ContinueWith(t => - { - this.sessions.TryRemove(session.Id, out _); - this.log.Debug("RPC connection removed: {Id}", session.Id); - }, TaskScheduler.Default); - } - catch (OperationCanceledException) - { - clientSocket?.Dispose(); - break; - } - catch (Exception ex) - { - clientSocket?.Dispose(); - this.log.Error(ex, "Error in socket accept loop"); - await Task.Delay(500, token).ConfigureAwait(false); - } - } - } - catch (Exception ex) - { - this.log.Error(ex, "Fatal error in Unix socket accept loop"); - } - } -} diff --git a/Dalamud/Plugin/Services/IPluginLinkHandler.cs b/Dalamud/Plugin/Services/IPluginLinkHandler.cs deleted file mode 100644 index 37101222a..000000000 --- a/Dalamud/Plugin/Services/IPluginLinkHandler.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -using Dalamud.Networking.Rpc.Model; - -namespace Dalamud.Plugin.Services; - -/// -/// A service to allow plugins to subscribe to dalamud:// URIs targeting them. Plugins will receive any URI sent to the -/// dalamud://plugin/{PLUGIN_INTERNAL_NAME}/... namespace. -/// -[Experimental("DAL_RPC", Message = "This service will be finalized around 7.41 and may change before then.")] -public interface IPluginLinkHandler : IDalamudService -{ - /// - /// A delegate containing the received URI. - /// - /// The URI opened by the user. - public delegate void PluginUriReceived(DalamudUri uri); - - /// - /// The event fired when a URI targeting this plugin is received. - /// - event PluginUriReceived OnUriReceived; -} diff --git a/Directory.Packages.props b/Directory.Packages.props index 77a4035a4..18760037b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -46,9 +46,6 @@ - - -