diff --git a/MoCha/Assets/Scenes/Main.unity b/MoCha/Assets/Scenes/Main.unity index 4653ae1..1cef1c2 100644 Binary files a/MoCha/Assets/Scenes/Main.unity and b/MoCha/Assets/Scenes/Main.unity differ diff --git a/MoCha/Assets/Scripts/CustomAndroidPlugin.cs b/MoCha/Assets/Scripts/CustomAndroidPlugin.cs new file mode 100644 index 0000000..a040bd8 --- /dev/null +++ b/MoCha/Assets/Scripts/CustomAndroidPlugin.cs @@ -0,0 +1,23 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using Assets.Scripts.Fitbit; + + +namespace Assets.Scripts +{ + public class CustomAndroidPlugin : MonoBehaviour + { + + public GameObject Manager; + + public void PassReturnCode(string value) + { + Manager.GetComponent().SetReturnCodeFromAndroid(value); + + } + + } + + +} diff --git a/MoCha/Assets/Scripts/CustomAndroidPlugin.cs.meta b/MoCha/Assets/Scripts/CustomAndroidPlugin.cs.meta new file mode 100644 index 0000000..a23005d --- /dev/null +++ b/MoCha/Assets/Scripts/CustomAndroidPlugin.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 80685d60eb42cd340a5c76d410187801 +timeCreated: 1524716619 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MoCha/Assets/Scripts/FitBitAPI.cs b/MoCha/Assets/Scripts/FitBitAPI.cs new file mode 100644 index 0000000..4dfdbca --- /dev/null +++ b/MoCha/Assets/Scripts/FitBitAPI.cs @@ -0,0 +1,563 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Linq; +using Newtonsoft.Json; +using UnityEngine; + +namespace Assets.Scripts.Fitbit +{ + + public class FitBitAPI : MonoBehaviour + { + + private const string _consumerSecret = "69307b9f332caf9946ef4e23cabde2e4"; + private const string _clientId = "22CX4L"; + private const string _callbackURL = "http://localhost/callback"; + //If you're making an app for Android, fill in your custom scheme here from Fitbit + //if you don't know how to do the callback through a native browser on a mobile device + //http://technicalartistry.blogspot.ca/2016/01/fitbit-unity-oauth-2-and-native.html + //can probably help :) + private const string CustomAndroidScheme = "mochaapp://TheCallback"; + + private const string _tokenUrl = "https://api.fitbit.com/oauth2/token"; + private const string _baseGetUrl = "https://api.fitbit.com/1/user/-/"; + private const string _profileUrl = _baseGetUrl + "profile.json/"; + private const string _activityUrl = _baseGetUrl + "activities/"; + + private string _distanceUrl = _activityUrl + "distance/date/" + _currentDateTime + "/1d.json"; + + private string _stepsUrl = _activityUrl + "steps/date/" + _currentDateTime + "/1d.json"; + + private string _caloriesUrl = _activityUrl + "calories/date/" + _currentDateTime + "/1d.json"; + + private string _sleepUrl = _baseGetUrl + "sleep/minutesAsleep/date/" + _currentDateTime + "/" + _currentDateTime + ".json"; + + private static string _currentDateTime = GetCurrentDate(); + + private string _returnCode; + private WWW _wwwRequest; + private bool _bGotTheData = false; + private bool _bFirstFire = true; + + private OAuth2AccessToken _oAuth2 = new OAuth2AccessToken(); + public FitbitData _fitbitData = new FitbitData(); + + //Debug String for Android + private string _statusMessage; + + private string CallBackUrl + { + get + { + //determine which platform we're running on and use the appropriate url + if (Application.platform == RuntimePlatform.WindowsEditor) + return WWW.EscapeURL(_callbackURL); + else if (Application.platform == RuntimePlatform.Android) + { + return WWW.EscapeURL(CustomAndroidScheme); + } + else + { + return WWW.EscapeURL(CustomAndroidScheme); + } + } + } + + public void Start() + { + DontDestroyOnLoad(this); + } + + private void OnGUI() + { + if (!_bGotTheData && !string.IsNullOrEmpty(_statusMessage) && _bFirstFire) + { + _bFirstFire = false; + } + GUI.Label(new Rect(10, 10, 500, 500), "Number Steps: " + _fitbitData.CurrentSteps); + + GUI.Label(new Rect(10, 20, 500, 500), "Calories: " + _fitbitData.CurrentCalories); + GUI.Label(new Rect(10, 40, 500, 500), "Distance Walked: " + _fitbitData.CurrentDistance); + GUI.Label(new Rect(10, 60, 500, 500), "Return Code " + _returnCode); + } + + public void LoginToFitbit() + { + //we'll check to see if we have the RefreshToken in PlayerPrefs or not. + //if we do, then we'll use the RefreshToken to get the data + //if not then we will just do the regular ask user to login to get data + //then save the tokens correctly. + + if (PlayerPrefs.HasKey("FitbitRefreshToken")) + { + UseRefreshToken(); + } + else + { + UserAcceptOrDeny(); + } + + } + public void UserAcceptOrDeny() + { + //we don't have a refresh token so we gotta go through the whole auth process. + var url = + "https://www.fitbit.com/oauth2/authorize?response_type=code&client_id=" + _clientId + "&redirect_uri=" + + CallBackUrl + + "&scope=activity%20nutrition%20heartrate%20location%20profile%20sleep%20weight%20social"; + Application.OpenURL(url); + // print(url); +#if UNITY_EDITOR +#endif + } + + public void ClearRefreshCode() + { + PlayerPrefs.DeleteKey("FitbitRefreshToken"); + Debug.Log("Refresh Token has been CLEARED!"); + } + + private void UseReturnCode() + { + Debug.Log("return code isn't empty"); + //not empty means we put a code in + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(_clientId + ":" + _consumerSecret); + var encoded = Convert.ToBase64String(plainTextBytes); + + var form = new WWWForm(); + form.AddField("client_id", _clientId); + form.AddField("grant_type", "authorization_code"); + form.AddField("redirect_uri", WWW.UnEscapeURL(CallBackUrl)); + form.AddField("code", _returnCode); + + var headers = form.headers; + headers["Authorization"] = "Basic " + encoded; + + _wwwRequest = new WWW(_tokenUrl, form.data, headers); + StartCoroutine(WaitForAccess(_wwwRequest)); + + //DIRTY DIRTY HACK + while (!_wwwRequest.isDone) + { + } + + Debug.Log("Token: " + _wwwRequest.text); + Debug.Log("parsing token"); + + var parsed = new JSONObject(_wwwRequest.text); + ParseAccessToken(parsed); + Debug.Log("\nParsed Token: " + _oAuth2.Token); + + //now that we have the Auth Token, Lets use it and get data. + GetAllData(); + Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps); + _bGotTheData = true; + } + + public void UseRefreshToken() + { + Debug.Log("Using Refresh Token"); + var plainTextBytes = Encoding.UTF8.GetBytes(_clientId + ":" + _consumerSecret); + var encoded = Convert.ToBase64String(plainTextBytes); + + var form = new WWWForm(); + form.AddField("grant_type", "refresh_token"); + form.AddField("refresh_token", PlayerPrefs.GetString("FitbitRefreshToken")); + + var headers = form.headers; + headers["Authorization"] = "Basic " + encoded; + + _wwwRequest = new WWW(_tokenUrl, form.data, headers); + StartCoroutine(WaitForAccess(_wwwRequest)); + //DIRTY DIRTY HACK + while (!_wwwRequest.isDone) + { + } + + Debug.Log("RefreshToken wwwText: " + _wwwRequest.text); + //check to see if it's errored or not + //we have an error and thus should just redo the Auth. + if (!String.IsNullOrEmpty(_wwwRequest.error)) + { + PlayerPrefs.DeleteKey("FitbitRefreshToken"); + UserAcceptOrDeny(); + UseReturnCode(); + GetAllData(); + } + else + { + Debug.Log("Using the Auth Token (UseRefreshToken)"); + //no errors so parse the accessToken and update everything :) + var parsed = new JSONObject(_wwwRequest.text); + ParseAccessToken(parsed); + GetAllData(); + } + } + + public void SetReturnCodeFromAndroid(string code) + { + if (string.IsNullOrEmpty(code)) + return; + //we passed the full URL so we'll have to extract the + //We will add 6 to the string lenght to account for "?code=" + _returnCode = code.Substring(CustomAndroidScheme.Length + 6); + Debug.Log("Return Code is: " + _returnCode); + + UseReturnCode(); + } + + public void SetReturnCode(string code) + { + if (string.IsNullOrEmpty(code)) + return; + + _returnCode = code; + UseReturnCode(); + } + + + + + public void GetAllData() + { + GetProfileData(); + GetAllRelevantData(); + BuildProfile(); + + //make sure the loading screen is open and change message + _fitbitData.LastSyncTime = DateTime.Now.ToUniversalTime(); + Debug.Log("LastSyncTime: " + DateTime.Now.ToUniversalTime().ToString("g")); + } + + private void GetAllRelevantData() + { + GetSteps(); + GetDistance(); + GetCalories(); + GetSleep(); + } + + #region GetData + private void GetProfileData() + { + + //time for Getting Dataz + var headers = new Dictionary(); + headers["Authorization"] = "Bearer " + _oAuth2.Token; + + _wwwRequest = new WWW(_profileUrl, null, headers); + Debug.Log("Doing GET Request"); + StartCoroutine(WaitForAccess(_wwwRequest)); + + //DIRTY DIRTY HACK + while (!_wwwRequest.isDone) + { + } + + ParseProfileData(_wwwRequest.text); + + } + + private void GetCalories() + { + //time for Getting Dataz + var headers = new Dictionary(); + headers["Authorization"] = "Bearer " + _oAuth2.Token; + + Debug.Log("Calories URL is: " + _caloriesUrl); + _wwwRequest = new WWW(_caloriesUrl, null, headers); + Debug.Log("Doing Calories GET Request"); + StartCoroutine(WaitForAccess(_wwwRequest)); + + //DIRTY DIRTY HACK + while (!_wwwRequest.isDone) + { + } + ParseCaloriesData(_wwwRequest.text); + } + + private void GetDistance() + { + //time for Getting Dataz + var headers = new Dictionary(); + headers["Authorization"] = "Bearer " + _oAuth2.Token; + + Debug.Log("Distance URL is: " + _distanceUrl); + _wwwRequest = new WWW(_distanceUrl, null, headers); + Debug.Log("Doing Distance GET Request"); + StartCoroutine(WaitForAccess(_wwwRequest)); + + //DIRTY DIRTY HACK + while (!_wwwRequest.isDone) + { + } + + ParseDistanceData(_wwwRequest.text); + } + + private void GetSteps() + { + //time for Getting Dataz + var headers = new Dictionary(); + headers["Authorization"] = "Bearer " + _oAuth2.Token; + + Debug.Log("Steps URL is: " + _stepsUrl); + _wwwRequest = new WWW(_stepsUrl, null, headers); + Debug.Log("Doing Steps GET Request"); + StartCoroutine(WaitForAccess(_wwwRequest)); + + //DIRTY DIRTY HACK + while (!_wwwRequest.isDone) + { + } + + ParseStepsData(_wwwRequest.text); + } + + private void GetSleep() + { + //time for Getting Dataz + var headers = new Dictionary(); + headers["Authorization"] = "Bearer " + _oAuth2.Token; + + Debug.Log("Sleep URL is: " + _sleepUrl); + _wwwRequest = new WWW(_sleepUrl, null, headers); + Debug.Log("Doing Sleep GET Request"); + StartCoroutine(WaitForAccess(_wwwRequest)); + + //DIRTY DIRTY HACK + while (!_wwwRequest.isDone) + { + } + + ParseSleepData(_wwwRequest.text); + } + + private void BuildProfile() + { + var imageWWW = new WWW(_fitbitData.ProfileData["avatar"]); + //DIRTY DIRTY HACK + while (!imageWWW.isDone) + { + } + + Debug.Log(_fitbitData.RawProfileData["fullName"]); + + //we should check to see if there is "data" already + if (_fitbitData.ProfileData.Count != 0) + { + foreach (KeyValuePair kvp in _fitbitData.ProfileData) + { + if (kvp.Key == "avatar") + continue; + + //put a space between the camelCase + var tempKey = Regex.Replace(kvp.Key, "(\\B[A-Z])", " $1"); + //then capitalize the first letter + UppercaseFirst(tempKey); + } + } + + _bGotTheData = true; + } + #endregion + + #region Parsing + private void ParseAccessToken(JSONObject parsed) + { + var dict = parsed.ToDictionary(); + foreach (KeyValuePair kvp in dict) + { + if (kvp.Key == "access_token") + { + _oAuth2.Token = kvp.Value; + PlayerPrefs.SetString("FitbitAccessToken", kvp.Value); + } + else if (kvp.Key == "expires_in") + { + var num = 0; + Int32.TryParse(kvp.Value, out num); + _oAuth2.ExpiresIn = num; + + } + else if (kvp.Key == "refresh_token") + { + _oAuth2.RefreshToken = kvp.Value; + Debug.Log("REFRESH TOKEN: " + kvp.Value); + PlayerPrefs.SetString("FitbitRefreshToken", kvp.Value); + Debug.Log("Token We Just Store: " + PlayerPrefs.GetString("FitbitRefreshToken")); + } + else if (kvp.Key == "token_type") + { + _oAuth2.TokenType = kvp.Value; + PlayerPrefs.SetString("FitbitTokenType", kvp.Value); + } + } + } + + private void ParseProfileData(string data) + { + Debug.Log("inserting json data into fitbitData.RawProfileData"); + //Debug.LogWarning(data); + XmlDocument xmldoc = JsonConvert.DeserializeXmlNode(data); + + var doc = XDocument.Parse(xmldoc.InnerXml); + + + doc.Descendants("topBadges").Remove(); + foreach (XElement xElement in doc.Descendants()) + { + //Debug.Log(xElement.Name.LocalName + ": Value:" + xElement.Value);V + if (!_fitbitData.RawProfileData.ContainsKey(xElement.Name.LocalName)) + _fitbitData.RawProfileData.Add(xElement.Name.LocalName, xElement.Value); + else + { + //Debug.LogWarning("Key already found in RawProfileData: " + xElement.Name.LocalName); + //if the key is already in the dict, we will just update the value for consistency. + _fitbitData.RawProfileData[xElement.Name.LocalName] = xElement.Value; + } + + if (_fitbitData.ProfileData.ContainsKey(xElement.Name.LocalName)) + { + _fitbitData.ProfileData[xElement.Name.LocalName] = xElement.Value; + } + } + } + + private void ParseStepsData(string data) + { + //convert the json to xml cause json blows hard. + XmlDocument json = JsonConvert.DeserializeXmlNode(data); + + XDocument doc = XDocument.Parse(json.InnerXml); + var root = doc.Descendants("value").FirstOrDefault(); + _fitbitData.CurrentSteps = ToInt(root.Value); + + Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps); + } + + private void ParseDistanceData(string data) + { + XmlDocument json = JsonConvert.DeserializeXmlNode(data); + + XDocument doc = XDocument.Parse(json.InnerXml); + var root = doc.Descendants("value").FirstOrDefault().Value; + //trim the value + if (root.Length > 4) + root = root.Substring(0, 4); + + _fitbitData.CurrentDistance = ToDouble(root); + + Debug.Log("Distance from Fitbit is:" + _fitbitData.CurrentDistance); + } + + private void ParseCaloriesData(string data) + { + XmlDocument json = JsonConvert.DeserializeXmlNode(data); + + var doc = XDocument.Parse(json.InnerXml); + var calories = doc.Descendants("value").FirstOrDefault().Value; + + _fitbitData.CurrentCalories = ToInt(calories); + } + + private void ParseSleepData(string data) + { + Debug.Log(data); + XmlDocument json = JsonConvert.DeserializeXmlNode(data); + + var doc = XDocument.Parse(json.InnerXml); + var sleepTimeTotal = doc.Descendants("value").FirstOrDefault().Value; + Debug.Log("Minutes asleep for: " + sleepTimeTotal); + + _fitbitData.CurrentSleep = ToInt(sleepTimeTotal); + } + + + #endregion + + static string UppercaseFirst(string s) + { + // Check for empty string. + if (string.IsNullOrEmpty(s)) + { + return string.Empty; + } + // Return char and concat substring. + return char.ToUpper(s[0]) + s.Substring(1); + } + + IEnumerator WaitForAccess(WWW www) + { + Debug.Log("waiting for access\n"); + yield return www; + Debug.Log("Past the Yield \n"); + // check for errors + if (www.error == null) + { + Debug.Log("no error \n"); + Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps); + Debug.Log(www.text); + Debug.Log("Steps from Fitbit: " + _fitbitData.CurrentSteps); + //Debug.Log("WWW Ok!: " + www.text); + // _accessToken = www.responseHeaders["access_token"]; + } + if (www.error != null) + { + Debug.Log("\n Error" + www.error); + Debug.Log(www.error); + } + Debug.Log("end of WaitForAccess \n"); + + } + + //just a utility function to get the correct date format for activity calls that require one + public static string GetCurrentDate() + { + var date = ""; + date += DateTime.Now.Year; + if (DateTime.Now.Month < 10) + { + date += "-" + "0" + DateTime.Now.Month; + } + else + { + date += "-" + DateTime.Now.Month; + } + + if (DateTime.Now.Day < 10) + { + date += "-" + "0" + DateTime.Now.Day; + } + else + { + date += "-" + DateTime.Now.Day; + } + //date += "-" + 15; + return date; + } + + + + private int ToInt(string thing) + { + var temp = 0; + Int32.TryParse(thing, out temp); + return temp; + } + + private double ToDouble(string thing) + { + var temp = 0.0; + Double.TryParse(thing, out temp); + return temp; + } + + } + +} diff --git a/MoCha/Assets/Scripts/FitBitAPI.cs.meta b/MoCha/Assets/Scripts/FitBitAPI.cs.meta new file mode 100644 index 0000000..8514324 --- /dev/null +++ b/MoCha/Assets/Scripts/FitBitAPI.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: aa5e856e9b878764db7c192f3aa20277 +timeCreated: 1524716713 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MoCha/Assets/Scripts/FitbitCaller.cs b/MoCha/Assets/Scripts/FitbitCaller.cs new file mode 100644 index 0000000..0dad878 --- /dev/null +++ b/MoCha/Assets/Scripts/FitbitCaller.cs @@ -0,0 +1,15 @@ +using UnityEngine; +using System.Collections; + +public class FitbitCaller : MonoBehaviour { + + // Use this for initialization + void Start () { + + } + + // Update is called once per frame + void Update () { + + } +} diff --git a/MoCha/Assets/Scripts/FitbitCaller.cs.meta b/MoCha/Assets/Scripts/FitbitCaller.cs.meta new file mode 100644 index 0000000..1dc151a --- /dev/null +++ b/MoCha/Assets/Scripts/FitbitCaller.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 36444c03eedb84147a326a329de30d0e +timeCreated: 1524716713 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MoCha/Assets/Scripts/FitbitData.cs b/MoCha/Assets/Scripts/FitbitData.cs new file mode 100644 index 0000000..2d7e88b --- /dev/null +++ b/MoCha/Assets/Scripts/FitbitData.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; + +namespace Assets.Scripts.Fitbit +{ + /// + /// Holder class for Fitbit pulled Data. + /// + public class FitbitData + { + public string CurrentTab = "Profile"; + + public Dictionary RawProfileData; + public Dictionary ProfileData; + + public int CurrentSteps; + public int LastSteps; + + public double CurrentDistance; + public double LastDistance; + + public int CurrentCalories; + public int LastCalories; + + public int CurrentSleep; + public int LastSleep; + + public DateTime LastSyncTime; + + public enum summary + { + activityCalories,caloriesBMR,caloriesOut,distances,activityDistance,distance, + elevation,fairlyActiveMinutes,floors,lightlyActiveMinutes,marginalCalories,sedentaryMinutes, + steps,veryActiveMinutes + } + + public FitbitData() + { + RawProfileData =new Dictionary(); + ProfileData = new Dictionary(); + + //we will build the Profile Data Keys that we want so we can compare them later + //to decide what we keep and what we don't when we get the actual data + ProfileData.Add("age",""); + ProfileData.Add("avatar",""); + ProfileData.Add("averageDailySteps",""); + ProfileData.Add("city",""); + ProfileData.Add("country",""); + ProfileData.Add("dateOfBirth",""); + ProfileData.Add("gender",""); + ProfileData.Add("memberSince",""); + } + } +} diff --git a/MoCha/Assets/Scripts/FitbitData.cs.meta b/MoCha/Assets/Scripts/FitbitData.cs.meta new file mode 100644 index 0000000..add5ba3 --- /dev/null +++ b/MoCha/Assets/Scripts/FitbitData.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 7a928c029cb14204588724261a513ec1 +timeCreated: 1524716713 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MoCha/Assets/Scripts/JSONObject.cs b/MoCha/Assets/Scripts/JSONObject.cs new file mode 100644 index 0000000..c6ede39 --- /dev/null +++ b/MoCha/Assets/Scripts/JSONObject.cs @@ -0,0 +1,1017 @@ +#define PRETTY //Comment out when you no longer need to read JSON to disable pretty Print system-wide +//Using doubles will cause errors in VectorTemplates.cs; Unity speaks floats +#define USEFLOAT //Use floats for numbers instead of doubles (enable if you're getting too many significant digits in string output) +//#define POOLING //Currently using a build setting for this one (also it's experimental) + +using System.Diagnostics; +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Debug = UnityEngine.Debug; + +/* + * http://www.opensource.org/licenses/lgpl-2.1.php + * JSONObject class v.1.4.1 + * for use with Unity + * Copyright Matt Schoen 2010 - 2013 + */ + +public class JSONObject { +#if POOLING + const int MAX_POOL_SIZE = 10000; + public static Queue releaseQueue = new Queue(); +#endif + + const int MAX_DEPTH = 100; + const string INFINITY = "\"INFINITY\""; + const string NEGINFINITY = "\"NEGINFINITY\""; + const string NaN = "\"NaN\""; + public static readonly char[] WHITESPACE = { ' ', '\r', '\n', '\t', '\uFEFF', '\u0009' }; + public enum Type { NULL, STRING, NUMBER, OBJECT, ARRAY, BOOL, BAKED } + public bool isContainer { get { return (type == Type.ARRAY || type == Type.OBJECT); } } + public Type type = Type.NULL; + public int Count { + get { + if(list == null) + return -1; + return list.Count; + } + } + public List list; + public List keys; + public string str; +#if USEFLOAT + public float n; + public float f { + get { + return n; + } + } +#else + public double n; + public float f { + get { + return (float)n; + } + } +#endif + public bool b; + public delegate void AddJSONConents(JSONObject self); + + public static JSONObject nullJO { get { return Create(Type.NULL); } } //an empty, null object + public static JSONObject obj { get { return Create(Type.OBJECT); } } //an empty object + public static JSONObject arr { get { return Create(Type.ARRAY); } } //an empty array + + public JSONObject(Type t) { + type = t; + switch(t) { + case Type.ARRAY: + list = new List(); + break; + case Type.OBJECT: + list = new List(); + keys = new List(); + break; + } + } + public JSONObject(bool b) { + type = Type.BOOL; + this.b = b; + } +#if USEFLOAT + public JSONObject(float f) { + type = Type.NUMBER; + n = f; + } +#else + public JSONObject(double d) { + type = Type.NUMBER; + n = d; + } +#endif + public JSONObject(Dictionary dic) { + type = Type.OBJECT; + keys = new List(); + list = new List(); + //Not sure if it's worth removing the foreach here + foreach(KeyValuePair kvp in dic) { + keys.Add(kvp.Key); + list.Add(CreateStringObject(kvp.Value)); + } + } + public JSONObject(Dictionary dic) { + type = Type.OBJECT; + keys = new List(); + list = new List(); + //Not sure if it's worth removing the foreach here + foreach(KeyValuePair kvp in dic) { + keys.Add(kvp.Key); + list.Add(kvp.Value); + } + } + public JSONObject(AddJSONConents content) { + content.Invoke(this); + } + public JSONObject(JSONObject[] objs) { + type = Type.ARRAY; + list = new List(objs); + } + //Convenience function for creating a JSONObject containing a string. This is not part of the constructor so that malformed JSON data doesn't just turn into a string object + public static JSONObject StringObject(string val) { return CreateStringObject(val); } + public void Absorb(JSONObject obj) { + list.AddRange(obj.list); + keys.AddRange(obj.keys); + str = obj.str; + n = obj.n; + b = obj.b; + type = obj.type; + } + public static JSONObject Create() { +#if POOLING + JSONObject result = null; + while(result == null && releaseQueue.Count > 0) { + result = releaseQueue.Dequeue(); +#if DEV + //The following cases should NEVER HAPPEN (but they do...) + if(result == null) + Debug.Log("wtf " + releaseQueue.Count); + else if(result.list != null) + Debug.Log("wtflist " + result.list.Count); +#endif + } + if(result != null) + return result; +#endif + return new JSONObject(); + } + public static JSONObject Create(Type t) { + JSONObject obj = Create(); + obj.type = t; + switch(t) { + case Type.ARRAY: + obj.list = new List(); + break; + case Type.OBJECT: + obj.list = new List(); + obj.keys = new List(); + break; + } + return obj; + } + public static JSONObject Create(bool val) { + JSONObject obj = Create(); + obj.type = Type.BOOL; + obj.b = val; + return obj; + } + public static JSONObject Create(float val) { + JSONObject obj = Create(); + obj.type = Type.NUMBER; + obj.n = val; + return obj; + } + public static JSONObject Create(int val) { + JSONObject obj = Create(); + obj.type = Type.NUMBER; + obj.n = val; + return obj; + } + public static JSONObject CreateStringObject(string val) { + JSONObject obj = Create(); + obj.type = Type.STRING; + obj.str = val; + return obj; + } + public static JSONObject CreateBakedObject(string val) { + JSONObject bakedObject = Create(); + bakedObject.type = Type.BAKED; + bakedObject.str = val; + return bakedObject; + } + /// + /// Create a JSONObject by parsing string data + /// + /// The string to be parsed + /// The maximum depth for the parser to search. Set this to to 1 for the first level, + /// 2 for the first 2 levels, etc. It defaults to -2 because -1 is the depth value that is parsed (see below) + /// Whether to store levels beyond maxDepth in baked JSONObjects + /// Whether to be strict in the parsing. For example, non-strict parsing will successfully + /// parse "a string" into a string-type + /// + public static JSONObject Create(string val, int maxDepth = -2, bool storeExcessLevels = false, bool strict = false) { + JSONObject obj = Create(); + obj.Parse(val, maxDepth, storeExcessLevels, strict); + return obj; + } + public static JSONObject Create(AddJSONConents content) { + JSONObject obj = Create(); + content.Invoke(obj); + return obj; + } + public static JSONObject Create(Dictionary dic) { + JSONObject obj = Create(); + obj.type = Type.OBJECT; + obj.keys = new List(); + obj.list = new List(); + //Not sure if it's worth removing the foreach here + foreach(KeyValuePair kvp in dic) { + obj.keys.Add(kvp.Key); + obj.list.Add(CreateStringObject(kvp.Value)); + } + return obj; + } + public JSONObject() { } + #region PARSE + public JSONObject(string str, int maxDepth = -2, bool storeExcessLevels = false, bool strict = false) { //create a new JSONObject from a string (this will also create any children, and parse the whole string) + Parse(str, maxDepth, storeExcessLevels, strict); + } + void Parse(string str, int maxDepth = -2, bool storeExcessLevels = false, bool strict = false) { + if(!string.IsNullOrEmpty(str)) { + str = str.Trim(WHITESPACE); + if(strict) { + if(str[0] != '[' && str[0] != '{') { + type = Type.NULL; + Debug.LogWarning("Improper (strict) JSON formatting. First character must be [ or {"); + return; + } + } + if(str.Length > 0) { +#if UNITY_WP8 + if (str == "true") { + type = Type.BOOL; + b = true; + } else if (str == "false") { + type = Type.BOOL; + b = false; + } else if (str == "null") { + type = Type.NULL; +#else + if(string.Compare(str, "true", true) == 0) { + type = Type.BOOL; + b = true; + } else if(string.Compare(str, "false", true) == 0) { + type = Type.BOOL; + b = false; + } else if(string.Compare(str, "null", true) == 0) { + type = Type.NULL; +#endif +#if USEFLOAT + } else if(str == INFINITY) { + type = Type.NUMBER; + n = float.PositiveInfinity; + } else if(str == NEGINFINITY) { + type = Type.NUMBER; + n = float.NegativeInfinity; + } else if(str == NaN) { + type = Type.NUMBER; + n = float.NaN; +#else + } else if(str == INFINITY) { + type = Type.NUMBER; + n = double.PositiveInfinity; + } else if(str == NEGINFINITY) { + type = Type.NUMBER; + n = double.NegativeInfinity; + } else if(str == NaN) { + type = Type.NUMBER; + n = double.NaN; +#endif + } else if(str[0] == '"') { + type = Type.STRING; + this.str = str.Substring(1, str.Length - 2); + } else { + int tokenTmp = 1; + /* + * Checking for the following formatting (www.json.org) + * object - {"field1":value,"field2":value} + * array - [value,value,value] + * value - string - "string" + * - number - 0.0 + * - bool - true -or- false + * - null - null + */ + int offset = 0; + switch(str[offset]) { + case '{': + type = Type.OBJECT; + keys = new List(); + list = new List(); + break; + case '[': + type = Type.ARRAY; + list = new List(); + break; + default: + try { +#if USEFLOAT + n = System.Convert.ToSingle(str); +#else + n = System.Convert.ToDouble(str); +#endif + type = Type.NUMBER; + } catch(System.FormatException) { + type = Type.NULL; + Debug.LogWarning("improper JSON formatting:" + str); + } + return; + } + string propName = ""; + bool openQuote = false; + bool inProp = false; + int depth = 0; + while(++offset < str.Length) { + if(System.Array.IndexOf(WHITESPACE, str[offset]) > -1) + continue; + if(str[offset] == '\\') { + offset += 1; + continue; + } + if(str[offset] == '"') { + if(openQuote) { + if(!inProp && depth == 0 && type == Type.OBJECT) + propName = str.Substring(tokenTmp + 1, offset - tokenTmp - 1); + openQuote = false; + } else { + if(depth == 0 && type == Type.OBJECT) + tokenTmp = offset; + openQuote = true; + } + } + if(openQuote) + continue; + if(type == Type.OBJECT && depth == 0) { + if(str[offset] == ':') { + tokenTmp = offset + 1; + inProp = true; + } + } + + if(str[offset] == '[' || str[offset] == '{') { + depth++; + } else if(str[offset] == ']' || str[offset] == '}') { + depth--; + } + //if (encounter a ',' at top level) || a closing ]/} + if((str[offset] == ',' && depth == 0) || depth < 0) { + inProp = false; + string inner = str.Substring(tokenTmp, offset - tokenTmp).Trim(WHITESPACE); + if(inner.Length > 0) { + if(type == Type.OBJECT) + keys.Add(propName); + if(maxDepth != -1) //maxDepth of -1 is the end of the line + list.Add(Create(inner, (maxDepth < -1) ? -2 : maxDepth - 1)); + else if(storeExcessLevels) + list.Add(CreateBakedObject(inner)); + + } + tokenTmp = offset + 1; + } + } + } + } else type = Type.NULL; + } else type = Type.NULL; //If the string is missing, this is a null + //Profiler.EndSample(); + } + #endregion + public bool IsNumber { get { return type == Type.NUMBER; } } + public bool IsNull { get { return type == Type.NULL; } } + public bool IsString { get { return type == Type.STRING; } } + public bool IsBool { get { return type == Type.BOOL; } } + public bool IsArray { get { return type == Type.ARRAY; } } + public bool IsObject { get { return type == Type.OBJECT || type == Type.BAKED; } } + public void Add(bool val) { + Add(Create(val)); + } + public void Add(float val) { + Add(Create(val)); + } + public void Add(int val) { + Add(Create(val)); + } + public void Add(string str) { + Add(CreateStringObject(str)); + } + public void Add(AddJSONConents content) { + Add(Create(content)); + } + public void Add(JSONObject obj) { + if(obj) { //Don't do anything if the object is null + if(type != Type.ARRAY) { + type = Type.ARRAY; //Congratulations, son, you're an ARRAY now + if(list == null) + list = new List(); + } + list.Add(obj); + } + } + public void AddField(string name, bool val) { + AddField(name, Create(val)); + } + public void AddField(string name, float val) { + AddField(name, Create(val)); + } + public void AddField(string name, int val) { + AddField(name, Create(val)); + } + public void AddField(string name, AddJSONConents content) { + AddField(name, Create(content)); + } + public void AddField(string name, string val) { + AddField(name, CreateStringObject(val)); + } + public void AddField(string name, JSONObject obj) { + if(obj) { //Don't do anything if the object is null + if(type != Type.OBJECT) { + if(keys == null) + keys = new List(); + if(type == Type.ARRAY) { + for(int i = 0; i < list.Count; i++) + keys.Add(i + ""); + } else + if(list == null) + list = new List(); + type = Type.OBJECT; //Congratulations, son, you're an OBJECT now + } + keys.Add(name); + list.Add(obj); + } + } + public void SetField(string name, string val) { SetField(name, CreateStringObject(val)); } + public void SetField(string name, bool val) { SetField(name, Create(val)); } + public void SetField(string name, float val) { SetField(name, Create(val)); } + public void SetField(string name, int val) { SetField(name, Create(val)); } + public void SetField(string name, JSONObject obj) { + if(HasField(name)) { + list.Remove(this[name]); + keys.Remove(name); + } + AddField(name, obj); + } + public void RemoveField(string name) { + if(keys.IndexOf(name) > -1) { + list.RemoveAt(keys.IndexOf(name)); + keys.Remove(name); + } + } + public delegate void FieldNotFound(string name); + public delegate void GetFieldResponse(JSONObject obj); + public bool GetField(ref bool field, string name, bool fallback) { + if (GetField(ref field, name)) { return true; } + field = fallback; + return false; + } + public bool GetField(ref bool field, string name, FieldNotFound fail = null) { + if(type == Type.OBJECT) { + int index = keys.IndexOf(name); + if(index >= 0) { + field = list[index].b; + return true; + } + } + if(fail != null) fail.Invoke(name); + return false; + } +#if USEFLOAT + public bool GetField(ref float field, string name, float fallback) { +#else + public bool GetField(ref double field, string name, double fallback) { +#endif + if (GetField(ref field, name)) { return true; } + field = fallback; + return false; + } +#if USEFLOAT + public bool GetField(ref float field, string name, FieldNotFound fail = null) { +#else + public bool GetField(ref double field, string name, FieldNotFound fail = null) { +#endif + if(type == Type.OBJECT) { + int index = keys.IndexOf(name); + if(index >= 0) { + field = list[index].n; + return true; + } + } + if(fail != null) fail.Invoke(name); + return false; + } + public bool GetField(ref int field, string name, int fallback) { + if (GetField(ref field, name)) { return true; } + field = fallback; + return false; + } + public bool GetField(ref int field, string name, FieldNotFound fail = null) { + if (IsObject) { + int index = keys.IndexOf(name); + if(index >= 0) { + field = (int)list[index].n; + return true; + } + } + if(fail != null) fail.Invoke(name); + return false; + } + public bool GetField(ref uint field, string name, uint fallback) { + if (GetField(ref field, name)) { return true; } + field = fallback; + return false; + } + public bool GetField(ref uint field, string name, FieldNotFound fail = null) { + if (IsObject) { + int index = keys.IndexOf(name); + if(index >= 0) { + field = (uint)list[index].n; + return true; + } + } + if(fail != null) fail.Invoke(name); + return false; + } + public bool GetField(ref string field, string name, string fallback) { + if (GetField(ref field, name)) { return true; } + field = fallback; + return false; + } + public bool GetField(ref string field, string name, FieldNotFound fail = null) { + if (IsObject) { + int index = keys.IndexOf(name); + if(index >= 0) { + field = list[index].str; + return true; + } + } + if(fail != null) fail.Invoke(name); + return false; + } + public void GetField(string name, GetFieldResponse response, FieldNotFound fail = null) { + if(response != null && IsObject) { + int index = keys.IndexOf(name); + if(index >= 0) { + response.Invoke(list[index]); + return; + } + } + if(fail != null) fail.Invoke(name); + } + public JSONObject GetField(string name) { + if (IsObject) + for(int i = 0; i < keys.Count; i++) + if(keys[i] == name) + return list[i]; + return null; + } + public bool HasFields(string[] names) { + if (!IsObject) + return false; + for(int i = 0; i < names.Length; i++) + if(!keys.Contains(names[i])) + return false; + return true; + } + public bool HasField(string name) { + if (!IsObject) + return false; + for (int i = 0; i < keys.Count; i++) + if (keys[i] == name) + return true; + return false; + } + public void Clear() { + type = Type.NULL; + if(list != null) + list.Clear(); + if(keys != null) + keys.Clear(); + str = ""; + n = 0; + b = false; + } + /// + /// Copy a JSONObject. This could probably work better + /// + /// + public JSONObject Copy() { + return Create(Print()); + } + /* + * The Merge function is experimental. Use at your own risk. + */ + public void Merge(JSONObject obj) { + MergeRecur(this, obj); + } + /// + /// Merge object right into left recursively + /// + /// The left (base) object + /// The right (new) object + static void MergeRecur(JSONObject left, JSONObject right) { + if(left.type == Type.NULL) + left.Absorb(right); + else if(left.type == Type.OBJECT && right.type == Type.OBJECT) { + for(int i = 0; i < right.list.Count; i++) { + string key = right.keys[i]; + if(right[i].isContainer) { + if(left.HasField(key)) + MergeRecur(left[key], right[i]); + else + left.AddField(key, right[i]); + } else { + if(left.HasField(key)) + left.SetField(key, right[i]); + else + left.AddField(key, right[i]); + } + } + } else if(left.type == Type.ARRAY && right.type == Type.ARRAY) { + if(right.Count > left.Count) { + Debug.LogError("Cannot merge arrays when right object has more elements"); + return; + } + for(int i = 0; i < right.list.Count; i++) { + if(left[i].type == right[i].type) { //Only overwrite with the same type + if(left[i].isContainer) + MergeRecur(left[i], right[i]); + else { + left[i] = right[i]; + } + } + } + } + } + public void Bake() { + if(type != Type.BAKED) { + str = Print(); + type = Type.BAKED; + } + } + public IEnumerable BakeAsync() { + if(type != Type.BAKED) { + foreach(string s in PrintAsync()) { + if(s == null) + yield return s; + else { + str = s; + } + } + type = Type.BAKED; + } + } +#pragma warning disable 219 + public string Print(bool pretty = false) { + StringBuilder builder = new StringBuilder(); + Stringify(0, builder, pretty); + return builder.ToString(); + } + public IEnumerable PrintAsync(bool pretty = false) { + StringBuilder builder = new StringBuilder(); + printWatch.Reset(); + printWatch.Start(); + foreach(IEnumerable e in StringifyAsync(0, builder, pretty)) { + yield return null; + } + yield return builder.ToString(); + } +#pragma warning restore 219 + #region STRINGIFY + const float maxFrameTime = 0.008f; + static readonly Stopwatch printWatch = new Stopwatch(); + IEnumerable StringifyAsync(int depth, StringBuilder builder, bool pretty = false) { //Convert the JSONObject into a string + //Profiler.BeginSample("JSONprint"); + if(depth++ > MAX_DEPTH) { + Debug.Log("reached max depth!"); + yield break; + } + if(printWatch.Elapsed.TotalSeconds > maxFrameTime) { + printWatch.Reset(); + yield return null; + printWatch.Start(); + } + switch(type) { + case Type.BAKED: + builder.Append(str); + break; + case Type.STRING: + builder.AppendFormat("\"{0}\"", str); + break; + case Type.NUMBER: +#if USEFLOAT + if(float.IsInfinity(n)) + builder.Append(INFINITY); + else if(float.IsNegativeInfinity(n)) + builder.Append(NEGINFINITY); + else if(float.IsNaN(n)) + builder.Append(NaN); +#else + if(double.IsInfinity(n)) + builder.Append(INFINITY); + else if(double.IsNegativeInfinity(n)) + builder.Append(NEGINFINITY); + else if(double.IsNaN(n)) + builder.Append(NaN); +#endif + else + builder.Append(n.ToString()); + break; + case Type.OBJECT: + builder.Append("{"); + if(list.Count > 0) { +#if(PRETTY) //for a bit more readability, comment the define above to disable system-wide + if(pretty) + builder.Append("\n"); +#endif + for(int i = 0; i < list.Count; i++) { + string key = keys[i]; + JSONObject obj = list[i]; + if(obj) { +#if(PRETTY) + if(pretty) + for(int j = 0; j < depth; j++) + builder.Append("\t"); //for a bit more readability +#endif + builder.AppendFormat("\"{0}\":", key); + foreach(IEnumerable e in obj.StringifyAsync(depth, builder, pretty)) + yield return e; + builder.Append(","); +#if(PRETTY) + if(pretty) + builder.Append("\n"); +#endif + } + } +#if(PRETTY) + if(pretty) + builder.Length -= 2; + else +#endif + builder.Length--; + } +#if(PRETTY) + if(pretty && list.Count > 0) { + builder.Append("\n"); + for(int j = 0; j < depth - 1; j++) + builder.Append("\t"); //for a bit more readability + } +#endif + builder.Append("}"); + break; + case Type.ARRAY: + builder.Append("["); + if(list.Count > 0) { +#if(PRETTY) + if(pretty) + builder.Append("\n"); //for a bit more readability +#endif + for(int i = 0; i < list.Count; i++) { + if(list[i]) { +#if(PRETTY) + if(pretty) + for(int j = 0; j < depth; j++) + builder.Append("\t"); //for a bit more readability +#endif + foreach(IEnumerable e in list[i].StringifyAsync(depth, builder, pretty)) + yield return e; + builder.Append(","); +#if(PRETTY) + if(pretty) + builder.Append("\n"); //for a bit more readability +#endif + } + } +#if(PRETTY) + if(pretty) + builder.Length -= 2; + else +#endif + builder.Length--; + } +#if(PRETTY) + if(pretty && list.Count > 0) { + builder.Append("\n"); + for(int j = 0; j < depth - 1; j++) + builder.Append("\t"); //for a bit more readability + } +#endif + builder.Append("]"); + break; + case Type.BOOL: + if(b) + builder.Append("true"); + else + builder.Append("false"); + break; + case Type.NULL: + builder.Append("null"); + break; + } + //Profiler.EndSample(); + } + //TODO: Refactor Stringify functions to share core logic + /* + * I know, I know, this is really bad form. It turns out that there is a + * significant amount of garbage created when calling as a coroutine, so this + * method is duplicated. Hopefully there won't be too many future changes, but + * I would still like a more elegant way to optionaly yield + */ + void Stringify(int depth, StringBuilder builder, bool pretty = false) { //Convert the JSONObject into a string + //Profiler.BeginSample("JSONprint"); + if(depth++ > MAX_DEPTH) { + Debug.Log("reached max depth!"); + return; + } + switch(type) { + case Type.BAKED: + builder.Append(str); + break; + case Type.STRING: + builder.AppendFormat("\"{0}\"", str); + break; + case Type.NUMBER: +#if USEFLOAT + if(float.IsInfinity(n)) + builder.Append(INFINITY); + else if(float.IsNegativeInfinity(n)) + builder.Append(NEGINFINITY); + else if(float.IsNaN(n)) + builder.Append(NaN); +#else + if(double.IsInfinity(n)) + builder.Append(INFINITY); + else if(double.IsNegativeInfinity(n)) + builder.Append(NEGINFINITY); + else if(double.IsNaN(n)) + builder.Append(NaN); +#endif + else + builder.Append(n.ToString()); + break; + case Type.OBJECT: + builder.Append("{"); + if(list.Count > 0) { +#if(PRETTY) //for a bit more readability, comment the define above to disable system-wide + if(pretty) + builder.Append("\n"); +#endif + for(int i = 0; i < list.Count; i++) { + string key = keys[i]; + JSONObject obj = list[i]; + if(obj) { +#if(PRETTY) + if(pretty) + for(int j = 0; j < depth; j++) + builder.Append("\t"); //for a bit more readability +#endif + builder.AppendFormat("\"{0}\":", key); + obj.Stringify(depth, builder, pretty); + builder.Append(","); +#if(PRETTY) + if(pretty) + builder.Append("\n"); +#endif + } + } +#if(PRETTY) + if(pretty) + builder.Length -= 2; + else +#endif + builder.Length--; + } +#if(PRETTY) + if(pretty && list.Count > 0) { + builder.Append("\n"); + for(int j = 0; j < depth - 1; j++) + builder.Append("\t"); //for a bit more readability + } +#endif + builder.Append("}"); + break; + case Type.ARRAY: + builder.Append("["); + if(list.Count > 0) { +#if(PRETTY) + if(pretty) + builder.Append("\n"); //for a bit more readability +#endif + for(int i = 0; i < list.Count; i++) { + if(list[i]) { +#if(PRETTY) + if(pretty) + for(int j = 0; j < depth; j++) + builder.Append("\t"); //for a bit more readability +#endif + list[i].Stringify(depth, builder, pretty); + builder.Append(","); +#if(PRETTY) + if(pretty) + builder.Append("\n"); //for a bit more readability +#endif + } + } +#if(PRETTY) + if(pretty) + builder.Length -= 2; + else +#endif + builder.Length--; + } +#if(PRETTY) + if(pretty && list.Count > 0) { + builder.Append("\n"); + for(int j = 0; j < depth - 1; j++) + builder.Append("\t"); //for a bit more readability + } +#endif + builder.Append("]"); + break; + case Type.BOOL: + if(b) + builder.Append("true"); + else + builder.Append("false"); + break; + case Type.NULL: + builder.Append("null"); + break; + } + //Profiler.EndSample(); + } + #endregion + public static implicit operator WWWForm(JSONObject obj) { + WWWForm form = new WWWForm(); + for(int i = 0; i < obj.list.Count; i++) { + string key = i + ""; + if(obj.type == Type.OBJECT) + key = obj.keys[i]; + string val = obj.list[i].ToString(); + if(obj.list[i].type == Type.STRING) + val = val.Replace("\"", ""); + form.AddField(key, val); + } + return form; + } + public JSONObject this[int index] { + get { + if(list.Count > index) return list[index]; + return null; + } + set { + if(list.Count > index) + list[index] = value; + } + } + public JSONObject this[string index] { + get { + return GetField(index); + } + set { + SetField(index, value); + } + } + public override string ToString() { + return Print(); + } + public string ToString(bool pretty) { + return Print(pretty); + } + public Dictionary ToDictionary() { + if(type == Type.OBJECT) { + Dictionary result = new Dictionary(); + for(int i = 0; i < list.Count; i++) { + JSONObject val = list[i]; + switch(val.type) { + case Type.STRING: result.Add(keys[i], val.str); break; + case Type.NUMBER: result.Add(keys[i], val.n + ""); break; + case Type.BOOL: result.Add(keys[i], val.b + ""); break; + default: Debug.LogWarning("Omitting object: " + keys[i] + " in dictionary conversion"); break; + } + } + return result; + } + Debug.LogWarning("Tried to turn non-Object JSONObject into a dictionary"); + return null; + } + public static implicit operator bool(JSONObject o) { + return o != null; + } +#if POOLING + static bool pool = true; + public static void ClearPool() { + pool = false; + releaseQueue.Clear(); + pool = true; + } + + ~JSONObject() { + if(pool && releaseQueue.Count < MAX_POOL_SIZE) { + type = Type.NULL; + list = null; + keys = null; + str = ""; + n = 0; + b = false; + releaseQueue.Enqueue(this); + } + } +#endif +} \ No newline at end of file diff --git a/MoCha/Assets/Scripts/JSONObject.cs.meta b/MoCha/Assets/Scripts/JSONObject.cs.meta new file mode 100644 index 0000000..c095bab --- /dev/null +++ b/MoCha/Assets/Scripts/JSONObject.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: da64cad21a33a6444ac511376fcb771b +timeCreated: 1524716713 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MoCha/Assets/Scripts/OAuth2AccessToken.cs b/MoCha/Assets/Scripts/OAuth2AccessToken.cs new file mode 100644 index 0000000..e12b10e --- /dev/null +++ b/MoCha/Assets/Scripts/OAuth2AccessToken.cs @@ -0,0 +1,10 @@ +namespace Assets.Scripts +{ + public class OAuth2AccessToken + { + public string Token { get; set; } + public string TokenType { get; set; } // "Bearer" is expected + public int ExpiresIn { get; set; } //maybe convert this to a DateTime ? + public string RefreshToken { get; set; } + } +} \ No newline at end of file diff --git a/MoCha/Assets/Scripts/OAuth2AccessToken.cs.meta b/MoCha/Assets/Scripts/OAuth2AccessToken.cs.meta new file mode 100644 index 0000000..320c6d8 --- /dev/null +++ b/MoCha/Assets/Scripts/OAuth2AccessToken.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 2e4069fa83cbbb041aa91e83f7ece28e +timeCreated: 1524716713 +licenseType: Free +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MoCha/ProjectSettings/ProjectSettings.asset b/MoCha/ProjectSettings/ProjectSettings.asset index f93fdd1..d8a42fd 100644 Binary files a/MoCha/ProjectSettings/ProjectSettings.asset and b/MoCha/ProjectSettings/ProjectSettings.asset differ