Skip to content

Commit

Permalink
Merge pull request #10 from vrchat-community/feature/oscquery-explore…
Browse files Browse the repository at this point in the history
…r-view

Added OSCQuery Explorer
  • Loading branch information
momo-the-monster authored Oct 21, 2022
2 parents 0d74906 + 935297a commit 3c898c7
Show file tree
Hide file tree
Showing 9 changed files with 977 additions and 120 deletions.
Git LFS file not shown
3 changes: 2 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ This library does not yet return limited attributes based on query strings, like
1. Build vrc-oscquery-lib into vrc-oscquery-lib.dll and add it to your project (will make this a NuGet package once it's ready for wider use).
2. Construct a new OSCQuery service with `new OSCQueryService()`, optionally passing in the name, TCP port to use for serving HTTP, UDP port that you're using for OSC, and an ILogger if you want logs.
3. You should now be able to visit `http://localhost:tcpPort` in a browser and see raw JSON describing an empty root node.
- You can also visit `http://localhost:tcpPort?explorer` to see an OSCQuery Explorer UI for the OSCQuery service, which should be easier to navigate than the raw JSON.
4. You can also visit `http://localhost:tcpPort?HOST_INFO` to get information about the supported attributes of this OSCQuery Server.
5. Next, you can call `AddEndpoint` on your service to add information about an available OSC method. Note that this library does not send or receive OSC messages directly, it is up to you to choose and implement an OSC Library.
6. After you have added an endpoint, you can its information by querying the root node again, or query for your method specifically. If you added an endpoint for the OSC address "/foo/bar", you would query this method directly at `http://localhost:tcpPort/foo/bar`.
7. To remove the endpoint, call the `RemoveEndpoint()` method on your OSCQueryService instance, passing in the OSC address as a string ("/foo/bar");
8. When you are done with the service, call `Dispose` to clean it up
8. When you are done with the service, call `Dispose` to clean it up.

---

Expand Down
10 changes: 6 additions & 4 deletions Tests/vrc-oscquery-tests/SpecTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,15 @@ public void GetOSCTree_ReturnsExpectedValues()
[Test]
public async Task Service_AfterAddingGrandChildNode_HasNodesForEachAncestor()
{
var service = new OSCQueryService();

var port = Extensions.GetAvailableTcpPort();
var service = new OSCQueryService(Guid.NewGuid().ToString(), port);

string fullPath = "/foo/bar/baz";

service.AddEndpoint<int>(fullPath, Attributes.AccessValues.ReadOnly);

var response = await new HttpClient().GetAsync($"http://localhost:{OSCQueryService.DefaultPortHttp}/");
var response = await new HttpClient().GetAsync($"http://localhost:{port}/");

Assert.True(response.IsSuccessStatusCode);

Expand All @@ -197,13 +199,13 @@ public async Task Service_AfterAddingGrandChildNode_HasNodesForEachAncestor()
}

[Test]
public async Task Service_WithRequestForFavicon_NoCrash()
public async Task Service_WithRequestForFavicon_ReturnsSuccess()
{
var port = Extensions.GetAvailableTcpPort();
var service = new OSCQueryService("TestService", port);

var response = await new HttpClient().GetAsync($"http://localhost:{port}/favicon.ico");
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}

[Test]
Expand Down
6 changes: 6 additions & 0 deletions vrc-oscquery-lib/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,11 @@ public static string OSCTypeFor(Type type)
public const string SERVICE_OSC_UDP = "_osc._udp";

#endregion

#region HTTPServer

public static string EXPLORER = "?explorer";

#endregion
}
}
104 changes: 58 additions & 46 deletions vrc-oscquery-lib/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
Expand All @@ -12,70 +13,81 @@ public static class Extensions
{
private static HttpClient _client = new HttpClient();

public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
{
var queue = new Queue<T>();
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
{
var queue = new Queue<T>();

using (var e = source.GetEnumerator())
using (var e = source.GetEnumerator())
{
while (e.MoveNext())
{
while (e.MoveNext())
if (queue.Count == count)
{
if (queue.Count == count)
{
do
{
yield return queue.Dequeue();
queue.Enqueue(e.Current);
} while (e.MoveNext());
}
else
do
{
yield return queue.Dequeue();
queue.Enqueue(e.Current);
}
} while (e.MoveNext());
}
else
{
queue.Enqueue(e.Current);
}
}
}
}

private static readonly IPEndPoint DefaultLoopbackEndpoint = new IPEndPoint(IPAddress.Loopback, port: 0);

private static readonly IPEndPoint DefaultLoopbackEndpoint = new IPEndPoint(IPAddress.Loopback, port: 0);
public static int GetAvailableTcpPort()
public static int GetAvailableTcpPort()
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
socket.Bind(DefaultLoopbackEndpoint);
return ((IPEndPoint)socket.LocalEndPoint).Port;
}
socket.Bind(DefaultLoopbackEndpoint);
return ((IPEndPoint)socket.LocalEndPoint).Port;
}

public static int GetAvailableUdpPort()
}

public static int GetAvailableUdpPort()
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
{
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
{
socket.Bind(DefaultLoopbackEndpoint);
return ((IPEndPoint)socket.LocalEndPoint).Port;
}
socket.Bind(DefaultLoopbackEndpoint);
return ((IPEndPoint)socket.LocalEndPoint).Port;
}
}

public static async Task<OSCQueryRootNode> GetOSCTree(IPAddress ip, int port)
public static async Task<OSCQueryRootNode> GetOSCTree(IPAddress ip, int port)
{
var response = await _client.GetAsync($"http://{ip}:{port}/");
if (!response.IsSuccessStatusCode)
{
var response = await _client.GetAsync($"http://{ip}:{port}/");
if (!response.IsSuccessStatusCode)
{
// Logger.Error($"Could not get OSC Tree from {ip}:{port} because {response.ReasonPhrase}");
return null;
}

var oscTreeString = await response.Content.ReadAsStringAsync();
var oscTree = OSCQueryRootNode.FromString(oscTreeString);

return oscTree;
return null;
}

public static async Task<HostInfo> GetHostInfo(IPAddress address, int port)
var oscTreeString = await response.Content.ReadAsStringAsync();
var oscTree = OSCQueryRootNode.FromString(oscTreeString);

return oscTree;
}

public static async Task<HostInfo> GetHostInfo(IPAddress address, int port)
{
var response = await _client.GetAsync($"http://{address}:{port}?{Attributes.HOST_INFO}");
var hostInfoString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<HostInfo>(hostInfoString);
}

public static async Task ServeStaticFile(string path, string mimeType, HttpListenerContext context)
{
using (var targetFile = File.OpenRead(path))
{
var response = await _client.GetAsync($"http://{address}:{port}?{Attributes.HOST_INFO}");
var hostInfoString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<HostInfo>(hostInfoString);
context.Response.ContentType =mimeType;
context.Response.StatusCode = 200;
context.Response.ContentLength64 = targetFile.Length;
await targetFile.CopyToAsync(context.Response.OutputStream);
await context.Response.OutputStream.FlushAsync();
}
}
}
}
Loading

0 comments on commit 3c898c7

Please sign in to comment.