diff --git a/unity_package/Runtime/VELConnectManager.cs b/unity_package/Runtime/VELConnectManager.cs index 3c7e3e6..f6027ac 100644 --- a/unity_package/Runtime/VELConnectManager.cs +++ b/unity_package/Runtime/VELConnectManager.cs @@ -1,574 +1,599 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using Newtonsoft.Json; -using UnityEngine; -using UnityEngine.Networking; -using VelNet; - -namespace VELConnect -{ - public class VELConnectManager : MonoBehaviour - { - public string velConnectUrl = "http://localhost"; - private static VELConnectManager instance; - - public class State - { - public class Device - { - public string hw_id; - public string os_info; - public string friendly_name; - public string modified_by; - public string current_app; - public string current_room; - public int pairing_code; - public string date_created; - public string last_modified; - public Dictionary data; - - /// - /// Returns the value if it exists, otherwise null - /// - public string TryGetData(string key) - { - string val = null; - return data?.TryGetValue(key, out val) == true ? val : null; - } - } - - public class RoomState - { - public string error; - public string id; - public string category; - public string date_created; - public string modified_by; - public string last_modified; - public string last_accessed; - public Dictionary data; - - /// - /// Returns the value if it exists, otherwise null - /// - public string TryGetData(string key) - { - string val = null; - return data?.TryGetValue(key, out val) == true ? val : null; - } - } - - public Device device; - public RoomState room; - } - - public State lastState; - - public static Action OnInitialState; - public static Action OnDeviceFieldChanged; - public static Action OnDeviceDataChanged; - public static Action OnRoomDataChanged; - - private static readonly Dictionary> deviceFieldCallbacks = new Dictionary>(); - private static readonly Dictionary> deviceDataCallbacks = new Dictionary>(); - private static readonly Dictionary> roomDataCallbacks = new Dictionary>(); - - private struct CallbackListener - { - /// - /// Used so that other objects don't have to remove listeners themselves - /// - public MonoBehaviour keepAliveObject; - - public Action callback; - - /// - /// Sends the first state received from the network or the state at binding time - /// - public bool sendInitialState; - } - - public static int 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; - } - } - - private static string DeviceId - { - get - { -#if UNITY_EDITOR - // allows running multiple builds on the same computer - // return SystemInfo.deviceUniqueIdentifier + Hash128.Compute(Application.dataPath); - return SystemInfo.deviceUniqueIdentifier + "_EDITOR"; -#else - return SystemInfo.deviceUniqueIdentifier; -#endif - } - } - - private void Awake() - { - if (instance != null) Debug.LogError("VELConnectManager instance already exists", this); - instance = this; - } - - // Start is called before the first frame update - private void Start() - { - SetDeviceField(new Dictionary - { - { "current_app", Application.productName }, - { "pairing_code", PairingCode } - }); - - UpdateUserCount(); - - - StartCoroutine(SlowLoop()); - - VelNetManager.OnJoinedRoom += room => - { - SetDeviceField(new Dictionary - { - { "current_app", Application.productName }, - { "current_room", room }, - }); - }; - } - - - private void UpdateUserCount(bool leaving = false) - { - if (!VelNetManager.InRoom) return; - - VelNetManager.GetRooms(rooms => - { - Dictionary postData = new Dictionary - { - { "hw_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/v2/update_user_count", JsonConvert.SerializeObject(postData)); - }); - } - - private IEnumerator SlowLoop() - { - while (true) - { - try - { - GetRequestCallback(velConnectUrl + "/api/v2/device/get_data/" + DeviceId, json => - { - State state = JsonConvert.DeserializeObject(json); - if (state == null) return; - - bool isInitialState = false; - - // first load stuff - if (lastState == null) - { - try - { - OnInitialState?.Invoke(state); - } - catch (Exception e) - { - Debug.LogError(e); - } - - isInitialState = true; - // lastState = state; - // return; - } - - - if (state.device.modified_by != DeviceId) - { - FieldInfo[] fields = state.device.GetType().GetFields(); - - foreach (FieldInfo fieldInfo in fields) - { - string newValue = fieldInfo.GetValue(state.device) as string; - string oldValue = lastState != null ? fieldInfo.GetValue(lastState.device) as string : null; - if (newValue != oldValue) - { - try - { - if (!isInitialState) OnDeviceFieldChanged?.Invoke(fieldInfo.Name, newValue); - } - catch (Exception e) - { - Debug.LogError(e); - } - - // send specific listeners data - if (deviceFieldCallbacks.ContainsKey(fieldInfo.Name)) - { - // clear the list of old listeners - deviceFieldCallbacks[fieldInfo.Name].RemoveAll(e => e.keepAliveObject == null); - - // send the callbacks - deviceFieldCallbacks[fieldInfo.Name].ForEach(e => - { - if (!isInitialState || e.sendInitialState) - { - try - { - e.callback(newValue); - } - catch (Exception ex) - { - Debug.LogError(ex); - } - } - }); - } - } - } - - if (state.device.data != null) - { - foreach (KeyValuePair elem in state.device.data) - { - string oldValue = null; - lastState?.device.data.TryGetValue(elem.Key, out oldValue); - if (elem.Value != oldValue) - { - try - { - if (!isInitialState) OnDeviceDataChanged?.Invoke(elem.Key, elem.Value); - } - catch (Exception ex) - { - Debug.LogError(ex); - } - - // send specific listeners data - if (deviceDataCallbacks.ContainsKey(elem.Key)) - { - // clear the list of old listeners - deviceDataCallbacks[elem.Key].RemoveAll(e => e.keepAliveObject == null); - - // send the callbacks - deviceDataCallbacks[elem.Key].ForEach(e => - { - if (!isInitialState || e.sendInitialState) - { - try - { - e.callback(elem.Value); - } - catch (Exception ex) - { - Debug.LogError(ex); - } - } - }); - } - } - } - } - } - - if (state.room.modified_by != DeviceId && state.room.data != null) - { - foreach (KeyValuePair 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); - } - } - }); - } - } - } - } - - lastState = state; - }); - } - catch (Exception e) - { - Debug.LogError(e); - // this make sure the coroutine never quits - } - - yield return new WaitForSeconds(1); - } - } - - /// - /// Adds a change listener callback to a particular field name within the Device main fields. - /// - public static void AddDeviceFieldListener(string key, MonoBehaviour keepAliveObject, Action callback, bool sendInitialState = false) - { - if (!deviceFieldCallbacks.ContainsKey(key)) - { - deviceFieldCallbacks[key] = new List(); - } - - 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)?.GetValue(instance.lastState.device) is string val) - { - try - { - callback(val); - } - catch (Exception e) - { - Debug.LogError(e); - } - } - } - } - } - - /// - /// Adds a change listener callback to a particular field name within the Device data JSON. - /// - public static void AddDeviceDataListener(string key, MonoBehaviour keepAliveObject, Action callback, bool sendInitialState = false) - { - if (!deviceDataCallbacks.ContainsKey(key)) - { - deviceDataCallbacks[key] = new List(); - } - - deviceDataCallbacks[key].Add(new CallbackListener() - { - keepAliveObject = keepAliveObject, - callback = callback, - sendInitialState = sendInitialState - }); - - if (sendInitialState) - { - string val = GetDeviceData(key); - if (val != null) - { - try - { - callback(val); - } - catch (Exception e) - { - Debug.LogError(e); - } - } - } - } - - /// - /// Adds a change listener callback to a particular field name within the Room data JSON. - /// - public static void AddRoomDataListener(string key, MonoBehaviour keepAliveObject, Action callback, bool sendInitialState = false) - { - if (!roomDataCallbacks.ContainsKey(key)) - { - roomDataCallbacks[key] = new List(); - } - - roomDataCallbacks[key].Add(new CallbackListener() - { - keepAliveObject = keepAliveObject, - callback = callback, - sendInitialState = sendInitialState - }); - - if (sendInitialState) - { - string val = GetRoomData(key); - if (val != null) - { - try - { - callback(val); - } - catch (Exception e) - { - Debug.LogError(e); - } - } - } - } - - public static string GetDeviceData(string key) - { - return instance != null ? instance.lastState?.device?.TryGetData(key) : null; - } - - public static string GetRoomData(string key) - { - return instance != null ? instance.lastState?.room?.TryGetData(key) : null; - } - - - /// - /// Sets data on the device keys themselves - /// - public static void SetDeviceField(Dictionary device) - { - instance.PostRequestCallback( - instance.velConnectUrl + "/api/v2/device/set_data/" + DeviceId, - JsonConvert.SerializeObject(device), - new Dictionary { { "modified_by", DeviceId } } - ); - } - - /// - /// Sets the 'data' object of the Device table - /// - public static void SetDeviceData(Dictionary data) - { - instance.PostRequestCallback( - instance.velConnectUrl + "/api/v2/device/set_data/" + DeviceId, - JsonConvert.SerializeObject(new Dictionary { { "data", data } }), - new Dictionary { { "modified_by", DeviceId } } - ); - } - - public static void SetRoomData(Dictionary data) - { - if (!VelNetManager.InRoom) - { - Debug.LogError("Can't set data for a room if you're not in a room."); - return; - } - - instance.PostRequestCallback( - instance.velConnectUrl + "/api/v2/set_data/" + Application.productName + "_" + VelNetManager.Room, - JsonConvert.SerializeObject(data), - new Dictionary { { "modified_by", DeviceId } } - ); - } - - - public void GetRequestCallback(string url, Action successCallback = null, Action failureCallback = null) - { - StartCoroutine(GetRequestCallbackCo(url, successCallback, failureCallback)); - } - - private IEnumerator GetRequestCallbackCo(string url, Action successCallback = null, Action 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); - failureCallback?.Invoke(webRequest.error); - break; - case UnityWebRequest.Result.Success: - successCallback?.Invoke(webRequest.downloadHandler.text); - break; - } - } - - public void PostRequestCallback(string url, string postData, Dictionary headers = null, Action successCallback = null, - Action failureCallback = null) - { - StartCoroutine(PostRequestCallbackCo(url, postData, headers, successCallback, failureCallback)); - } - - private static IEnumerator PostRequestCallbackCo(string url, string postData, Dictionary headers = null, Action successCallback = null, - Action failureCallback = null) - { - UnityWebRequest webRequest = new UnityWebRequest(url, "POST"); - byte[] bodyRaw = Encoding.UTF8.GetBytes(postData); - UploadHandlerRaw uploadHandler = new UploadHandlerRaw(bodyRaw); - webRequest.uploadHandler = uploadHandler; - webRequest.SetRequestHeader("Content-Type", "application/json"); - if (headers != null) - { - foreach (KeyValuePair 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.LogError(url + ": Error: " + webRequest.error); - failureCallback?.Invoke(webRequest.error); - break; - case UnityWebRequest.Result.Success: - successCallback?.Invoke(webRequest.downloadHandler.text); - break; - } - - uploadHandler.Dispose(); - webRequest.Dispose(); - } - - private void OnApplicationFocus(bool focus) - { - UpdateUserCount(!focus); - } - } +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Newtonsoft.Json; +using UnityEngine; +using UnityEngine.Networking; +using VelNet; + +namespace VELConnect +{ + public class VELConnectManager : MonoBehaviour + { + public string velConnectUrl = "http://localhost"; + private static VELConnectManager instance; + + public class State + { + public class User + { + public string id; + public string email; + public string username; + public string date_created; + public string last_modified; + public Dictionary data; + } + public class Device + { + public string hw_id; + public string os_info; + public string friendly_name; + public string modified_by; + public string current_app; + public string current_room; + public int pairing_code; + public string date_created; + public string last_modified; + public Dictionary data; + + /// + /// Returns the value if it exists, otherwise null + /// + public string TryGetData(string key) + { + string val = null; + return data?.TryGetValue(key, out val) == true ? val : null; + } + } + + public class RoomState + { + public string error; + public string id; + public string category; + public string date_created; + public string modified_by; + public string last_modified; + public string last_accessed; + public Dictionary data; + + /// + /// Returns the value if it exists, otherwise null + /// + public string TryGetData(string key) + { + string val = null; + return data?.TryGetValue(key, out val) == true ? val : null; + } + } + + public User user; + public Device device; + public RoomState room; + } + + public State lastState; + + public static Action OnInitialState; + public static Action OnDeviceFieldChanged; + public static Action OnDeviceDataChanged; + public static Action OnRoomDataChanged; + + private static readonly Dictionary> deviceFieldCallbacks = + new Dictionary>(); + + private static readonly Dictionary> deviceDataCallbacks = + new Dictionary>(); + + private static readonly Dictionary> roomDataCallbacks = + new Dictionary>(); + + private struct CallbackListener + { + /// + /// Used so that other objects don't have to remove listeners themselves + /// + public MonoBehaviour keepAliveObject; + + public Action callback; + + /// + /// Sends the first state received from the network or the state at binding time + /// + public bool sendInitialState; + } + + public static int 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; + } + } + + private static string DeviceId + { + get + { +#if UNITY_EDITOR + // allows running multiple builds on the same computer + // return SystemInfo.deviceUniqueIdentifier + Hash128.Compute(Application.dataPath); + return SystemInfo.deviceUniqueIdentifier + "_EDITOR"; +#else + return SystemInfo.deviceUniqueIdentifier; +#endif + } + } + + private void Awake() + { + if (instance != null) Debug.LogError("VELConnectManager instance already exists", this); + instance = this; + } + + // Start is called before the first frame update + private void Start() + { + SetDeviceField(new Dictionary + { + { "current_app", Application.productName }, + { "pairing_code", PairingCode } + }); + + UpdateUserCount(); + + + StartCoroutine(SlowLoop()); + + VelNetManager.OnJoinedRoom += room => + { + SetDeviceField(new Dictionary + { + { "current_app", Application.productName }, + { "current_room", room }, + }); + }; + } + + + private void UpdateUserCount(bool leaving = false) + { + if (!VelNetManager.InRoom) return; + + VelNetManager.GetRooms(rooms => + { + Dictionary postData = new Dictionary + { + { "hw_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/update_user_count", JsonConvert.SerializeObject(postData)); + }); + } + + private IEnumerator SlowLoop() + { + while (true) + { + try + { + GetRequestCallback(velConnectUrl + "/api/device/get_data/" + DeviceId, json => + { + State state = JsonConvert.DeserializeObject(json); + if (state == null) return; + + bool isInitialState = false; + + // first load stuff + if (lastState == null) + { + try + { + OnInitialState?.Invoke(state); + } + catch (Exception e) + { + Debug.LogError(e); + } + + isInitialState = true; + // lastState = state; + // return; + } + + + if (state.device.modified_by != DeviceId) + { + FieldInfo[] fields = state.device.GetType().GetFields(); + + foreach (FieldInfo fieldInfo in fields) + { + string newValue = fieldInfo.GetValue(state.device) as string; + string oldValue = lastState != null + ? fieldInfo.GetValue(lastState.device) as string + : null; + if (newValue != oldValue) + { + try + { + if (!isInitialState) OnDeviceFieldChanged?.Invoke(fieldInfo.Name, newValue); + } + catch (Exception e) + { + Debug.LogError(e); + } + + // send specific listeners data + if (deviceFieldCallbacks.ContainsKey(fieldInfo.Name)) + { + // clear the list of old listeners + deviceFieldCallbacks[fieldInfo.Name].RemoveAll(e => e.keepAliveObject == null); + + // send the callbacks + deviceFieldCallbacks[fieldInfo.Name].ForEach(e => + { + if (!isInitialState || e.sendInitialState) + { + try + { + e.callback(newValue); + } + catch (Exception ex) + { + Debug.LogError(ex); + } + } + }); + } + } + } + + if (state.device.data != null) + { + foreach (KeyValuePair elem in state.device.data) + { + string oldValue = null; + lastState?.device.data.TryGetValue(elem.Key, out oldValue); + if (elem.Value != oldValue) + { + try + { + if (!isInitialState) OnDeviceDataChanged?.Invoke(elem.Key, elem.Value); + } + catch (Exception ex) + { + Debug.LogError(ex); + } + + // send specific listeners data + if (deviceDataCallbacks.ContainsKey(elem.Key)) + { + // clear the list of old listeners + deviceDataCallbacks[elem.Key].RemoveAll(e => e.keepAliveObject == null); + + // send the callbacks + deviceDataCallbacks[elem.Key].ForEach(e => + { + if (!isInitialState || e.sendInitialState) + { + try + { + e.callback(elem.Value); + } + catch (Exception ex) + { + Debug.LogError(ex); + } + } + }); + } + } + } + } + } + + if (state.room.modified_by != DeviceId && state.room.data != null) + { + foreach (KeyValuePair 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); + } + } + }); + } + } + } + } + + lastState = state; + }); + } + catch (Exception e) + { + Debug.LogError(e); + // this make sure the coroutine never quits + } + + yield return new WaitForSeconds(1); + } + } + + /// + /// Adds a change listener callback to a particular field name within the Device main fields. + /// + public static void AddDeviceFieldListener(string key, MonoBehaviour keepAliveObject, Action callback, + bool sendInitialState = false) + { + if (!deviceFieldCallbacks.ContainsKey(key)) + { + deviceFieldCallbacks[key] = new List(); + } + + 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) + ?.GetValue(instance.lastState.device) is string val) + { + try + { + callback(val); + } + catch (Exception e) + { + Debug.LogError(e); + } + } + } + } + } + + /// + /// Adds a change listener callback to a particular field name within the Device data JSON. + /// + public static void AddDeviceDataListener(string key, MonoBehaviour keepAliveObject, Action callback, + bool sendInitialState = false) + { + if (!deviceDataCallbacks.ContainsKey(key)) + { + deviceDataCallbacks[key] = new List(); + } + + deviceDataCallbacks[key].Add(new CallbackListener() + { + keepAliveObject = keepAliveObject, + callback = callback, + sendInitialState = sendInitialState + }); + + if (sendInitialState) + { + string val = GetDeviceData(key); + if (val != null) + { + try + { + callback(val); + } + catch (Exception e) + { + Debug.LogError(e); + } + } + } + } + + /// + /// Adds a change listener callback to a particular field name within the Room data JSON. + /// + public static void AddRoomDataListener(string key, MonoBehaviour keepAliveObject, Action callback, + bool sendInitialState = false) + { + if (!roomDataCallbacks.ContainsKey(key)) + { + roomDataCallbacks[key] = new List(); + } + + roomDataCallbacks[key].Add(new CallbackListener() + { + keepAliveObject = keepAliveObject, + callback = callback, + sendInitialState = sendInitialState + }); + + if (sendInitialState) + { + string val = GetRoomData(key); + if (val != null) + { + try + { + callback(val); + } + catch (Exception e) + { + Debug.LogError(e); + } + } + } + } + + public static string GetDeviceData(string key) + { + return instance != null ? instance.lastState?.device?.TryGetData(key) : null; + } + + public static string GetRoomData(string key) + { + return instance != null ? instance.lastState?.room?.TryGetData(key) : null; + } + + + /// + /// Sets data on the device keys themselves + /// + public static void SetDeviceField(Dictionary device) + { + instance.PostRequestCallback( + instance.velConnectUrl + "/api/device/set_data/" + DeviceId, + JsonConvert.SerializeObject(device), + new Dictionary { { "modified_by", DeviceId } } + ); + } + + /// + /// Sets the 'data' object of the Device table + /// + public static void SetDeviceData(Dictionary data) + { + instance.PostRequestCallback( + instance.velConnectUrl + "/api/device/set_data/" + DeviceId, + JsonConvert.SerializeObject(new Dictionary { { "data", data } }), + new Dictionary { { "modified_by", DeviceId } } + ); + } + + public static void SetRoomData(Dictionary data) + { + if (!VelNetManager.InRoom) + { + Debug.LogError("Can't set data for a room if you're not in a room."); + return; + } + + instance.PostRequestCallback( + instance.velConnectUrl + "/api/set_data/" + Application.productName + "_" + VelNetManager.Room, + JsonConvert.SerializeObject(data), + new Dictionary { { "modified_by", DeviceId } } + ); + } + + + public void GetRequestCallback(string url, Action successCallback = null, + Action failureCallback = null) + { + StartCoroutine(GetRequestCallbackCo(url, successCallback, failureCallback)); + } + + private IEnumerator GetRequestCallbackCo(string url, Action successCallback = null, + Action 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 void PostRequestCallback(string url, string postData, Dictionary headers = null, + Action successCallback = null, + Action failureCallback = null) + { + StartCoroutine(PostRequestCallbackCo(url, postData, headers, successCallback, failureCallback)); + } + + private static IEnumerator PostRequestCallbackCo(string url, string postData, + Dictionary headers = null, Action successCallback = null, + Action failureCallback = null) + { + UnityWebRequest webRequest = new UnityWebRequest(url, "POST"); + byte[] bodyRaw = Encoding.UTF8.GetBytes(postData); + UploadHandlerRaw uploadHandler = new UploadHandlerRaw(bodyRaw); + webRequest.uploadHandler = uploadHandler; + webRequest.SetRequestHeader("Content-Type", "application/json"); + if (headers != null) + { + foreach (KeyValuePair 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.LogError(url + ": Error: " + webRequest.error + "\n" + Environment.StackTrace); + failureCallback?.Invoke(webRequest.error); + break; + case UnityWebRequest.Result.Success: + successCallback?.Invoke(webRequest.downloadHandler.text); + break; + } + + uploadHandler.Dispose(); + webRequest.Dispose(); + } + + private void OnApplicationFocus(bool focus) + { + UpdateUserCount(!focus); + } + } } \ No newline at end of file diff --git a/velconnect/CreateDB.sql b/velconnect/CreateDB.sql index 03d4107..ebc88b8 100644 --- a/velconnect/CreateDB.sql +++ b/velconnect/CreateDB.sql @@ -73,7 +73,7 @@ CREATE TABLE `DataBlock` ( -- id of the owner of this file. Ownership is not transferable because ids may collide, -- but the owner could be null for global scope `owner_id` TEXT, - `visibility` ENUM('public', 'private', 'unlisted') NOT NULL DEFAULT 'public', + `visibility` TEXT CHECK( `visibility` IN ('public','private','unlisted') ) NOT NULL DEFAULT 'public', -- This is an indexable field to filter out different types of datablocks `category` TEXT, `date_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, diff --git a/velconnect/routes/api.py b/velconnect/routes/api.py index c90bcbe..3dbf467 100644 --- a/velconnect/routes/api.py +++ b/velconnect/routes/api.py @@ -54,6 +54,16 @@ def parse_data(device: dict): device['data'] = json.loads(device['data']) +@router.get('/get_all_users') +def get_all_users(): + """Returns a list of all devices and details associated with them.""" + values = db.query("SELECT * FROM `User`;") + values = [dict(v) for v in values] + for v in values: + parse_data(v) + return values + + @router.get('/get_all_devices') def get_all_devices(): """Returns a list of all devices and details associated with them.""" @@ -92,22 +102,22 @@ def get_device_by_pairing_code_dict(pairing_code: str) -> dict | None: def get_user_for_device(hw_id: str) -> dict: values = db.query("""SELECT * FROM `UserDevice` WHERE `hw_id`=:hw_id;""", {'hw_id': hw_id}) if len(values) == 1: - user = dict(values[0]) - parse_data(user) - return user - # create new user instead + user_id = dict(values[0])['user_id'] + user = get_user_dict(user_id=user_id) else: + # create new user instead user = create_user(hw_id) + parse_data(user) + return user # creates a user with a device autoattached def create_user(hw_id: str) -> dict | None: - user_id = uuid.uuid4() - if not db.insert("""INSERT INTO `User`(id) VALUES (:user_id); - """, {'user_id': user_id}): + user_id = str(uuid.uuid4()) + if not db.insert("""INSERT INTO `User`(id) VALUES (:user_id);""", {'user_id': user_id}): return None - if not db.insert("""INSERT INTO `UserDevice`(user_id, hw_id) VALUES (:user_id, :hw_id); - """, {'user_id': user_id, 'hw_id': hw_id}): + if not db.insert("""INSERT INTO `UserDevice`(user_id, hw_id) VALUES (:user_id, :hw_id); """, + {'user_id': user_id, 'hw_id': hw_id}): return None return get_user_for_device(hw_id) @@ -119,26 +129,29 @@ def create_device(hw_id: str): @router.get('/device/get_data/{hw_id}') -def get_state(request: fastapi.Request, hw_id: str): +def get_state(request: Request, response: Response, hw_id: str): """Gets the device state""" devices = db.query(""" SELECT * FROM `Device` WHERE `hw_id`=:hw_id; """, {'hw_id': hw_id}) if len(devices) == 0: + response.status_code = status.HTTP_404_NOT_FOUND return {'error': "Can't find device with that id."} block = dict(devices[0]) if 'data' in block and block['data'] is not None: block['data'] = json.loads(block['data']) + user = get_user_for_device(hw_id) + room_key: str = f"{devices[0]['current_app']}_{devices[0]['current_room']}" - room_data = get_data(room_key) + room_data = get_data(response, key=room_key, user_id=user['id']) if "error" in room_data: set_data(request, data={}, key=room_key, modified_by=None, category="room") - room_data = get_data(room_key) + room_data = get_data(response, key=room_key, user_id=user['id']) - return {'device': block, 'room': room_data} + return {'device': block, 'room': room_data, 'user': user} @router.post('/device/set_data/{hw_id}')