1036 lines
29 KiB
C#
1036 lines
29 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Reflection;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using JetBrains.Annotations;
|
|
using Newtonsoft.Json;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
using VelNet;
|
|
|
|
namespace VELConnect
|
|
{
|
|
// ReSharper disable once InconsistentNaming
|
|
public class VELConnectManager : MonoBehaviour
|
|
{
|
|
public string velConnectUrl = "http://localhost";
|
|
|
|
public static string VelConnectUrl
|
|
{
|
|
get => instance.velConnectUrl;
|
|
set
|
|
{
|
|
instance.velConnectUrl = value;
|
|
SetDeviceField(new Dictionary<DeviceField, string>
|
|
{
|
|
{ DeviceField.os_info, SystemInfo.operatingSystem },
|
|
{ DeviceField.friendly_name, SystemInfo.deviceName },
|
|
{ DeviceField.current_app, Application.productName },
|
|
{ DeviceField.pairing_code, PairingCode },
|
|
});
|
|
}
|
|
}
|
|
|
|
private static VELConnectManager instance;
|
|
|
|
public class State
|
|
{
|
|
public Device device;
|
|
public DataBlock room;
|
|
public User user;
|
|
|
|
|
|
public class User
|
|
{
|
|
public string id;
|
|
public string username;
|
|
public string created;
|
|
public string updated;
|
|
}
|
|
|
|
public class Device
|
|
{
|
|
[CanBeNull] public readonly string id;
|
|
[CanBeNull] public string created = null;
|
|
[CanBeNull] public string updated = null;
|
|
[CanBeNull] public string os_info;
|
|
[CanBeNull] public string friendly_name;
|
|
[CanBeNull] public string modified_by;
|
|
[CanBeNull] public string current_app;
|
|
[CanBeNull] public string current_room;
|
|
[CanBeNull] public string pairing_code;
|
|
[CanBeNull] public string last_online;
|
|
[CanBeNull] public string owner;
|
|
[CanBeNull] public string[] past_owners;
|
|
[CanBeNull] public DeviceExpand expand;
|
|
public DataBlock userData => expand?.data;
|
|
|
|
public class DeviceExpand
|
|
{
|
|
public DataBlock data;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Returns the value if it exists, otherwise null
|
|
/// </summary>
|
|
public string TryGetData(string key)
|
|
{
|
|
return userData.TryGetData(key);
|
|
}
|
|
}
|
|
|
|
public class DataBlock
|
|
{
|
|
public string id;
|
|
public readonly DateTime created;
|
|
public readonly DateTime updated;
|
|
public string block_id;
|
|
public string owner;
|
|
public string category;
|
|
public string modified_by;
|
|
public Dictionary<string, string> data;
|
|
|
|
/// <summary>
|
|
/// Returns the value if it exists, otherwise null
|
|
/// </summary>
|
|
public string TryGetData(string key)
|
|
{
|
|
string val = null;
|
|
return data?.TryGetValue(key, out val) == true ? val : null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public class PersistObject
|
|
{
|
|
public string id;
|
|
public readonly DateTime created;
|
|
public readonly DateTime updated;
|
|
public string app;
|
|
public string room;
|
|
public string network_id;
|
|
public bool spawned;
|
|
public string name;
|
|
public string data;
|
|
}
|
|
|
|
public class RecordList<T>
|
|
{
|
|
public int page;
|
|
public int perPage;
|
|
public int totalPages;
|
|
public int totalItems;
|
|
public List<T> items;
|
|
}
|
|
|
|
public class ComponentState
|
|
{
|
|
public int componentIdx;
|
|
public string state;
|
|
}
|
|
|
|
public class UserCount
|
|
{
|
|
[CanBeNull] public readonly string id;
|
|
public readonly DateTime? created;
|
|
public readonly DateTime? updated;
|
|
public string device_id;
|
|
public string app_id;
|
|
public string room_id;
|
|
public int total_users;
|
|
public int room_users;
|
|
public string version;
|
|
public string platform;
|
|
}
|
|
|
|
public enum DeviceField
|
|
{
|
|
id,
|
|
os_info,
|
|
friendly_name,
|
|
modified_by,
|
|
current_app,
|
|
current_room,
|
|
pairing_code,
|
|
last_online,
|
|
data,
|
|
owner,
|
|
past_owners
|
|
}
|
|
|
|
private State lastState;
|
|
private State state;
|
|
|
|
[CanBeNull] public static State CurrentState => instance == null ? null : instance.state;
|
|
|
|
public static Action<State> OnInitialState;
|
|
public static Action<DeviceField, string> OnDeviceFieldChanged;
|
|
public static Action<string, object> OnUserDataChanged;
|
|
public static Action<string, object> OnRoomDataChanged;
|
|
|
|
private static readonly Dictionary<DeviceField, List<CallbackListener>> deviceFieldCallbacks =
|
|
new Dictionary<DeviceField, List<CallbackListener>>();
|
|
|
|
private static readonly Dictionary<string, List<CallbackListener>> userDataCallbacks =
|
|
new Dictionary<string, List<CallbackListener>>();
|
|
|
|
private static readonly Dictionary<string, List<CallbackListener>> roomDataCallbacks =
|
|
new Dictionary<string, List<CallbackListener>>();
|
|
|
|
private struct CallbackListener
|
|
{
|
|
/// <summary>
|
|
/// Used so that other objects don't have to remove listeners themselves
|
|
/// </summary>
|
|
public MonoBehaviour keepAliveObject;
|
|
|
|
public Action<string> callback;
|
|
|
|
/// <summary>
|
|
/// Sends the first state received from the network or the state at binding time
|
|
/// </summary>
|
|
public bool sendInitialState;
|
|
}
|
|
|
|
public static string PairingCode
|
|
{
|
|
get
|
|
{
|
|
Hash128 hash = new Hash128();
|
|
hash.Append(deviceId);
|
|
// change once a day
|
|
hash.Append(DateTime.UtcNow.DayOfYear);
|
|
// between 1000 and 9999 inclusive (any 4 digit number)
|
|
return (Math.Abs(hash.GetHashCode()) % 9000 + 1000).ToString();
|
|
}
|
|
}
|
|
|
|
private static string deviceId;
|
|
|
|
private void Awake()
|
|
{
|
|
velConnectUrl = velConnectUrl.TrimEnd('/');
|
|
if (instance != null) Debug.LogError("VELConnectManager instance already exists", this);
|
|
instance = this;
|
|
deviceId = CreateDeviceId();
|
|
VelNetManager.OnLocalNetworkObjectSpawned += networkObject =>
|
|
{
|
|
if (!networkObject.ownershipLocked)
|
|
{
|
|
// TODO
|
|
// SetRoomData("spawned_" + networkObject.networkId, networkObject.prefabName);
|
|
}
|
|
};
|
|
}
|
|
|
|
// Computes 15-char device id compatibly with pocketbase
|
|
private static string CreateDeviceId()
|
|
{
|
|
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
|
|
StringBuilder sb = new StringBuilder(SystemInfo.deviceUniqueIdentifier);
|
|
sb.Append(Application.productName);
|
|
#if UNITY_EDITOR
|
|
// allows running multiple builds on the same computer
|
|
// return SystemInfo.deviceUniqueIdentifier + Hash128.Compute(Application.dataPath);
|
|
sb.Append(Application.dataPath);
|
|
sb.Append("EDITOR");
|
|
#endif
|
|
string id = Convert.ToBase64String(md5.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString())));
|
|
return id[..15];
|
|
}
|
|
|
|
// Start is called before the first frame update
|
|
private void Start()
|
|
{
|
|
SetDeviceField(new Dictionary<DeviceField, string>
|
|
{
|
|
{ DeviceField.os_info, SystemInfo.operatingSystem },
|
|
{ DeviceField.friendly_name, SystemInfo.deviceName },
|
|
{ DeviceField.current_app, Application.productName },
|
|
{ DeviceField.pairing_code, PairingCode },
|
|
});
|
|
|
|
UpdateUserCount();
|
|
|
|
StartCoroutine(SlowLoop());
|
|
|
|
VelNetManager.OnJoinedRoom += room =>
|
|
{
|
|
SetDeviceField(new Dictionary<DeviceField, string>
|
|
{
|
|
{ DeviceField.current_app, Application.productName },
|
|
{ DeviceField.current_room, room },
|
|
});
|
|
};
|
|
}
|
|
|
|
private void UpdateUserCount(bool leaving = false)
|
|
{
|
|
if (!VelNetManager.InRoom) return;
|
|
|
|
VelNetManager.GetRooms(rooms =>
|
|
{
|
|
UserCount postData = new UserCount
|
|
{
|
|
device_id = deviceId,
|
|
app_id = Application.productName,
|
|
room_id = VelNetManager.Room ?? "",
|
|
total_users = rooms.rooms.Sum(r => r.numUsers) - (leaving ? 1 : 0),
|
|
room_users = VelNetManager.PlayerCount - (leaving ? 1 : 0),
|
|
version = Application.version,
|
|
platform = SystemInfo.operatingSystem,
|
|
};
|
|
PostRequestCallback(velConnectUrl + "/api/collections/UserCount/records", JsonConvert.SerializeObject(
|
|
postData, Formatting.None, new JsonSerializerSettings
|
|
{
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
}));
|
|
});
|
|
}
|
|
|
|
private IEnumerator SlowLoop()
|
|
{
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
GetRequestCallback(velConnectUrl + "/state/" + deviceId, json =>
|
|
{
|
|
state = JsonConvert.DeserializeObject<State>(json);
|
|
if (state == null) return;
|
|
|
|
bool isInitialState = lastState == null;
|
|
|
|
// if (state.device.modified_by != DeviceId)
|
|
{
|
|
FieldInfo[] fields = state.device.GetType().GetFields();
|
|
|
|
// loop through all the fields in the device
|
|
foreach (FieldInfo fieldInfo in fields)
|
|
{
|
|
string newValue = fieldInfo.GetValue(state.device) as string;
|
|
string oldValue = lastState != null
|
|
? fieldInfo.GetValue(lastState.device) as string
|
|
: null;
|
|
|
|
DeviceField fieldName;
|
|
if (Enum.TryParse(fieldInfo.Name, out fieldName))
|
|
{
|
|
if (newValue != oldValue)
|
|
{
|
|
try
|
|
{
|
|
if (!isInitialState) OnDeviceFieldChanged?.Invoke(fieldName, newValue);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError(e);
|
|
}
|
|
|
|
// send specific listeners data
|
|
if (deviceFieldCallbacks.ContainsKey(fieldName))
|
|
{
|
|
// clear the list of old listeners
|
|
deviceFieldCallbacks[fieldName].RemoveAll(e => e.keepAliveObject == null);
|
|
|
|
// send the callbacks
|
|
foreach (CallbackListener e in deviceFieldCallbacks[fieldName])
|
|
{
|
|
if (!isInitialState || e.sendInitialState)
|
|
{
|
|
try
|
|
{
|
|
e.callback(newValue);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError(ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (state.device.userData.data != null)
|
|
{
|
|
foreach (KeyValuePair<string, string> elem in state.device.userData.data)
|
|
{
|
|
string oldValue = null;
|
|
lastState?.device?.userData?.data?.TryGetValue(elem.Key, out oldValue);
|
|
if (elem.Value != oldValue)
|
|
{
|
|
try
|
|
{
|
|
if (!isInitialState) OnUserDataChanged?.Invoke(elem.Key, elem.Value);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError(ex);
|
|
}
|
|
|
|
// send specific listeners data
|
|
if (userDataCallbacks.ContainsKey(elem.Key))
|
|
{
|
|
// clear the list of old listeners
|
|
userDataCallbacks[elem.Key].RemoveAll(e => e.keepAliveObject == null);
|
|
|
|
// send the callbacks
|
|
foreach (CallbackListener e in userDataCallbacks[elem.Key])
|
|
{
|
|
if (!isInitialState || e.sendInitialState)
|
|
{
|
|
try
|
|
{
|
|
e.callback(elem.Value);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError(ex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// on the initial state, also activate callbacks for null values
|
|
if (isInitialState)
|
|
{
|
|
foreach ((string userDataKey, List<CallbackListener> callbackList) in
|
|
userDataCallbacks)
|
|
{
|
|
if (!state.device.userData.data.ContainsKey(userDataKey))
|
|
{
|
|
// send the callbacks
|
|
callbackList.ForEach(e =>
|
|
{
|
|
if (e.sendInitialState)
|
|
{
|
|
try
|
|
{
|
|
e.callback(null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError(ex);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// if (state.room.modified_by != DeviceId && state.room.data != null)
|
|
if (state.room?.data != null)
|
|
{
|
|
foreach (KeyValuePair<string, string> elem in state.room.data)
|
|
{
|
|
string oldValue = null;
|
|
lastState?.room?.data.TryGetValue(elem.Key, out oldValue);
|
|
if (elem.Value != oldValue)
|
|
{
|
|
try
|
|
{
|
|
if (!isInitialState) OnRoomDataChanged?.Invoke(elem.Key, elem.Value);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError(e);
|
|
}
|
|
|
|
// send specific listeners data
|
|
if (roomDataCallbacks.ContainsKey(elem.Key))
|
|
{
|
|
// clear the list of old listeners
|
|
roomDataCallbacks[elem.Key].RemoveAll(e => e.keepAliveObject == null);
|
|
|
|
// send the callbacks
|
|
roomDataCallbacks[elem.Key].ForEach(e =>
|
|
{
|
|
if (!isInitialState || e.sendInitialState)
|
|
{
|
|
try
|
|
{
|
|
e.callback(elem.Value);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError(ex);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// on the initial state, also activate callbacks for null values
|
|
if (isInitialState)
|
|
{
|
|
foreach ((string key, List<CallbackListener> callbackList) in roomDataCallbacks)
|
|
{
|
|
if (!state.room.data.ContainsKey(key))
|
|
{
|
|
// send the callbacks
|
|
callbackList.ForEach(e =>
|
|
{
|
|
if (e.sendInitialState)
|
|
{
|
|
try
|
|
{
|
|
e.callback(null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError(ex);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
lastState = state;
|
|
if (lastState?.device?.pairing_code == null)
|
|
{
|
|
Debug.LogError("Pairing code nulllll");
|
|
}
|
|
|
|
if (isInitialState)
|
|
{
|
|
try
|
|
{
|
|
OnInitialState?.Invoke(state);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError(e);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError(e);
|
|
// this make sure the coroutine never quits
|
|
}
|
|
|
|
yield return new WaitForSeconds(1);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a change listener callback to a particular field name within the Device main fields.
|
|
/// </summary>
|
|
public static void AddDeviceFieldListener(DeviceField key, MonoBehaviour keepAliveObject,
|
|
Action<string> callback,
|
|
bool sendInitialState = false)
|
|
{
|
|
if (!deviceFieldCallbacks.ContainsKey(key))
|
|
{
|
|
deviceFieldCallbacks[key] = new List<CallbackListener>();
|
|
}
|
|
|
|
deviceFieldCallbacks[key].Add(new CallbackListener()
|
|
{
|
|
keepAliveObject = keepAliveObject,
|
|
callback = callback,
|
|
sendInitialState = sendInitialState
|
|
});
|
|
|
|
if (sendInitialState)
|
|
{
|
|
if (instance != null && instance.lastState?.device != null)
|
|
{
|
|
if (instance.lastState.device.GetType().GetField(key.ToString())
|
|
?.GetValue(instance.lastState.device) is string val)
|
|
{
|
|
try
|
|
{
|
|
callback(val);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a change listener callback to a particular field name within the User data JSON.
|
|
/// If the initial state doesn't contain this key, this sends back null
|
|
/// </summary>
|
|
public static void AddUserDataListener(string key, MonoBehaviour keepAliveObject, Action<string> callback,
|
|
bool sendInitialState = false)
|
|
{
|
|
if (!userDataCallbacks.ContainsKey(key))
|
|
{
|
|
userDataCallbacks[key] = new List<CallbackListener>();
|
|
}
|
|
|
|
userDataCallbacks[key].Add(new CallbackListener()
|
|
{
|
|
keepAliveObject = keepAliveObject,
|
|
callback = callback,
|
|
sendInitialState = sendInitialState
|
|
});
|
|
|
|
// if we have already received data, and we should send right away
|
|
if (sendInitialState && instance.state != null)
|
|
{
|
|
string val = GetUserData(key);
|
|
try
|
|
{
|
|
callback(val);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a change listener callback to a particular field name within the Room data JSON.
|
|
/// If the initial state doesn't contain this key, this sends back null
|
|
/// </summary>
|
|
public static void AddRoomDataListener(string key, MonoBehaviour keepAliveObject, Action<string> callback,
|
|
bool sendInitialState = false)
|
|
{
|
|
if (!roomDataCallbacks.ContainsKey(key))
|
|
{
|
|
roomDataCallbacks[key] = new List<CallbackListener>();
|
|
}
|
|
|
|
roomDataCallbacks[key].Add(new CallbackListener()
|
|
{
|
|
keepAliveObject = keepAliveObject,
|
|
callback = callback,
|
|
sendInitialState = sendInitialState
|
|
});
|
|
|
|
// if we have already received data, and we should send right away
|
|
if (sendInitialState && instance.state != null)
|
|
{
|
|
string val = GetRoomData(key);
|
|
try
|
|
{
|
|
callback(val);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static string GetUserData(string key, string defaultValue = null)
|
|
{
|
|
return instance != null ? instance.lastState?.device?.TryGetData(key) : defaultValue;
|
|
}
|
|
|
|
public static string GetRoomData(string key, string defaultValue = null)
|
|
{
|
|
return instance != null ? instance.lastState?.room?.TryGetData(key) : defaultValue;
|
|
}
|
|
|
|
public static T GetRoomData<T>(string key, string defaultValue = null)
|
|
{
|
|
string value = instance != null ? instance.lastState?.room?.TryGetData(key) : defaultValue;
|
|
|
|
if (typeof(T) == typeof(int))
|
|
{
|
|
if (int.TryParse(value, out int result))
|
|
{
|
|
return (T)Convert.ChangeType(result, typeof(T));
|
|
}
|
|
|
|
return (T)(object)0;
|
|
}
|
|
else if (typeof(T) == typeof(double))
|
|
{
|
|
if (double.TryParse(value, out double result))
|
|
{
|
|
return (T)Convert.ChangeType(result, typeof(T));
|
|
}
|
|
|
|
return (T)(object)0.0;
|
|
}
|
|
else if (typeof(T) == typeof(float))
|
|
{
|
|
if (float.TryParse(value, out float result))
|
|
{
|
|
return (T)Convert.ChangeType(result, typeof(T));
|
|
}
|
|
|
|
return (T)(object)0f;
|
|
}
|
|
|
|
throw new NotSupportedException($"Conversion to type {typeof(T)} is not supported.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets data on the device keys themselves
|
|
/// These are fixed fields defined for every application
|
|
/// </summary>
|
|
public static void SetDeviceField(Dictionary<DeviceField, string> device)
|
|
{
|
|
device[DeviceField.last_online] = DateTime.UtcNow.ToLongDateString();
|
|
|
|
if (instance.state?.device != null)
|
|
{
|
|
// loop through all the fields in the device
|
|
foreach (DeviceField key in device.Keys.ToArray())
|
|
{
|
|
FieldInfo field = instance.state.device.GetType().GetField(key.ToString());
|
|
if ((string)field.GetValue(instance.state.device) != device[key])
|
|
{
|
|
if (instance.lastState?.device != null)
|
|
{
|
|
// update our local state, so we don't get change events on our own updates
|
|
field.SetValue(instance.lastState.device, device[key]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// don't send this field, since it's the same
|
|
device.Remove(key);
|
|
}
|
|
}
|
|
|
|
// last_online field always changes
|
|
if (device.Keys.Count <= 1)
|
|
{
|
|
// nothing changed, don't send
|
|
return;
|
|
}
|
|
}
|
|
|
|
PostRequestCallback(
|
|
instance.velConnectUrl + "/device/" + deviceId,
|
|
JsonConvert.SerializeObject(device)
|
|
);
|
|
}
|
|
|
|
public static void SetUserData(string key, string value)
|
|
{
|
|
SetUserData(new Dictionary<string, string> { { key, value } });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the 'data' object of the Device table
|
|
/// </summary>
|
|
public static void SetUserData(Dictionary<string, string> data)
|
|
{
|
|
if (instance.state?.device != null)
|
|
{
|
|
foreach (string key in data.Keys.ToList())
|
|
{
|
|
// if the value is unchanged from the current state, remove it so we don't double-update
|
|
if (instance.state.device.userData.data.TryGetValue(key, out string val) && val == data[key])
|
|
{
|
|
data.Remove(key);
|
|
}
|
|
else
|
|
{
|
|
// update our local state, so we don't get change events on our own updates
|
|
if (instance.lastState?.device?.userData?.data != null)
|
|
{
|
|
instance.lastState.device.userData.data[key] = data[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
// nothing was changed
|
|
if (data.Keys.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if we have no data, just set the whole thing
|
|
if (instance.lastState?.device?.userData != null) instance.lastState.device.userData.data ??= data;
|
|
}
|
|
|
|
|
|
Dictionary<string, object> device = new Dictionary<string, object>
|
|
{
|
|
{ "last_online", DateTime.UtcNow.ToLongDateString() },
|
|
{ "data", data },
|
|
};
|
|
|
|
PostRequestCallback(
|
|
instance.velConnectUrl + "/device/" + deviceId,
|
|
JsonConvert.SerializeObject(device, Formatting.None, new JsonSerializerSettings
|
|
{
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
})
|
|
);
|
|
}
|
|
|
|
public static void SetRoomData(string key, string value)
|
|
{
|
|
SetRoomData(new Dictionary<string, string> { { key, value } });
|
|
}
|
|
|
|
public static void SetRoomData(Dictionary<string, string> data)
|
|
{
|
|
if (!VelNetManager.InRoom)
|
|
{
|
|
Debug.LogError("Can't set data for a room if you're not in a room.");
|
|
return;
|
|
}
|
|
|
|
State.DataBlock room = new State.DataBlock
|
|
{
|
|
category = "room",
|
|
modified_by = "Unity",
|
|
data = data
|
|
};
|
|
|
|
// remove keys that already match our current state
|
|
if (instance.state?.room != null)
|
|
{
|
|
foreach (string key in data.Keys.ToArray())
|
|
{
|
|
instance.state.room.data.TryGetValue(key, out string currentValue);
|
|
if (currentValue == data[key])
|
|
{
|
|
data.Remove(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we have no changed values
|
|
if (data.Keys.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// update our local state, so we don't get change events on our own updates
|
|
if (instance.lastState?.room != null)
|
|
{
|
|
foreach (KeyValuePair<string, string> kvp in data)
|
|
{
|
|
instance.lastState.room.data[kvp.Key] = kvp.Value;
|
|
}
|
|
}
|
|
|
|
PostRequestCallback(
|
|
instance.velConnectUrl + "/data_block/" + Application.productName + "_" + VelNetManager.Room,
|
|
JsonConvert.SerializeObject(room, Formatting.None, new JsonSerializerSettings
|
|
{
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
})
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unpairs this device from the current user.
|
|
/// </summary>
|
|
public static void Unpair()
|
|
{
|
|
if (instance.state?.device != null)
|
|
{
|
|
PostRequestCallback(
|
|
instance.velConnectUrl + "/unpair",
|
|
JsonConvert.SerializeObject(new Dictionary<string, string>()
|
|
{
|
|
{ "device_id", instance.state.device.id },
|
|
{ "user_id", instance.state.user.id }
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
// TODO
|
|
public static void UploadFile(string fileName, byte[] fileData, Action<string> successCallback = null)
|
|
{
|
|
// MultipartFormDataContent requestContent = new MultipartFormDataContent();
|
|
// ByteArrayContent fileContent = new ByteArrayContent(fileData);
|
|
//
|
|
// requestContent.Add(fileContent, "file", fileName);
|
|
//
|
|
// Task.Run(async () =>
|
|
// {
|
|
// HttpResponseMessage r =
|
|
// await new HttpClient().PostAsync(_instance.velConnectUrl + "/api/upload_file", requestContent);
|
|
// string resp = await r.Content.ReadAsStringAsync();
|
|
// Dictionary<string, string> dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(resp);
|
|
// successCallback?.Invoke(dict["key"]);
|
|
// });
|
|
}
|
|
|
|
// TODO
|
|
public static void DownloadFile(string key, Action<byte[]> successCallback = null)
|
|
{
|
|
// _instance.StartCoroutine(_instance.DownloadFileCo(key, successCallback));
|
|
}
|
|
|
|
private IEnumerator DownloadFileCo(string key, Action<byte[]> successCallback = null)
|
|
{
|
|
UnityWebRequest www = new UnityWebRequest(velConnectUrl + "/api/download_file/" + key);
|
|
www.downloadHandler = new DownloadHandlerBuffer();
|
|
yield return www.SendWebRequest();
|
|
|
|
if (www.result != UnityWebRequest.Result.Success)
|
|
{
|
|
Debug.Log(www.error);
|
|
}
|
|
else
|
|
{
|
|
// Show results as text
|
|
Debug.Log(www.downloadHandler.text);
|
|
|
|
// Or retrieve results as binary data
|
|
byte[] results = www.downloadHandler.data;
|
|
|
|
successCallback?.Invoke(results);
|
|
}
|
|
}
|
|
|
|
public static void GetRequestCallback(string url, Action<string> successCallback = null,
|
|
Action<string> failureCallback = null)
|
|
{
|
|
instance.StartCoroutine(instance.GetRequestCallbackCo(url, successCallback, failureCallback));
|
|
}
|
|
|
|
private IEnumerator GetRequestCallbackCo(string url, Action<string> successCallback = null,
|
|
Action<string> failureCallback = null)
|
|
{
|
|
using UnityWebRequest webRequest = UnityWebRequest.Get(url);
|
|
// Request and wait for the desired page.
|
|
yield return webRequest.SendWebRequest();
|
|
|
|
switch (webRequest.result)
|
|
{
|
|
case UnityWebRequest.Result.ConnectionError:
|
|
case UnityWebRequest.Result.DataProcessingError:
|
|
case UnityWebRequest.Result.ProtocolError:
|
|
Debug.LogError(url + ": Error: " + webRequest.error + "\n" + Environment.StackTrace);
|
|
failureCallback?.Invoke(webRequest.error);
|
|
break;
|
|
case UnityWebRequest.Result.Success:
|
|
successCallback?.Invoke(webRequest.downloadHandler.text);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void PostRequestCallback(
|
|
string url,
|
|
string postData,
|
|
Dictionary<string, string> headers = null,
|
|
Action<string> successCallback = null,
|
|
Action<string> failureCallback = null,
|
|
string method = "POST"
|
|
)
|
|
{
|
|
instance.StartCoroutine(PostRequestCallbackCo(url, postData, headers, successCallback, failureCallback, method));
|
|
}
|
|
|
|
|
|
private static IEnumerator PostRequestCallbackCo(string url, string postData,
|
|
Dictionary<string, string> headers = null, Action<string> successCallback = null,
|
|
Action<string> failureCallback = null, string method = "POST")
|
|
{
|
|
UnityWebRequest webRequest = new UnityWebRequest(url, method);
|
|
UploadHandlerRaw uploadHandler = null;
|
|
if (!string.IsNullOrEmpty(postData))
|
|
{
|
|
byte[] bodyRaw = Encoding.UTF8.GetBytes(postData);
|
|
uploadHandler = new UploadHandlerRaw(bodyRaw);
|
|
webRequest.uploadHandler = uploadHandler;
|
|
}
|
|
|
|
webRequest.downloadHandler = new DownloadHandlerBuffer();
|
|
webRequest.SetRequestHeader("Content-Type", "application/json");
|
|
if (headers != null)
|
|
{
|
|
foreach (KeyValuePair<string, string> keyValuePair in headers)
|
|
{
|
|
webRequest.SetRequestHeader(keyValuePair.Key, keyValuePair.Value);
|
|
}
|
|
}
|
|
|
|
yield return webRequest.SendWebRequest();
|
|
|
|
switch (webRequest.result)
|
|
{
|
|
case UnityWebRequest.Result.ConnectionError:
|
|
case UnityWebRequest.Result.DataProcessingError:
|
|
case UnityWebRequest.Result.ProtocolError:
|
|
Debug.LogWarning(url + ": Error: " + webRequest.error + "\n" + webRequest.downloadHandler.text + "\n" + Environment.StackTrace);
|
|
failureCallback?.Invoke(webRequest.error);
|
|
break;
|
|
case UnityWebRequest.Result.Success:
|
|
successCallback?.Invoke(webRequest.downloadHandler.text);
|
|
break;
|
|
}
|
|
|
|
uploadHandler?.Dispose();
|
|
webRequest.Dispose();
|
|
}
|
|
|
|
public static void SetDataBlock(State.DataBlock dataBlock, Action<State.DataBlock> successCallback = null)
|
|
{
|
|
PostRequestCallback(instance.velConnectUrl + "/api/collections/DataBlock/records", JsonConvert.SerializeObject(dataBlock, Formatting.None,
|
|
new JsonSerializerSettings
|
|
{
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
}), null, s =>
|
|
{
|
|
if (successCallback != null)
|
|
{
|
|
State.DataBlock resp = JsonConvert.DeserializeObject<State.DataBlock>(s);
|
|
successCallback?.Invoke(resp);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Setting with a block ID will update the existing block, otherwise it will create a new one
|
|
/// </summary>
|
|
/// <param name="blockId"></param>
|
|
/// <param name="dataBlock"></param>
|
|
/// <param name="successCallback"></param>
|
|
public static void SetDataBlock([CanBeNull] string blockId, State.DataBlock dataBlock, Action<State.DataBlock> successCallback = null)
|
|
{
|
|
PostRequestCallback(instance.velConnectUrl + "/data_block/" + blockId, JsonConvert.SerializeObject(dataBlock, Formatting.None, new JsonSerializerSettings
|
|
{
|
|
NullValueHandling = NullValueHandling.Ignore
|
|
}), null, s =>
|
|
{
|
|
if (successCallback != null)
|
|
{
|
|
State.DataBlock resp = JsonConvert.DeserializeObject<State.DataBlock>(s);
|
|
successCallback?.Invoke(resp);
|
|
}
|
|
});
|
|
}
|
|
|
|
public static void GetDataBlock(string blockId, Action<State.DataBlock> successCallback = null, Action<string> failureCallback = null)
|
|
{
|
|
GetRequestCallback(instance.velConnectUrl + "/data_block/" + blockId, data =>
|
|
{
|
|
State.DataBlock dict = JsonConvert.DeserializeObject<State.DataBlock>(data);
|
|
successCallback?.Invoke(dict);
|
|
}, failureCallback);
|
|
}
|
|
|
|
private void OnApplicationFocus(bool focus)
|
|
{
|
|
UpdateUserCount(!focus);
|
|
}
|
|
}
|
|
} |