mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-18 14:23:51 +02:00
Add /settings endpoint, clean up README, make WorldDB U# impl easier/faster (#556)
* docs: Change readme * feat: New /settings endpoint; Make U# implementation easier To further avoid the 5 second string loading delay, developers are no longer required to use /init before getting data; Every data request endpoint will now initialize the world automatically and include a connection key in the response. Changed the way debug init works; It should now work for all endpoints without extra checks by just returning the current world as wrld_12345. New /settings endpoint allows the change of the allow_external_reads column in the database, but getting data is currently unimplemented. Fix store request logging being... completely wrong. * Update README.md Co-authored-by: Tayou <31988415+TayouVR@users.noreply.github.com> --------- Co-authored-by: Tayou <31988415+TayouVR@users.noreply.github.com>
This commit is contained in:
@@ -18,8 +18,8 @@ namespace VRCX
|
||||
private readonly WorldDatabase worldDB;
|
||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
private string currentWorldId = null;
|
||||
private string lastError = null;
|
||||
private bool debugWorld = false;
|
||||
|
||||
public WorldDBManager(string url)
|
||||
{
|
||||
@@ -50,6 +50,7 @@ namespace VRCX
|
||||
|
||||
responseData.Error = "VRCX not yet initialized. Try again in a moment.";
|
||||
responseData.StatusCode = 503;
|
||||
responseData.ConnectionKey = null;
|
||||
SendJsonResponse(context.Response, responseData);
|
||||
continue;
|
||||
};
|
||||
@@ -75,6 +76,7 @@ namespace VRCX
|
||||
responseData.OK = lastError == null;
|
||||
responseData.StatusCode = 200;
|
||||
responseData.Data = lastError;
|
||||
responseData.ConnectionKey = null;
|
||||
lastError = null;
|
||||
SendJsonResponse(context.Response, responseData);
|
||||
break;
|
||||
@@ -87,9 +89,14 @@ namespace VRCX
|
||||
context.Response.StatusCode = 200;
|
||||
context.Response.Close();
|
||||
break;
|
||||
case "/vrcx/data/settings":
|
||||
responseData = await HandleSetSettingsRequest(context);
|
||||
SendJsonResponse(context.Response, responseData);
|
||||
break;
|
||||
default:
|
||||
responseData.Error = "Invalid VRCX endpoint.";
|
||||
responseData.StatusCode = 404;
|
||||
responseData.ConnectionKey = null;
|
||||
SendJsonResponse(context.Response, responseData);
|
||||
break;
|
||||
}
|
||||
@@ -105,12 +112,53 @@ namespace VRCX
|
||||
|
||||
responseData.Error = $"VRCX has encountered an exception while processing the url '{request.Url}': {ex.Message}";
|
||||
responseData.StatusCode = 500;
|
||||
responseData.ConnectionKey = null;
|
||||
SendJsonResponse(context.Response, responseData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task<WorldDataRequestResponse> HandleSetSettingsRequest(HttpListenerContext context)
|
||||
{
|
||||
var request = context.Request;
|
||||
|
||||
string worldId = await GetCurrentWorldID();
|
||||
string set = request.QueryString["set"];
|
||||
string value = request.QueryString["value"];
|
||||
|
||||
if (!TryInitializeWorld(worldId, out string connectionKey))
|
||||
{
|
||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
||||
}
|
||||
|
||||
if (set != null && value != null)
|
||||
{
|
||||
switch (set)
|
||||
{
|
||||
case "externalReads":
|
||||
if (request.QueryString["value"] == "true")
|
||||
{
|
||||
worldDB.SetWorldAllowExternalRead(worldId, true);
|
||||
}
|
||||
else if (request.QueryString["value"] == "false")
|
||||
{
|
||||
worldDB.SetWorldAllowExternalRead(worldId, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ConstructErrorResponse(400, "Invalid value for 'externalReads' setting.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ConstructErrorResponse(400, "Invalid setting name.");
|
||||
}
|
||||
}
|
||||
|
||||
return ConstructSuccessResponse(null, connectionKey);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handles an HTTP listener request to initialize a connection to the world db manager.
|
||||
/// </summary>
|
||||
@@ -122,41 +170,24 @@ namespace VRCX
|
||||
|
||||
if (request.QueryString["debug"] == "true")
|
||||
{
|
||||
if (!worldDB.DoesWorldExist("wrld_12345"))
|
||||
{
|
||||
worldDB.AddWorld("wrld_12345", "12345");
|
||||
worldDB.AddDataEntry("wrld_12345", "test", "testvalue");
|
||||
}
|
||||
|
||||
currentWorldId = "wrld_12345";
|
||||
|
||||
return ConstructSuccessResponse("12345");
|
||||
debugWorld = true;
|
||||
}
|
||||
else if (request.QueryString["debug"] == "false")
|
||||
{
|
||||
debugWorld = false;
|
||||
}
|
||||
|
||||
string worldId = await GetCurrentWorldID();
|
||||
|
||||
if (String.IsNullOrEmpty(worldId))
|
||||
if (TryInitializeWorld(worldId, out string connectionKey))
|
||||
{
|
||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
||||
}
|
||||
|
||||
currentWorldId = worldId;
|
||||
|
||||
var existsInDB = worldDB.DoesWorldExist(currentWorldId);
|
||||
string connectionKey;
|
||||
|
||||
if (!existsInDB)
|
||||
{
|
||||
connectionKey = GenerateWorldConnectionKey();
|
||||
worldDB.AddWorld(currentWorldId, connectionKey);
|
||||
logger.Info("Initialized a connection to the world database for world ID '{0}' with connection key {1}.", worldId, connectionKey);
|
||||
return ConstructSuccessResponse(connectionKey, connectionKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionKey = worldDB.GetWorldConnectionKey(currentWorldId);
|
||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
||||
}
|
||||
|
||||
logger.Info("Initialized connection to world ID '{0}' with connection key '{1}'.", currentWorldId, connectionKey);
|
||||
return ConstructSuccessResponse(connectionKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -174,38 +205,18 @@ namespace VRCX
|
||||
return ConstructErrorResponse(400, "Missing key parameter.");
|
||||
}
|
||||
|
||||
var worldIdOverride = request.QueryString["world"];
|
||||
var worldId = await GetCurrentWorldID();
|
||||
|
||||
if (worldIdOverride != null)
|
||||
if (!TryInitializeWorld(worldId, out string connectionKey))
|
||||
{
|
||||
var world = worldDB.GetWorld(worldIdOverride);
|
||||
|
||||
if (world == null)
|
||||
{
|
||||
return ConstructErrorResponse(200, $"World ID '{worldIdOverride}' not initialized in this user's database.");
|
||||
}
|
||||
|
||||
if (!world.AllowExternalRead)
|
||||
{
|
||||
return ConstructErrorResponse(200, $"World ID '{worldIdOverride}' does not allow external reads.");
|
||||
}
|
||||
}
|
||||
|
||||
if (currentWorldId == "wrld_12345" && worldIdOverride == null)
|
||||
worldIdOverride = "wrld_12345";
|
||||
|
||||
var worldId = worldIdOverride ?? await GetCurrentWorldID();
|
||||
|
||||
if (worldIdOverride == null && (String.IsNullOrEmpty(currentWorldId) || worldId != currentWorldId))
|
||||
{
|
||||
return ConstructErrorResponse(400, "World ID not initialized.");
|
||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
||||
}
|
||||
|
||||
var value = worldDB.GetDataEntry(worldId, key);
|
||||
|
||||
logger.Debug("Serving a request for data with key '{0}' from world ID '{1}'.", key, worldId);
|
||||
// This is intended to be null if the key doesn't exist.
|
||||
return ConstructSuccessResponse(value?.Value);
|
||||
logger.Debug("Serving a request for data with key '{0}' from world ID '{1}' with connection key {2}.", key, worldId, connectionKey);
|
||||
// This value is intended to be null if the key doesn't exist.
|
||||
return ConstructSuccessResponse(value?.Value, connectionKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -217,45 +228,24 @@ namespace VRCX
|
||||
{
|
||||
var request = context.Request;
|
||||
|
||||
var worldIdOverride = request.QueryString["world"];
|
||||
|
||||
if (worldIdOverride != null)
|
||||
var worldId = await GetCurrentWorldID();
|
||||
|
||||
if (!TryInitializeWorld(worldId, out string connectionKey))
|
||||
{
|
||||
var world = worldDB.GetWorld(worldIdOverride);
|
||||
|
||||
if (world == null)
|
||||
{
|
||||
return ConstructErrorResponse(200, $"World ID '{worldIdOverride}' not initialized in this user's database.");
|
||||
}
|
||||
|
||||
if (!world.AllowExternalRead)
|
||||
{
|
||||
return ConstructErrorResponse(200, $"World ID '{worldIdOverride}' does not allow external reads.");
|
||||
}
|
||||
}
|
||||
|
||||
if (currentWorldId == "wrld_12345" && worldIdOverride == null)
|
||||
worldIdOverride = "wrld_12345";
|
||||
|
||||
var worldId = worldIdOverride ?? await GetCurrentWorldID();
|
||||
|
||||
if (worldIdOverride == null && (String.IsNullOrEmpty(currentWorldId) || worldId != currentWorldId))
|
||||
{
|
||||
return ConstructErrorResponse(400, "World ID not initialized.");
|
||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
||||
}
|
||||
|
||||
var entries = worldDB.GetAllDataEntries(worldId);
|
||||
|
||||
logger.Debug("Serving a request for all data from world ID '{0}'.", worldId);
|
||||
|
||||
var data = new Dictionary<string, string>();
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
data.Add(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
// This is intended to be null if the key doesn't exist.
|
||||
return ConstructSuccessResponse(JsonConvert.SerializeObject(data));
|
||||
logger.Debug("Serving a request for all data ({0} entries) for world ID '{1}' with connection key {2}.", data.Count, worldId, connectionKey);
|
||||
return ConstructSuccessResponse(JsonConvert.SerializeObject(data), connectionKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -277,19 +267,12 @@ namespace VRCX
|
||||
|
||||
var worldId = await GetCurrentWorldID();
|
||||
|
||||
if (String.IsNullOrEmpty(currentWorldId) || (worldId != currentWorldId && currentWorldId != "wrld_12345"))
|
||||
if (!TryInitializeWorld(worldId, out string connectionKey))
|
||||
{
|
||||
return ConstructErrorResponse(400, "World ID not initialized.");
|
||||
return ConstructErrorResponse(500, "Failed to get/verify current world ID.");
|
||||
}
|
||||
|
||||
var values = worldDB.GetDataEntries(currentWorldId, keyArray).ToList();
|
||||
|
||||
/*if (values == null)
|
||||
{
|
||||
responseData.Error = $"No data found for keys '{keys}' under world id '{currentWorldId}'.";
|
||||
responseData.StatusCode = 404;
|
||||
return responseData;
|
||||
}*/
|
||||
var values = worldDB.GetDataEntries(worldId, keyArray).ToList();
|
||||
|
||||
// Build a dictionary of key/value pairs to send back. If a key doesn't exist in the database, the key will be included in the response as requested but with a null value.
|
||||
var data = new Dictionary<string, string>();
|
||||
@@ -301,8 +284,32 @@ namespace VRCX
|
||||
data.Add(dataKey, dataValue);
|
||||
}
|
||||
|
||||
logger.Debug("Serving a request for bulk data with keys '{0}' from world ID '{1}'.", keys, currentWorldId);
|
||||
return ConstructSuccessResponse(JsonConvert.SerializeObject(data));
|
||||
logger.Debug("Serving a request for bulk data with keys '{0}' from world ID '{1}' with connection key {2}.", keys, worldId, connectionKey);
|
||||
return ConstructSuccessResponse(JsonConvert.SerializeObject(data), connectionKey);
|
||||
}
|
||||
|
||||
private bool TryInitializeWorld(string worldId, out string connectionKey)
|
||||
{
|
||||
if (String.IsNullOrEmpty(worldId))
|
||||
{
|
||||
connectionKey = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var existsInDB = worldDB.DoesWorldExist(worldId);
|
||||
|
||||
if (!existsInDB)
|
||||
{
|
||||
connectionKey = GenerateWorldConnectionKey();
|
||||
worldDB.AddWorld(worldId, connectionKey);
|
||||
logger.Info("Added new world ID '{0}' with connection key '{1}' to the database.", worldId, connectionKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionKey = worldDB.GetWorldConnectionKey(worldId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -311,6 +318,8 @@ namespace VRCX
|
||||
/// <returns>A string representation of a GUID that can be used to identify the world on requests.</returns>
|
||||
private string GenerateWorldConnectionKey()
|
||||
{
|
||||
if (debugWorld) return "12345";
|
||||
|
||||
// Ditched the old method of generating a short key, since we're just going with json anyway who cares about a longer identifier
|
||||
// Since we can rely on this GUID being unique, we can use it to identify the world on requests instead of trying to keep track of the user's current world.
|
||||
// I uhh, should probably make sure this is actually unique though. Just in case. I'll do that later.
|
||||
@@ -323,6 +332,8 @@ namespace VRCX
|
||||
/// <returns>The ID of the current world as a string, or null if it could not be retrieved.</returns>
|
||||
private async Task<string> GetCurrentWorldID()
|
||||
{
|
||||
if (debugWorld) return "wrld_12345";
|
||||
|
||||
JavascriptResponse funcResult;
|
||||
|
||||
try
|
||||
@@ -349,7 +360,7 @@ namespace VRCX
|
||||
return worldId;
|
||||
}
|
||||
|
||||
private WorldDataRequestResponse ConstructSuccessResponse(string data = null)
|
||||
private WorldDataRequestResponse ConstructSuccessResponse(string data = null, string connectionKey = null)
|
||||
{
|
||||
var responseData = new WorldDataRequestResponse(true, null, null);
|
||||
|
||||
@@ -357,6 +368,7 @@ namespace VRCX
|
||||
responseData.Error = null;
|
||||
responseData.OK = true;
|
||||
responseData.Data = data;
|
||||
responseData.ConnectionKey = connectionKey;
|
||||
return responseData;
|
||||
}
|
||||
|
||||
@@ -368,6 +380,7 @@ namespace VRCX
|
||||
responseData.Error = error;
|
||||
responseData.OK = false;
|
||||
responseData.Data = null;
|
||||
responseData.ConnectionKey = null;
|
||||
|
||||
return responseData;
|
||||
}
|
||||
@@ -433,31 +446,17 @@ namespace VRCX
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(request.Key))
|
||||
if (String.IsNullOrEmpty(request.RequestType))
|
||||
{
|
||||
logger.Warn("World {0} tried to store data with no key provided", request.Key);
|
||||
this.lastError = "`key` is missing or null";
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(request.Value))
|
||||
{
|
||||
logger.Warn("World {0} tried to store data with no value provided", request.Key);
|
||||
this.lastError = "`value` is missing or null";
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(request.ConnectionKey))
|
||||
{
|
||||
logger.Warn("World {0} tried to store data with no connection key provided", request.Key);
|
||||
this.lastError = "`connectionKey` is missing or null";
|
||||
logger.Warn("World tried to store data with no request type provided. Request: ", json);
|
||||
this.lastError = "`requestType` is missing or null";
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the connection key is a valid GUID. No point in doing anything else if it's not.
|
||||
if (!Guid.TryParse(request.ConnectionKey, out Guid _))
|
||||
if (!debugWorld && !Guid.TryParse(request.ConnectionKey, out Guid _))
|
||||
{
|
||||
logger.Warn("World {0} tried to store data with an invalid GUID as a connection key {1}", request.Key, request.ConnectionKey);
|
||||
logger.Warn("World tried to store data with an invalid GUID as a connection key '{0}'", request.ConnectionKey);
|
||||
this.lastError = "Invalid GUID provided as connection key";
|
||||
// invalid guid
|
||||
return;
|
||||
@@ -465,14 +464,29 @@ namespace VRCX
|
||||
|
||||
// Get the world ID from the connection key
|
||||
string worldId = worldDB.GetWorldByConnectionKey(request.ConnectionKey);
|
||||
|
||||
if (worldId == null)
|
||||
{
|
||||
logger.Warn("World {0} tried to store data with invalid connection key {1}", request.Key, request.ConnectionKey);
|
||||
logger.Warn("World tried to store data under {0} with an invalid connection key {1}", request.Key, request.ConnectionKey);
|
||||
this.lastError = "Invalid connection key";
|
||||
// invalid connection key
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(request.Key))
|
||||
{
|
||||
logger.Warn("World {0} tried to store data with no key provided", worldId);
|
||||
this.lastError = "`key` is missing or null";
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(request.Value))
|
||||
{
|
||||
logger.Warn("World {0} tried to store data under key {1} with no value provided", worldId, request.Key);
|
||||
this.lastError = "`value` is missing or null";
|
||||
return;
|
||||
}
|
||||
|
||||
// Get/calculate the old and new data sizes for this key/the world
|
||||
int oldTotalDataSize = worldDB.GetWorldDataSize(worldId);
|
||||
int oldDataSize = worldDB.GetDataEntrySize(worldId, request.Key);
|
||||
|
||||
Reference in New Issue
Block a user