diff --git a/Common/Common-Unity/Common-Unity.csproj b/Common/Common-Unity/Common-Unity.csproj index 4bb9263..9ab3475 100644 --- a/Common/Common-Unity/Common-Unity.csproj +++ b/Common/Common-Unity/Common-Unity.csproj @@ -30,6 +30,9 @@ Json\LCJsonConverter.cs + + Exception\LCException.cs + diff --git a/Common/Common/Common.csproj b/Common/Common/Common.csproj index 8157b01..669c947 100644 --- a/Common/Common/Common.csproj +++ b/Common/Common/Common.csproj @@ -13,5 +13,6 @@ + diff --git a/Storage/Storage/LCException.cs b/Common/Common/Exception/LCException.cs similarity index 50% rename from Storage/Storage/LCException.cs rename to Common/Common/Exception/LCException.cs index acf2249..1daebab 100644 --- a/Storage/Storage/LCException.cs +++ b/Common/Common/Exception/LCException.cs @@ -1,11 +1,20 @@ using System; -namespace LeanCloud.Storage { +namespace LeanCloud { + /// + /// LeanCloud 异常 + /// public class LCException : Exception { + /// + /// 错误码 + /// public int Code { get; set; } + /// + /// 错误信息 + /// public new string Message { get; set; } @@ -14,5 +23,9 @@ namespace LeanCloud.Storage { Code = code; Message = message; } + + public override string ToString() { + return $"{Code} - {Message}"; + } } } diff --git a/Realtime/Realtime/Internal/Controller/LCIMConversationController.cs b/Realtime/Realtime/Internal/Controller/LCIMConversationController.cs index 1f23ead..68798b0 100644 --- a/Realtime/Realtime/Internal/Controller/LCIMConversationController.cs +++ b/Realtime/Realtime/Internal/Controller/LCIMConversationController.cs @@ -52,7 +52,7 @@ namespace LeanCloud.Realtime.Internal.Controller { attrs["name"] = name; } if (properties != null) { - attrs = properties.Union(attrs) + attrs = properties.Union(attrs.Where(kv => !properties.ContainsKey(kv.Key))) .ToDictionary(k => k.Key, v => v.Value); } conv.Attr = new JsonObjectMessage { diff --git a/Storage/Storage-Unity/Storage-Unity.csproj b/Storage/Storage-Unity/Storage-Unity.csproj index 7ea411a..42d8e68 100644 --- a/Storage/Storage-Unity/Storage-Unity.csproj +++ b/Storage/Storage-Unity/Storage-Unity.csproj @@ -15,9 +15,6 @@ LCCloud.cs - - LCException.cs - LCFile.cs diff --git a/Storage/Storage.Test/ACLTest.cs b/Storage/Storage.Test/ACLTest.cs index 967da0c..733cd02 100644 --- a/Storage/Storage.Test/ACLTest.cs +++ b/Storage/Storage.Test/ACLTest.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using System.Threading.Tasks; +using System.Collections.ObjectModel; using LeanCloud; using LeanCloud.Storage; @@ -74,5 +75,19 @@ namespace Storage.Test { TestContext.WriteLine(account.ObjectId); Assert.NotNull(account.ObjectId); } + + [Test] + public async Task Serialization() { + await LCUser.Login("hello", "world"); + LCQuery query = new LCQuery("Account") { + IncludeACL = true + }; + query.OrderByDescending("createdAt"); + ReadOnlyCollection accounts = await query.Find(); + foreach (LCObject account in accounts) { + TestContext.WriteLine($"public read access: {account.ACL.PublicReadAccess}"); + TestContext.WriteLine($"public write access: {account.ACL.PublicWriteAccess}"); + } + } } } diff --git a/Storage/Storage.Test/ExceptionTest.cs b/Storage/Storage.Test/ExceptionTest.cs index 83f70b3..bcd80c0 100644 --- a/Storage/Storage.Test/ExceptionTest.cs +++ b/Storage/Storage.Test/ExceptionTest.cs @@ -1,5 +1,5 @@ using NUnit.Framework; -using LeanCloud.Storage; +using LeanCloud; namespace Storage.Test { public class ExceptionTest { diff --git a/Storage/Storage/Internal/Codec/LCDecoder.cs b/Storage/Storage/Internal/Codec/LCDecoder.cs index f9dd00a..b4b9770 100644 --- a/Storage/Storage/Internal/Codec/LCDecoder.cs +++ b/Storage/Storage/Internal/Codec/LCDecoder.cs @@ -68,10 +68,25 @@ namespace LeanCloud.Storage.Internal.Codec { } public static LCGeoPoint DecodeGeoPoint(IDictionary data) { - double latitude = double.Parse(data["latitude"].ToString()); - double longitude = double.Parse(data["longitude"].ToString()); + double latitude = Convert.ToDouble(data["latitude"]); + double longitude = Convert.ToDouble(data["longitude"]); LCGeoPoint geoPoint = new LCGeoPoint(latitude, longitude); return geoPoint; } + + public static LCACL DecodeACL(Dictionary dict) { + LCACL acl = new LCACL(); + foreach (KeyValuePair kv in dict) { + string key = kv.Key; + Dictionary access = kv.Value as Dictionary; + if (access.TryGetValue("read", out object ra)) { + acl.readAccess[key] = Convert.ToBoolean(ra); + } + if (access.TryGetValue("write", out object wa)) { + acl.writeAccess[key] = Convert.ToBoolean(wa); + } + } + return acl; + } } } diff --git a/Storage/Storage/Internal/Codec/LCEncoder.cs b/Storage/Storage/Internal/Codec/LCEncoder.cs index 383247b..a216200 100644 --- a/Storage/Storage/Internal/Codec/LCEncoder.cs +++ b/Storage/Storage/Internal/Codec/LCEncoder.cs @@ -83,18 +83,25 @@ namespace LeanCloud.Storage.Internal.Codec { } public static object EncodeACL(LCACL acl) { - HashSet readers = acl.readers; - HashSet writers = acl.writers; - HashSet union = new HashSet(readers); - union.UnionWith(writers); - Dictionary dict = new Dictionary(); - foreach (string k in union) { - dict[k] = new Dictionary { - { "read", readers.Contains(k) }, - { "write", writers.Contains(k) } - }; + HashSet keys = new HashSet(); + if (acl.readAccess.Count > 0) { + keys.UnionWith(acl.readAccess.Keys); } - return dict; + if (acl.writeAccess.Count > 0) { + keys.UnionWith(acl.writeAccess.Keys); + } + Dictionary result = new Dictionary(); + foreach (string key in keys) { + Dictionary access = new Dictionary(); + if (acl.readAccess.TryGetValue(key, out bool ra)) { + access["read"] = ra; + } + if (acl.writeAccess.TryGetValue(key, out bool wa)) { + access["write"] = wa; + } + result[key] = access; + } + return result; } public static object EncodeRelation(LCRelation relation) { diff --git a/Storage/Storage/Internal/Object/LCObjectData.cs b/Storage/Storage/Internal/Object/LCObjectData.cs index 152c445..bc5e90d 100644 --- a/Storage/Storage/Internal/Object/LCObjectData.cs +++ b/Storage/Storage/Internal/Object/LCObjectData.cs @@ -40,11 +40,16 @@ namespace LeanCloud.Storage.Internal.Object { } else if (key == "objectId") { objectData.ObjectId = value.ToString(); } else if (key == "createdAt" && DateTime.TryParse(value.ToString(), out DateTime createdAt)) { - objectData.CreatedAt = createdAt; + objectData.CreatedAt = createdAt.ToLocalTime(); } else if (key == "updatedAt" && DateTime.TryParse(value.ToString(), out DateTime updatedAt)) { - objectData.UpdatedAt = updatedAt; + objectData.UpdatedAt = updatedAt.ToLocalTime(); } else { - objectData.CustomPropertyDict[key] = LCDecoder.Decode(value); + if (key == "ACL" && + value is Dictionary dic) { + objectData.CustomPropertyDict[key] = LCDecoder.DecodeACL(dic); + } else { + objectData.CustomPropertyDict[key] = LCDecoder.Decode(value); + } } } return objectData; @@ -61,10 +66,10 @@ namespace LeanCloud.Storage.Internal.Object { dict["objectId"] = objectData.ObjectId; } if (objectData.CreatedAt != null) { - dict["createdAt"] = objectData.CreatedAt; + dict["createdAt"] = objectData.CreatedAt.ToUniversalTime(); } if (objectData.UpdatedAt != null) { - dict["updatedAt"] = objectData.UpdatedAt; + dict["updatedAt"] = objectData.UpdatedAt.ToUniversalTime(); } if (objectData.CustomPropertyDict != null) { foreach (KeyValuePair kv in objectData.CustomPropertyDict) { diff --git a/Storage/Storage/Internal/Query/LCCompositionalCondition.cs b/Storage/Storage/Internal/Query/LCCompositionalCondition.cs index 426a904..7217927 100644 --- a/Storage/Storage/Internal/Query/LCCompositionalCondition.cs +++ b/Storage/Storage/Internal/Query/LCCompositionalCondition.cs @@ -24,6 +24,10 @@ namespace LeanCloud.Storage.Internal.Query { get; set; } + public bool IncludeACL { + get; set; + } + public LCCompositionalCondition(string composition = And) { this.composition = composition; Skip = 0; @@ -217,6 +221,9 @@ namespace LeanCloud.Storage.Internal.Query { if (selectedKeys != null && selectedKeys.Count > 0) { dict["keys"] = string.Join(",", selectedKeys); } + if (IncludeACL) { + dict["returnACL"] = "true"; + } return dict; } diff --git a/Storage/Storage/LCACL.cs b/Storage/Storage/LCACL.cs index b7b6664..024c48f 100644 --- a/Storage/Storage/LCACL.cs +++ b/Storage/Storage/LCACL.cs @@ -10,22 +10,32 @@ namespace LeanCloud.Storage { const string RoleKeyPrefix = "role:"; - internal HashSet readers; - internal HashSet writers; + internal Dictionary readAccess = new Dictionary(); + internal Dictionary writeAccess = new Dictionary(); + + public static LCACL CreateWithOwner(LCUser owner) { + if (owner == null) { + throw new ArgumentNullException(nameof(owner)); + } + LCACL acl = new LCACL(); + acl.SetUserReadAccess(owner, true); + acl.SetUserWriteAccess(owner, true); + return acl; + } public bool PublicReadAccess { get { - return GetAccess(readers, PublicKey); + return GetAccess(readAccess, PublicKey); } set { - SetAccess(readers, PublicKey, value); + SetAccess(readAccess, PublicKey, value); } } public bool PublicWriteAccess { get { - return GetAccess(writers, PublicKey); + return GetAccess(writeAccess, PublicKey); } set { - SetAccess(writers, PublicKey, value); + SetAccess(writeAccess, PublicKey, value); } } @@ -33,28 +43,28 @@ namespace LeanCloud.Storage { if (string.IsNullOrEmpty(userId)) { throw new ArgumentNullException(nameof(userId)); } - return GetAccess(readers, userId); + return GetAccess(readAccess, userId); } public void SetUserIdReadAccess(string userId, bool value) { if (string.IsNullOrEmpty(userId)) { throw new ArgumentNullException(nameof(userId)); } - SetAccess(readers, userId, value); + SetAccess(readAccess, userId, value); } public bool GetUserIdWriteAccess(string userId) { if (string.IsNullOrEmpty(userId)) { throw new ArgumentNullException(nameof(userId)); } - return GetAccess(writers, userId); + return GetAccess(writeAccess, userId); } public void SetUserIdWriteAccess(string userId, bool value) { if (string.IsNullOrEmpty(userId)) { throw new ArgumentNullException(nameof(userId)); } - SetAccess(writers, userId, value); + SetAccess(writeAccess, userId, value); } public bool GetUserReadAccess(LCUser user) { @@ -90,7 +100,7 @@ namespace LeanCloud.Storage { throw new ArgumentNullException(nameof(role)); } string roleKey = $"{RoleKeyPrefix}{role.ObjectId}"; - return GetAccess(readers, roleKey); + return GetAccess(readAccess, roleKey); } public void SetRoleReadAccess(LCRole role, bool value) { @@ -98,7 +108,7 @@ namespace LeanCloud.Storage { throw new ArgumentNullException(nameof(role)); } string roleKey = $"{RoleKeyPrefix}{role.ObjectId}"; - SetAccess(readers, roleKey, value); + SetAccess(readAccess, roleKey, value); } public bool GetRoleWriteAccess(LCRole role) { @@ -106,7 +116,7 @@ namespace LeanCloud.Storage { throw new ArgumentNullException(nameof(role)); } string roleKey = $"{RoleKeyPrefix}{role.ObjectId}"; - return GetAccess(writers, roleKey); + return GetAccess(writeAccess, roleKey); } public void SetRoleWriteAccess(LCRole role, bool value) { @@ -114,34 +124,16 @@ namespace LeanCloud.Storage { throw new ArgumentNullException(nameof(role)); } string roleKey = $"{RoleKeyPrefix}{role.ObjectId}"; - SetAccess(writers, roleKey, value); + SetAccess(writeAccess, roleKey, value); } - public LCACL() { - readers = new HashSet(); - writers = new HashSet(); + bool GetAccess(Dictionary access, string key) { + return access.ContainsKey(key) ? + access[key] : false; } - public static LCACL CreateWithOwner(LCUser owner) { - if (owner == null) { - throw new ArgumentNullException(nameof(owner)); - } - LCACL acl = new LCACL(); - acl.SetUserReadAccess(owner, true); - acl.SetUserWriteAccess(owner, true); - return acl; - } - - bool GetAccess(HashSet set, string key) { - return set.Contains(key); - } - - void SetAccess(HashSet set, string key, bool value) { - if (value) { - set.Add(key); - } else { - set.Remove(key); - } + void SetAccess(Dictionary access, string key, bool value) { + access[key] = value; } } } diff --git a/Storage/Storage/LCCloud.cs b/Storage/Storage/LCCloud.cs index 830c8e0..01066c7 100644 --- a/Storage/Storage/LCCloud.cs +++ b/Storage/Storage/LCCloud.cs @@ -8,20 +8,31 @@ namespace LeanCloud.Storage { /// public static class LCCloud { /// - /// 调用云函数,结果为 Dictionary 类型 + /// 调用云函数 /// /// /// - /// - public static async Task> Run(string name, Dictionary parameters = null) { + /// 返回类型为 Dictionary + public static async Task> Run(string name, + Dictionary parameters = null) { string path = $"functions/{name}"; - Dictionary response = await LCApplication.HttpClient.Post>(path, data: parameters); + object encodeParams = LCEncoder.Encode(parameters); + Dictionary response = await LCApplication.HttpClient.Post>(path, + data: encodeParams); return response; } - public static async Task RPC(string name, Dictionary parameters = null) { + /// + /// 调用 RPC 云函数 + /// + /// + /// + /// 返回类型为 LCObject 容器类型 + public static async Task RPC(string name, object parameters = null) { string path = $"call/{name}"; - Dictionary response = await LCApplication.HttpClient.Post>(path, data: parameters); + object encodeParams = LCEncoder.Encode(parameters); + Dictionary response = await LCApplication.HttpClient.Post>(path, + data: encodeParams); return LCDecoder.Decode(response["result"]); } } diff --git a/Storage/Storage/LCObject.cs b/Storage/Storage/LCObject.cs index aacc54f..4b832d2 100644 --- a/Storage/Storage/LCObject.cs +++ b/Storage/Storage/LCObject.cs @@ -462,7 +462,10 @@ namespace LeanCloud.Storage { /// /// public override string ToString() { - return JsonConvert.SerializeObject(LCObjectData.Encode(data)); + Dictionary originalData = LCObjectData.Encode(data); + Dictionary currentData = estimatedData.Union(originalData.Where(kv => !estimatedData.ContainsKey(kv.Key))) + .ToDictionary(k => k.Key, v => v.Value); + return JsonConvert.SerializeObject(currentData); } /// diff --git a/Storage/Storage/LCQuery.cs b/Storage/Storage/LCQuery.cs index 7d391d9..586a91d 100644 --- a/Storage/Storage/LCQuery.cs +++ b/Storage/Storage/LCQuery.cs @@ -333,6 +333,17 @@ namespace LeanCloud.Storage { return this; } + /// + /// 是否包含 ACL + /// + public bool IncludeACL { + get { + return Condition.IncludeACL; + } set { + Condition.IncludeACL = value; + } + } + /// /// 跳过 /// diff --git a/Storage/Storage/LCUser.cs b/Storage/Storage/LCUser.cs index 7a9cc6c..b86001b 100644 --- a/Storage/Storage/LCUser.cs +++ b/Storage/Storage/LCUser.cs @@ -49,13 +49,13 @@ namespace LeanCloud.Storage { public bool EmailVerified { get { - return (bool)this["emailVerified"]; + return Convert.ToBoolean(this["emailVerified"]); } } public bool MobileVerified { get { - return (bool)this["mobilePhoneVerified"]; + return Convert.ToBoolean(this["mobilePhoneVerified"]); } }