From 1c70bda35c1ac02188141d8da5a7e840b4aedae9 Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 14:07:35 +0800 Subject: [PATCH 01/13] =?UTF-8?q?chore:=20=E5=B0=86=20json=20=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E5=8C=96=E5=B7=A5=E5=85=B7=E6=8F=90=E5=87=BA=E6=9D=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Common/Common/Json/LCJsonConverter.cs | 37 ++++++++++++++++ .../Internal/LCLiveQueryHeartBeat.cs | 7 +++ LiveQuery/LiveQuery/LCQueryExtension.cs | 7 +++ Sample/LiveQueryApp/LiveQueryApp.csproj | 8 ++++ Sample/LiveQueryApp/Program.cs | 9 ++++ .../SingleThreadSynchronizationContext.cs | 43 +++++++++++++++++++ 6 files changed, 111 insertions(+) create mode 100644 Common/Common/Json/LCJsonConverter.cs create mode 100644 LiveQuery/LiveQuery/Internal/LCLiveQueryHeartBeat.cs create mode 100644 LiveQuery/LiveQuery/LCQueryExtension.cs create mode 100644 Sample/LiveQueryApp/LiveQueryApp.csproj create mode 100644 Sample/LiveQueryApp/Program.cs create mode 100644 Sample/LiveQueryApp/SingleThreadSynchronizationContext.cs diff --git a/Common/Common/Json/LCJsonConverter.cs b/Common/Common/Json/LCJsonConverter.cs new file mode 100644 index 0000000..3e78108 --- /dev/null +++ b/Common/Common/Json/LCJsonConverter.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace LeanCloud.Common { + public class LCJsonConverter : JsonConverter { + public override bool CanConvert(Type objectType) { + return objectType == typeof(object); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + serializer.Serialize(writer, value); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { + if (reader.TokenType == JsonToken.StartObject) { + var obj = new Dictionary(); + serializer.Populate(reader, obj); + return obj; + } + if (reader.TokenType == JsonToken.StartArray) { + var arr = new List(); + serializer.Populate(reader, arr); + return arr; + } + if (reader.TokenType == JsonToken.Integer) { + if ((long)reader.Value < int.MaxValue) { + return Convert.ToInt32(reader.Value); + } + } + + return serializer.Deserialize(reader); + } + + public readonly static LCJsonConverter Default = new LCJsonConverter(); + } +} diff --git a/LiveQuery/LiveQuery/Internal/LCLiveQueryHeartBeat.cs b/LiveQuery/LiveQuery/Internal/LCLiveQueryHeartBeat.cs new file mode 100644 index 0000000..a856d91 --- /dev/null +++ b/LiveQuery/LiveQuery/Internal/LCLiveQueryHeartBeat.cs @@ -0,0 +1,7 @@ +using System; +namespace LiveQuery.Internal { + public class LCLiveQueryHeartBeat { + public LCLiveQueryHeartBeat() { + } + } +} diff --git a/LiveQuery/LiveQuery/LCQueryExtension.cs b/LiveQuery/LiveQuery/LCQueryExtension.cs new file mode 100644 index 0000000..5877435 --- /dev/null +++ b/LiveQuery/LiveQuery/LCQueryExtension.cs @@ -0,0 +1,7 @@ +using System; +namespace LiveQuery { + public class LCQueryExtension { + public LCQueryExtension() { + } + } +} diff --git a/Sample/LiveQueryApp/LiveQueryApp.csproj b/Sample/LiveQueryApp/LiveQueryApp.csproj new file mode 100644 index 0000000..c73e0d1 --- /dev/null +++ b/Sample/LiveQueryApp/LiveQueryApp.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/Sample/LiveQueryApp/Program.cs b/Sample/LiveQueryApp/Program.cs new file mode 100644 index 0000000..2888d80 --- /dev/null +++ b/Sample/LiveQueryApp/Program.cs @@ -0,0 +1,9 @@ +using System; + +namespace LiveQueryApp { + class Program { + static void Main(string[] args) { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/Sample/LiveQueryApp/SingleThreadSynchronizationContext.cs b/Sample/LiveQueryApp/SingleThreadSynchronizationContext.cs new file mode 100644 index 0000000..fdfb2c9 --- /dev/null +++ b/Sample/LiveQueryApp/SingleThreadSynchronizationContext.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace RealtimeApp { + /// + /// 单线程环境,用于控制台应用 await 返回 + /// + public class SingleThreadSynchronizationContext : SynchronizationContext { + private readonly BlockingCollection> queue = new BlockingCollection>(); + + public override void Post(SendOrPostCallback d, object state) { + queue.Add(new KeyValuePair(d, state)); + } + + public void RunOnCurrentThread() { + while (queue.TryTake(out KeyValuePair workItem, Timeout.Infinite)) { + workItem.Key(workItem.Value); + } + } + + public void Complete() { + queue.CompleteAdding(); + } + + public static void Run(Func func) { + SynchronizationContext prevContext = Current; + try { + SingleThreadSynchronizationContext syncContext = new SingleThreadSynchronizationContext(); + SetSynchronizationContext(syncContext); + + Task t = func(); + syncContext.RunOnCurrentThread(); + + t.GetAwaiter().GetResult(); + } finally { + SetSynchronizationContext(prevContext); + } + } + } +} From 8a779af880b96754a4d9d0322e399e8c3caafc4b Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 14:13:08 +0800 Subject: [PATCH 02/13] * LCJsonConverter.cs: * Common-Unity.csproj: chore --- Common/Common-Unity/Common-Unity.csproj | 3 ++ .../Storage/Internal/Http/LCJsonConverter.cs | 35 ------------------- 2 files changed, 3 insertions(+), 35 deletions(-) delete mode 100644 Storage/Storage/Internal/Http/LCJsonConverter.cs diff --git a/Common/Common-Unity/Common-Unity.csproj b/Common/Common-Unity/Common-Unity.csproj index 63689bf..c8928d6 100644 --- a/Common/Common-Unity/Common-Unity.csproj +++ b/Common/Common-Unity/Common-Unity.csproj @@ -27,6 +27,9 @@ Task\LCTaskExtensions.cs + + Json\LCJsonConverter.cs + diff --git a/Storage/Storage/Internal/Http/LCJsonConverter.cs b/Storage/Storage/Internal/Http/LCJsonConverter.cs deleted file mode 100644 index d79bd9d..0000000 --- a/Storage/Storage/Internal/Http/LCJsonConverter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace LeanCloud.Storage.Internal { - public class LCJsonConverter : JsonConverter { - public override bool CanConvert(Type objectType) { - return objectType == typeof(object); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - serializer.Serialize(writer, value); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - if (reader.TokenType == JsonToken.StartObject) { - var obj = new Dictionary(); - serializer.Populate(reader, obj); - return obj; - } - if (reader.TokenType == JsonToken.StartArray) { - var arr = new List(); - serializer.Populate(reader, arr); - return arr; - } - if (reader.TokenType == JsonToken.Integer) { - if ((long)reader.Value < int.MaxValue) { - return Convert.ToInt32(reader.Value); - } - } - - return serializer.Deserialize(reader); - } - } -} From 8de256993a845ef512d3e386ef31c69ed72f45c4 Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 14:14:23 +0800 Subject: [PATCH 03/13] =?UTF-8?q?chore:=20=E8=B0=83=E6=95=B4=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Storage/Storage-Unity/Storage-Unity.csproj | 3 - .../Storage/Internal/Object/LCObjectData.cs | 18 ++--- .../Query/LCCompositionalCondition.cs | 4 +- Storage/Storage/LCObject.cs | 4 +- Storage/Storage/LCQuery.cs | 74 ++++++++++--------- 5 files changed, 51 insertions(+), 52 deletions(-) diff --git a/Storage/Storage-Unity/Storage-Unity.csproj b/Storage/Storage-Unity/Storage-Unity.csproj index 7f8ff6f..368a7a3 100644 --- a/Storage/Storage-Unity/Storage-Unity.csproj +++ b/Storage/Storage-Unity/Storage-Unity.csproj @@ -99,9 +99,6 @@ Internal\Http\LCHttpClient.cs - - Internal\Http\LCJsonConverter.cs - Internal\Query\LCEqualCondition.cs diff --git a/Storage/Storage/Internal/Object/LCObjectData.cs b/Storage/Storage/Internal/Object/LCObjectData.cs index d941068..152c445 100644 --- a/Storage/Storage/Internal/Object/LCObjectData.cs +++ b/Storage/Storage/Internal/Object/LCObjectData.cs @@ -4,30 +4,30 @@ using System.Collections.Generic; using LeanCloud.Storage.Internal.Codec; namespace LeanCloud.Storage.Internal.Object { - internal class LCObjectData { - internal string ClassName { + public class LCObjectData { + public string ClassName { get; set; } - internal string ObjectId { + public string ObjectId { get; set; } - internal DateTime CreatedAt { + public DateTime CreatedAt { get; set; } - internal DateTime UpdatedAt { + public DateTime UpdatedAt { get; set; } - internal Dictionary CustomPropertyDict; + public Dictionary CustomPropertyDict; - internal LCObjectData() { + public LCObjectData() { CustomPropertyDict = new Dictionary(); } - internal static LCObjectData Decode(IDictionary dict) { + public static LCObjectData Decode(IDictionary dict) { if (dict == null) { return null; } @@ -50,7 +50,7 @@ namespace LeanCloud.Storage.Internal.Object { return objectData; } - internal static Dictionary Encode(LCObjectData objectData) { + public static Dictionary Encode(LCObjectData objectData) { if (objectData == null) { return null; } diff --git a/Storage/Storage/Internal/Query/LCCompositionalCondition.cs b/Storage/Storage/Internal/Query/LCCompositionalCondition.cs index 54098e3..426a904 100644 --- a/Storage/Storage/Internal/Query/LCCompositionalCondition.cs +++ b/Storage/Storage/Internal/Query/LCCompositionalCondition.cs @@ -118,7 +118,7 @@ namespace LeanCloud.Storage.Internal.Query { public void WhereMatchesQuery(string key, LCQuery query) where K : LCObject { Dictionary inQuery = new Dictionary { - { "where", query.condition }, + { "where", query.Condition }, { "className", query.ClassName } }; AddOperation(key, "$inQuery", inQuery); @@ -126,7 +126,7 @@ namespace LeanCloud.Storage.Internal.Query { public void WhereDoesNotMatchQuery(string key, LCQuery query) where K : LCObject { Dictionary inQuery = new Dictionary { - { "where", query.condition }, + { "where", query.Condition }, { "className", query.ClassName } }; AddOperation(key, "$notInQuery", inQuery); diff --git a/Storage/Storage/LCObject.cs b/Storage/Storage/LCObject.cs index 84ad22b..aacc54f 100644 --- a/Storage/Storage/LCObject.cs +++ b/Storage/Storage/LCObject.cs @@ -93,7 +93,7 @@ namespace LeanCloud.Storage { return obj; } - internal static LCObject Create(string className) { + public static LCObject Create(string className) { if (subclassNameDict.TryGetValue(className, out LCSubclassInfo subclassInfo)) { return subclassInfo.Constructor.Invoke(); } @@ -494,7 +494,7 @@ namespace LeanCloud.Storage { } } - internal void Merge(LCObjectData objectData) { + public void Merge(LCObjectData objectData) { data.ClassName = objectData.ClassName ?? data.ClassName; data.ObjectId = objectData.ObjectId ?? data.ObjectId; data.CreatedAt = objectData.CreatedAt != null ? objectData.CreatedAt : data.CreatedAt; diff --git a/Storage/Storage/LCQuery.cs b/Storage/Storage/LCQuery.cs index f86d203..6b2fd44 100644 --- a/Storage/Storage/LCQuery.cs +++ b/Storage/Storage/LCQuery.cs @@ -17,11 +17,13 @@ namespace LeanCloud.Storage { get; private set; } - internal LCCompositionalCondition condition; + public LCCompositionalCondition Condition { + get; internal set; + } public LCQuery(string className) { ClassName = className; - condition = new LCCompositionalCondition(); + Condition = new LCCompositionalCondition(); } /// @@ -31,7 +33,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereEqualTo(string key, object value) { - condition.WhereEqualTo(key, value); + Condition.WhereEqualTo(key, value); return this; } @@ -42,7 +44,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereNotEqualTo(string key, object value) { - condition.WhereNotEqualTo(key, value); + Condition.WhereNotEqualTo(key, value); return this; } @@ -53,7 +55,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereContainedIn(string key, IEnumerable values) { - condition.WhereContainedIn(key, values); + Condition.WhereContainedIn(key, values); return this; } @@ -64,7 +66,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereNotContainedIn(string key, IEnumerable values) { - condition.WhereNotContainedIn(key, values); + Condition.WhereNotContainedIn(key, values); return this; } @@ -75,7 +77,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereContainsAll(string key, IEnumerable values) { - condition.WhereContainsAll(key, values); + Condition.WhereContainsAll(key, values); return this; } @@ -85,7 +87,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereExists(string key) { - condition.WhereExists(key); + Condition.WhereExists(key); return this; } @@ -95,7 +97,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereDoesNotExist(string key) { - condition.WhereDoesNotExist(key); + Condition.WhereDoesNotExist(key); return this; } @@ -106,7 +108,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereSizeEqualTo(string key, int size) { - condition.WhereSizeEqualTo(key, size); + Condition.WhereSizeEqualTo(key, size); return this; } @@ -117,7 +119,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereGreaterThan(string key, object value) { - condition.WhereGreaterThan(key, value); + Condition.WhereGreaterThan(key, value); return this; } @@ -128,7 +130,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereGreaterThanOrEqualTo(string key, object value) { - condition.WhereGreaterThanOrEqualTo(key, value); + Condition.WhereGreaterThanOrEqualTo(key, value); return this; } @@ -139,7 +141,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereLessThan(string key, object value) { - condition.WhereLessThan(key, value); + Condition.WhereLessThan(key, value); return this; } @@ -150,7 +152,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereLessThanOrEqualTo(string key, object value) { - condition.WhereLessThanOrEqualTo(key, value); + Condition.WhereLessThanOrEqualTo(key, value); return this; } @@ -161,7 +163,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereNear(string key, LCGeoPoint point) { - condition.WhereNear(key, point); + Condition.WhereNear(key, point); return this; } @@ -173,7 +175,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereWithinGeoBox(string key, LCGeoPoint southwest, LCGeoPoint northeast) { - condition.WhereWithinGeoBox(key, southwest, northeast); + Condition.WhereWithinGeoBox(key, southwest, northeast); return this; } @@ -184,7 +186,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereRelatedTo(LCObject parent, string key) { - condition.WhereRelatedTo(parent, key); + Condition.WhereRelatedTo(parent, key); return this; } @@ -195,7 +197,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereStartsWith(string key, string prefix) { - condition.WhereStartsWith(key, prefix); + Condition.WhereStartsWith(key, prefix); return this; } @@ -206,7 +208,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereEndsWith(string key, string suffix) { - condition.WhereEndsWith(key, suffix); + Condition.WhereEndsWith(key, suffix); return this; } @@ -217,7 +219,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereContains(string key, string subString) { - condition.WhereContains(key, subString); + Condition.WhereContains(key, subString); return this; } @@ -229,7 +231,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereMatches(string key, string regex, string modifiers = null) { - condition.WhereMatches(key, regex, modifiers); + Condition.WhereMatches(key, regex, modifiers); return this; } @@ -240,7 +242,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereMatchesQuery(string key, LCQuery query) where K : LCObject { - condition.WhereMatchesQuery(key, query); + Condition.WhereMatchesQuery(key, query); return this; } @@ -252,7 +254,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery WhereDoesNotMatchQuery(string key, LCQuery query) where K : LCObject { - condition.WhereDoesNotMatchQuery(key, query); + Condition.WhereDoesNotMatchQuery(key, query); return this; } @@ -262,7 +264,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery OrderByAscending(string key) { - condition.OrderByAscending(key); + Condition.OrderByAscending(key); return this; } @@ -272,7 +274,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery OrderByDescending(string key) { - condition.OrderByDescending(key); + Condition.OrderByDescending(key); return this; } @@ -282,7 +284,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery AddAscendingOrder(string key) { - condition.AddAscendingOrder(key); + Condition.AddAscendingOrder(key); return this; } @@ -292,7 +294,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery AddDescendingOrder(string key) { - condition.AddDescendingOrder(key); + Condition.AddDescendingOrder(key); return this; } @@ -302,7 +304,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery Include(string key) { - condition.Include(key); + Condition.Include(key); return this; } @@ -312,7 +314,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery Select(string key) { - condition.Select(key); + Condition.Select(key); return this; } @@ -322,7 +324,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery Skip(int value) { - condition.Skip = value; + Condition.Skip = value; return this; } @@ -332,7 +334,7 @@ namespace LeanCloud.Storage { /// /// public LCQuery Limit(int value) { - condition.Limit = value; + Condition.Limit = value; return this; } @@ -396,7 +398,7 @@ namespace LeanCloud.Storage { throw new Exception("All of the queries in an or query must be on the same class."); } className = query.ClassName; - compositionQuery.condition.Add(query.condition); + compositionQuery.Condition.Add(query.Condition); } compositionQuery.ClassName = className; return compositionQuery; @@ -407,25 +409,25 @@ namespace LeanCloud.Storage { throw new ArgumentNullException(nameof(queries)); } LCQuery compositionQuery = new LCQuery(null); - compositionQuery.condition = new LCCompositionalCondition(LCCompositionalCondition.Or); + compositionQuery.Condition = new LCCompositionalCondition(LCCompositionalCondition.Or); string className = null; foreach (LCQuery query in queries) { if (className != null && className != query.ClassName) { throw new Exception("All of the queries in an or query must be on the same class."); } className = query.ClassName; - compositionQuery.condition.Add(query.condition); + compositionQuery.Condition.Add(query.Condition); } compositionQuery.ClassName = className; return compositionQuery; } Dictionary BuildParams() { - return condition.BuildParams(); + return Condition.BuildParams(); } internal string BuildWhere() { - return condition.BuildWhere(); + return Condition.BuildWhere(); } } } From fb5c0754b2a74f1ff2ed7768d5dff93a1288f172 Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 15:54:50 +0800 Subject: [PATCH 04/13] =?UTF-8?q?chore:=20=E8=B0=83=E6=95=B4=20LCQuery=20?= =?UTF-8?q?=E4=B8=8E=20LCQuery=20=E7=9A=84=E5=85=B3=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Storage/Storage/LCQuery.cs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Storage/Storage/LCQuery.cs b/Storage/Storage/LCQuery.cs index 6b2fd44..7d391d9 100644 --- a/Storage/Storage/LCQuery.cs +++ b/Storage/Storage/LCQuery.cs @@ -8,13 +8,9 @@ using LeanCloud.Storage.Internal.Query; using LeanCloud.Storage.Internal.Object; namespace LeanCloud.Storage { - /// - /// 查询类 - /// - /// - public class LCQuery where T : LCObject { + public class LCQuery { public string ClassName { - get; private set; + get; internal set; } public LCCompositionalCondition Condition { @@ -26,6 +22,25 @@ namespace LeanCloud.Storage { Condition = new LCCompositionalCondition(); } + internal Dictionary BuildParams() { + return Condition.BuildParams(); + } + + internal string BuildWhere() { + return Condition.BuildWhere(); + } + } + + /// + /// 查询类 + /// + /// + public class LCQuery : LCQuery where T : LCObject { + public LCQuery(string className) : + base(className) { + + } + /// /// 等于 /// @@ -421,13 +436,5 @@ namespace LeanCloud.Storage { compositionQuery.ClassName = className; return compositionQuery; } - - Dictionary BuildParams() { - return Condition.BuildParams(); - } - - internal string BuildWhere() { - return Condition.BuildWhere(); - } } } From 6ef31f1188f343bc23d407b2e4c90d4820ffd7ac Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 16:11:46 +0800 Subject: [PATCH 05/13] chore --- Storage/Storage/LCUser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Storage/Storage/LCUser.cs b/Storage/Storage/LCUser.cs index df51104..7a9cc6c 100644 --- a/Storage/Storage/LCUser.cs +++ b/Storage/Storage/LCUser.cs @@ -85,7 +85,7 @@ namespace LeanCloud.Storage { } - internal LCUser(LCObjectData objectData) : this() { + public LCUser(LCObjectData objectData) : this() { Merge(objectData); } From ff8f12ddc08ca4f04f0fabf464b93c56b9fa8559 Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 16:25:01 +0800 Subject: [PATCH 06/13] chore --- Storage/Storage/Internal/Http/LCHttpClient.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Storage/Storage/Internal/Http/LCHttpClient.cs b/Storage/Storage/Internal/Http/LCHttpClient.cs index af4eb76..5a4bb6a 100644 --- a/Storage/Storage/Internal/Http/LCHttpClient.cs +++ b/Storage/Storage/Internal/Http/LCHttpClient.cs @@ -61,7 +61,8 @@ namespace LeanCloud.Storage.Internal.Http { LCHttpUtils.PrintResponse(response, resultString); if (response.IsSuccessStatusCode) { - T ret = JsonConvert.DeserializeObject(resultString, new LCJsonConverter()); + T ret = JsonConvert.DeserializeObject(resultString, + LCJsonConverter.Default); return ret; } throw HandleErrorResponse(response.StatusCode, resultString); @@ -94,7 +95,8 @@ namespace LeanCloud.Storage.Internal.Http { LCHttpUtils.PrintResponse(response, resultString); if (response.IsSuccessStatusCode) { - T ret = JsonConvert.DeserializeObject(resultString, new LCJsonConverter()); + T ret = JsonConvert.DeserializeObject(resultString, + LCJsonConverter.Default); return ret; } throw HandleErrorResponse(response.StatusCode, resultString); @@ -127,7 +129,8 @@ namespace LeanCloud.Storage.Internal.Http { LCHttpUtils.PrintResponse(response, resultString); if (response.IsSuccessStatusCode) { - T ret = JsonConvert.DeserializeObject(resultString, new LCJsonConverter()); + T ret = JsonConvert.DeserializeObject(resultString, + LCJsonConverter.Default); return ret; } throw HandleErrorResponse(response.StatusCode, resultString); @@ -150,7 +153,8 @@ namespace LeanCloud.Storage.Internal.Http { LCHttpUtils.PrintResponse(response, resultString); if (response.IsSuccessStatusCode) { - Dictionary ret = JsonConvert.DeserializeObject>(resultString, new LCJsonConverter()); + Dictionary ret = JsonConvert.DeserializeObject>(resultString, + LCJsonConverter.Default); return; } throw HandleErrorResponse(response.StatusCode, resultString); @@ -161,7 +165,8 @@ namespace LeanCloud.Storage.Internal.Http { string message = responseContent; try { // 尝试获取 LeanCloud 返回错误信息 - Dictionary error = JsonConvert.DeserializeObject>(responseContent, new LCJsonConverter()); + Dictionary error = JsonConvert.DeserializeObject>(responseContent, + LCJsonConverter.Default); code = (int)error["code"]; message = error["error"].ToString(); } catch (Exception e) { From 04e8229f898ba5e6fccdd5fd02aac4fddbce2705 Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 16:28:55 +0800 Subject: [PATCH 07/13] =?UTF-8?q?chore:=20=E8=B0=83=E6=95=B4=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E8=AE=BF=E9=97=AE=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Internal/Connection/LCConnection.cs | 9 +++-- .../Controller/LCIMConversationController.cs | 9 ++--- .../Realtime/Internal/Router/LCRTMRouter.cs | 9 +++-- .../Realtime/Internal/Router/LCRTMServer.cs | 16 ++++----- .../Internal/WebSocket/LCWebSocketClient.cs | 34 +++++++++++++------ Realtime/Realtime/Message/LCIMTypedMessage.cs | 4 +-- 6 files changed, 49 insertions(+), 32 deletions(-) diff --git a/Realtime/Realtime/Internal/Connection/LCConnection.cs b/Realtime/Realtime/Internal/Connection/LCConnection.cs index 2add3be..c52a2d9 100644 --- a/Realtime/Realtime/Internal/Connection/LCConnection.cs +++ b/Realtime/Realtime/Internal/Connection/LCConnection.cs @@ -33,6 +33,11 @@ namespace LeanCloud.Realtime.Internal.Connection { /// private const int HEART_BEAT_INTERVAL = 30000; + /// + /// 子协议 + /// + private const string SUB_PROTOCOL = "lc.protobuf2.3"; + /// /// 通知事件 /// @@ -79,11 +84,11 @@ namespace LeanCloud.Realtime.Internal.Connection { LCRTMServer rtmServer = await router.GetServer(); try { LCLogger.Debug($"Primary Server"); - await client.Connect(rtmServer.Primary); + await client.Connect(rtmServer.Primary, SUB_PROTOCOL); } catch (Exception e) { LCLogger.Error(e); LCLogger.Debug($"Secondary Server"); - await client.Connect(rtmServer.Secondary); + await client.Connect(rtmServer.Secondary, SUB_PROTOCOL); } } catch (Exception e) { throw e; diff --git a/Realtime/Realtime/Internal/Controller/LCIMConversationController.cs b/Realtime/Realtime/Internal/Controller/LCIMConversationController.cs index cd7836a..af0455f 100644 --- a/Realtime/Realtime/Internal/Controller/LCIMConversationController.cs +++ b/Realtime/Realtime/Internal/Controller/LCIMConversationController.cs @@ -5,7 +5,6 @@ using System.Collections.ObjectModel; using System.Threading.Tasks; using Newtonsoft.Json; using LeanCloud.Realtime.Internal.Protocol; -using LeanCloud.Storage.Internal; using LeanCloud.Storage.Internal.Codec; using LeanCloud.Common; @@ -441,7 +440,8 @@ namespace LeanCloud.Realtime.Internal.Controller { command.ConvMessage = convMessage; GenericCommand response = await Connection.SendRequest(command); JsonObjectMessage results = response.ConvMessage.Results; - List convs = JsonConvert.DeserializeObject>(results.Data, new LCJsonConverter()); + List convs = JsonConvert.DeserializeObject>(results.Data, + LCJsonConverter.Default); return convs.Select(item => { Dictionary conv = item as Dictionary; string convId = conv["objectId"] as string; @@ -478,7 +478,8 @@ namespace LeanCloud.Realtime.Internal.Controller { request.ConvMessage = convMessage; GenericCommand response = await Connection.SendRequest(request); JsonObjectMessage results = response.ConvMessage.Results; - List convs = JsonConvert.DeserializeObject>(results.Data, new LCJsonConverter()); + List convs = JsonConvert.DeserializeObject>(results.Data, + LCJsonConverter.Default); List convList = convs.Select(item => { LCIMTemporaryConversation temporaryConversation = new LCIMTemporaryConversation(Client); temporaryConversation.MergeFrom(item as Dictionary); @@ -800,7 +801,7 @@ namespace LeanCloud.Realtime.Internal.Controller { private async Task OnPropertiesUpdated(ConvCommand conv) { LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid); Dictionary updatedAttr = JsonConvert.DeserializeObject>(conv.AttrModified.Data, - new LCJsonConverter()); + LCJsonConverter.Default); // 更新内存数据 conversation.MergeInfo(updatedAttr); Client.OnConversationInfoUpdated?.Invoke(conversation, diff --git a/Realtime/Realtime/Internal/Router/LCRTMRouter.cs b/Realtime/Realtime/Internal/Router/LCRTMRouter.cs index 418ddb7..c1207b0 100644 --- a/Realtime/Realtime/Internal/Router/LCRTMRouter.cs +++ b/Realtime/Realtime/Internal/Router/LCRTMRouter.cs @@ -1,7 +1,6 @@ using System; using System.Threading.Tasks; using System.Net.Http; -using LeanCloud.Storage.Internal; using LeanCloud.Common; using Newtonsoft.Json; @@ -9,7 +8,7 @@ namespace LeanCloud.Realtime.Internal.Router { /// /// RTM Router /// - internal class LCRTMRouter { + public class LCRTMRouter { /// /// 请求超时 /// @@ -17,14 +16,14 @@ namespace LeanCloud.Realtime.Internal.Router { private LCRTMServer rtmServer; - internal LCRTMRouter() { + public LCRTMRouter() { } /// /// 获取服务器地址 /// /// - internal async Task GetServer() { + public async Task GetServer() { if (rtmServer == null || !rtmServer.IsValid) { await Fetch(); } @@ -53,7 +52,7 @@ namespace LeanCloud.Realtime.Internal.Router { response.Dispose(); LCHttpUtils.PrintResponse(response, resultString); - rtmServer = JsonConvert.DeserializeObject(resultString, new LCJsonConverter()); + rtmServer = JsonConvert.DeserializeObject(resultString, LCJsonConverter.Default); return rtmServer; } diff --git a/Realtime/Realtime/Internal/Router/LCRTMServer.cs b/Realtime/Realtime/Internal/Router/LCRTMServer.cs index 45a5176..0e6ad07 100644 --- a/Realtime/Realtime/Internal/Router/LCRTMServer.cs +++ b/Realtime/Realtime/Internal/Router/LCRTMServer.cs @@ -2,38 +2,38 @@ using Newtonsoft.Json; namespace LeanCloud.Realtime.Internal.Router { - internal class LCRTMServer { + public class LCRTMServer { [JsonProperty("groupId")] - internal string GroupId { + public string GroupId { get; set; } [JsonProperty("groupUrl")] - internal string GroupUrl { + public string GroupUrl { get; set; } [JsonProperty("server")] - internal string Primary { + public string Primary { get; set; } [JsonProperty("secondary")] - internal string Secondary { + public string Secondary { get; set; } [JsonProperty("ttl")] - internal int Ttl { + public int Ttl { get; set; } DateTimeOffset createdAt; - internal LCRTMServer() { + public LCRTMServer() { createdAt = DateTimeOffset.Now; } - internal bool IsValid => DateTimeOffset.Now < createdAt + TimeSpan.FromSeconds(Ttl); + public bool IsValid => DateTimeOffset.Now < createdAt + TimeSpan.FromSeconds(Ttl); } } diff --git a/Realtime/Realtime/Internal/WebSocket/LCWebSocketClient.cs b/Realtime/Realtime/Internal/WebSocket/LCWebSocketClient.cs index 09d599e..ca42dec 100644 --- a/Realtime/Realtime/Internal/WebSocket/LCWebSocketClient.cs +++ b/Realtime/Realtime/Internal/WebSocket/LCWebSocketClient.cs @@ -1,12 +1,13 @@ using System; using System.Threading.Tasks; using System.Net.WebSockets; +using System.Text; namespace LeanCloud.Realtime.Internal.WebSocket { /// /// WebSocket 客户端,负责底层连接和事件,只与通信协议相关 /// - internal class LCWebSocketClient { + public class LCWebSocketClient { // .net standard 2.0 好像在拼合 Frame 时有 bug,所以将这个值调整大一些 private const int RECV_BUFFER_SIZE = 1024 * 5; @@ -23,12 +24,12 @@ namespace LeanCloud.Realtime.Internal.WebSocket { /// /// 消息事件 /// - internal Action OnMessage; + public Action OnMessage; /// /// 连接关闭 /// - internal Action OnClose; + public Action OnClose; private ClientWebSocket ws; @@ -37,11 +38,14 @@ namespace LeanCloud.Realtime.Internal.WebSocket { /// /// /// - internal async Task Connect(string server) { + public async Task Connect(string server, + string subProtocol = null) { LCLogger.Debug($"Connecting WebSocket: {server}"); Task timeoutTask = Task.Delay(CONNECT_TIMEOUT); ws = new ClientWebSocket(); - ws.Options.AddSubProtocol("lc.protobuf2.3"); + if (!string.IsNullOrEmpty(subProtocol)) { + ws.Options.AddSubProtocol(subProtocol); + } Task connectTask = ws.ConnectAsync(new Uri(server), default); if (await Task.WhenAny(connectTask, timeoutTask) == connectTask) { LCLogger.Debug($"Connected WebSocket: {server}"); @@ -57,7 +61,7 @@ namespace LeanCloud.Realtime.Internal.WebSocket { /// 主动关闭连接 /// /// - internal async Task Close() { + public async Task Close() { LCLogger.Debug("Closing WebSocket"); OnMessage = null; OnClose = null; @@ -81,11 +85,12 @@ namespace LeanCloud.Realtime.Internal.WebSocket { /// /// /// - internal async Task Send(byte[] data) { + public async Task Send(byte[] data, + WebSocketMessageType messageType = WebSocketMessageType.Binary) { ArraySegment bytes = new ArraySegment(data); if (ws.State == WebSocketState.Open) { try { - await ws.SendAsync(bytes, WebSocketMessageType.Binary, true, default); + await ws.SendAsync(bytes, messageType, true, default); } catch (Exception e) { LCLogger.Error(e); throw e; @@ -97,6 +102,15 @@ namespace LeanCloud.Realtime.Internal.WebSocket { } } + /// + /// 发送文本数据 + /// + /// + /// + public async Task Send(string text) { + await Send(Encoding.UTF8.GetBytes(text), WebSocketMessageType.Text); + } + /// /// 接收数据 /// @@ -119,14 +133,12 @@ namespace LeanCloud.Realtime.Internal.WebSocket { HandleExceptionClose(); } } - } else if (result.MessageType == WebSocketMessageType.Binary) { + } else { // 拼合 WebSocket Message int length = result.Count; byte[] data = new byte[length]; Array.Copy(buffer, data, length); OnMessage?.Invoke(data); - } else { - LCLogger.Error($"Error message type: {result.MessageType}"); } } } catch (Exception e) { diff --git a/Realtime/Realtime/Message/LCIMTypedMessage.cs b/Realtime/Realtime/Message/LCIMTypedMessage.cs index d43b40d..39cbb79 100644 --- a/Realtime/Realtime/Message/LCIMTypedMessage.cs +++ b/Realtime/Realtime/Message/LCIMTypedMessage.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Newtonsoft.Json; using LeanCloud.Storage.Internal.Codec; -using LeanCloud.Storage.Internal; +using LeanCloud.Common; namespace LeanCloud.Realtime { /// @@ -115,7 +115,7 @@ namespace LeanCloud.Realtime { internal static LCIMTypedMessage Deserialize(string json) { Dictionary msgData = JsonConvert.DeserializeObject>(json, - new LCJsonConverter()); + LCJsonConverter.Default); LCIMTypedMessage message = null; int msgType = (int)msgData[MessageTypeKey]; if (customMessageDict.TryGetValue(msgType, out Func msgConstructor)) { From 51fe69d7c0ff1da7f753ecaf062d5238569b9df2 Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 17:04:23 +0800 Subject: [PATCH 08/13] chore --- .../Internal/LCLiveQueryConnection.cs | 230 +++++++++++++++ LiveQuery/LiveQuery/LCLiveQuery.cs | 265 ++++++++++++++++++ 2 files changed, 495 insertions(+) create mode 100644 LiveQuery/LiveQuery/Internal/LCLiveQueryConnection.cs create mode 100644 LiveQuery/LiveQuery/LCLiveQuery.cs diff --git a/LiveQuery/LiveQuery/Internal/LCLiveQueryConnection.cs b/LiveQuery/LiveQuery/Internal/LCLiveQueryConnection.cs new file mode 100644 index 0000000..7203eac --- /dev/null +++ b/LiveQuery/LiveQuery/Internal/LCLiveQueryConnection.cs @@ -0,0 +1,230 @@ +using System; +using System.Text; +using System.Collections.Generic; +using System.Threading.Tasks; +using Newtonsoft.Json; +using LeanCloud.Realtime.Internal.Router; +using LeanCloud.Realtime.Internal.WebSocket; +using LeanCloud.Common; +using LeanCloud.Storage; + +namespace LeanCloud.LiveQuery.Internal { + public class LCLiveQueryConnection { + /// + /// 发送超时 + /// + private const int SEND_TIMEOUT = 10000; + + /// + /// 最大重连次数,超过后重置 Router 缓存后再次尝试重连 + /// + private const int MAX_RECONNECT_TIMES = 10; + + /// + /// 重连间隔 + /// + private const int RECONNECT_INTERVAL = 10000; + + /// + /// 子协议 + /// + private const string SUB_PROTOCOL = "lc.json.3"; + + /// + /// 通知事件 + /// + internal Action> OnNotification; + + /// + /// 断线事件 + /// + internal Action OnDisconnect; + + /// + /// 重连成功事件 + /// + internal Action OnReconnected; + + internal string id; + + /// + /// 请求回调缓存 + /// + private readonly Dictionary>> responses; + + private int requestI = 1; + + private LCRTMRouter router; + + private LCLiveQueryHeartBeat heartBeat; + + private LCWebSocketClient client; + + public LCLiveQueryConnection(string id) { + this.id = id; + responses = new Dictionary>>(); + heartBeat = new LCLiveQueryHeartBeat(this); + router = new LCRTMRouter(); + client = new LCWebSocketClient { + OnMessage = OnClientMessage, + OnClose = OnClientDisconnect + }; + } + + public async Task Connect() { + try { + LCRTMServer rtmServer = await router.GetServer(); + try { + LCLogger.Debug($"Primary Server"); + await client.Connect(rtmServer.Primary, SUB_PROTOCOL); + } catch (Exception e) { + LCLogger.Error(e); + LCLogger.Debug($"Secondary Server"); + await client.Connect(rtmServer.Secondary, SUB_PROTOCOL); + } + } catch (Exception e) { + throw e; + } + } + + /// + /// 重置连接 + /// + /// + internal async Task Reset() { + // 关闭就连接 + await client.Close(); + // 重新创建连接组件 + heartBeat = new LCLiveQueryHeartBeat(this); + router = new LCRTMRouter(); + client = new LCWebSocketClient { + OnMessage = OnClientMessage, + OnClose = OnClientDisconnect + }; + await Reconnect(); + } + + /// + /// 发送请求,会在收到应答后返回 + /// + /// + /// + internal async Task> SendRequest(Dictionary request) { + TaskCompletionSource> tcs = new TaskCompletionSource>(); + int requestIndex = requestI++; + request["i"] = requestIndex; + responses.Add(requestIndex, tcs); + try { + string json = JsonConvert.SerializeObject(request); + await SendText(json); + } catch (Exception e) { + tcs.TrySetException(e); + } + return await tcs.Task; + } + + /// + /// 发送文本消息 + /// + /// + /// + internal async Task SendText(string text) { + LCLogger.Debug($"{id} => {text}"); + Task sendTask = client.Send(text); + if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask) { + await sendTask; + } else { + throw new TimeoutException("Send request time out"); + } + } + + /// + /// 关闭连接 + /// + /// + internal async Task Close() { + OnNotification = null; + OnDisconnect = null; + OnReconnected = null; + heartBeat.Stop(); + await client.Close(); + } + + private void OnClientMessage(byte[] bytes) { + _ = heartBeat.Refresh(OnPingTimeout); + try { + string json = Encoding.UTF8.GetString(bytes); + Dictionary msg = JsonConvert.DeserializeObject>(json, + LCJsonConverter.Default); + LCLogger.Debug($"{id} <= {json}"); + if (msg.TryGetValue("i", out object i)) { + int requestIndex = Convert.ToInt32(i); + if (responses.TryGetValue(requestIndex, out TaskCompletionSource> tcs)) { + if (msg.TryGetValue("error", out object error)) { + // 错误 + if (error is Dictionary dict) { + int code = Convert.ToInt32(dict["code"]); + string detail = dict["detail"] as string; + tcs.SetException(new LCException(code, detail)); + } else { + tcs.SetException(new Exception(error as string)); + } + } else { + tcs.SetResult(msg); + } + responses.Remove(requestIndex); + } else { + LCLogger.Error($"No request for {requestIndex}"); + } + } else { + // 通知 + OnNotification?.Invoke(msg); + } + } catch (Exception e) { + LCLogger.Error(e); + } + } + + private void OnClientDisconnect() { + heartBeat.Stop(); + OnDisconnect?.Invoke(); + // 重连 + _ = Reconnect(); + } + + private async void OnPingTimeout() { + await client.Close(); + OnClientDisconnect(); + } + + private async Task Reconnect() { + while (true) { + int reconnectCount = 0; + // 重连策略 + while (reconnectCount < MAX_RECONNECT_TIMES) { + try { + LCLogger.Debug($"Reconnecting... {reconnectCount}"); + await Connect(); + break; + } catch (Exception e) { + reconnectCount++; + LCLogger.Error(e); + LCLogger.Debug($"Reconnect after {RECONNECT_INTERVAL}ms"); + await Task.Delay(RECONNECT_INTERVAL); + } + } + if (reconnectCount < MAX_RECONNECT_TIMES) { + // 重连成功 + LCLogger.Debug("Reconnected"); + client.OnMessage = OnClientMessage; + client.OnClose = OnClientDisconnect; + OnReconnected?.Invoke(); + break; + } else { + // 重置 Router,继续尝试重连 + router = new LCRTMRouter(); + } + } + } + } +} diff --git a/LiveQuery/LiveQuery/LCLiveQuery.cs b/LiveQuery/LiveQuery/LCLiveQuery.cs new file mode 100644 index 0000000..c6f96a6 --- /dev/null +++ b/LiveQuery/LiveQuery/LCLiveQuery.cs @@ -0,0 +1,265 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using LeanCloud.Storage; +using LeanCloud.Storage.Internal.Object; +using LeanCloud.LiveQuery.Internal; + +namespace LeanCloud.LiveQuery { + /// + /// LiveQuery + /// + public class LCLiveQuery { + /// + /// 新对象创建事件 + /// + public Action OnCreate; + /// + /// 对象更新事件 + /// + public Action> OnUpdate; + /// + /// 对象被删除 + /// + public Action OnDelete; + /// + /// 有新的满足条件的对象产生 + /// + public Action> OnEnter; + /// + /// 不再满足条件 + /// + public Action> OnLeave; + /// + /// 当一个用户登录成功 + /// + public Action OnLogin; + + public string Id { + get; private set; + } + + public LCQuery Query { + get; internal set; + } + + private static LCLiveQueryConnection connection; + + private static Dictionary> liveQueries = new Dictionary>(); + + internal LCLiveQuery() { + + } + + private static readonly string DeviceId = Guid.NewGuid().ToString(); + + /// + /// 订阅 + /// + /// + public async Task Subscribe() { + // TODO 判断当前连接情况 + if (connection == null) { + connection = new LCLiveQueryConnection(DeviceId) { + OnReconnected = OnReconnected, + OnNotification = OnNotification + }; + await connection.Connect(); + await Login(); + } + Dictionary queryData = new Dictionary { + { "className", Query.ClassName }, + { "where", Query.Condition.Encode() } + }; + Dictionary data = new Dictionary { + { "query", queryData }, + { "id", DeviceId }, + { "clientTimestamp", DateTimeOffset.Now.ToUnixTimeMilliseconds() } + }; + LCUser user = await LCUser.GetCurrent(); + if (user != null && !string.IsNullOrEmpty(user.SessionToken)) { + data.Add("sessionToken", user.SessionToken); + } + string path = "LiveQuery/subscribe"; + Dictionary result = await LCApplication.HttpClient.Post>(path, + data: data); + if (result.TryGetValue("query_id", out object id)) { + Id = id as string; + WeakReference weakRef = new WeakReference(this); + liveQueries[Id] = weakRef; + } + } + + /// + /// 取消订阅 + /// + /// + public async Task Unsubscribe() { + Dictionary data = new Dictionary { + { "id", DeviceId }, + { "query_id", Id } + }; + string path = "LiveQuery/unsubscribe"; + await LCApplication.HttpClient.Post>(path, + data: data); + // 移除 + liveQueries.Remove(Id); + } + + private static async Task Login() { + Dictionary data = new Dictionary { + { "cmd", "login" }, + { "appId", LCApplication.AppId }, + { "installationId", DeviceId }, + { "clientTs", DateTimeOffset.Now.ToUnixTimeMilliseconds() }, + { "service", 1 } + }; + await connection.SendRequest(data); + } + + private static async void OnReconnected() { + await Login(); + Dictionary> oldLiveQueries = liveQueries; + liveQueries = new Dictionary>(); + foreach (WeakReference weakRef in oldLiveQueries.Values) { + if (weakRef.TryGetTarget(out LCLiveQuery liveQuery)) { + await liveQuery.Subscribe(); + } + } + } + + private static void OnNotification(Dictionary notification) { + if (!notification.TryGetValue("cmd", out object cmd) || + !"data".Equals(cmd)) { + return; + } + if (!notification.TryGetValue("msg", out object msg) || + !(msg is IEnumerable list)) { + return; + } + + foreach (object item in list) { + if (item is Dictionary dict) { + if (!dict.TryGetValue("op", out object op)) { + continue; + } + switch (op as string) { + case "create": + OnCreateNotification(dict); + break; + case "update": + OnUpdateNotification(dict); + break; + case "enter": + OnEnterNotification(dict); + break; + case "leave": + OnLeaveNotification(dict); + break; + case "delete": + OnDeleteNotification(dict); + break; + case "login": + OnLoginNotification(dict); + break; + default: + LCLogger.Debug($"Not support: {op}"); + break; + } + } + } + } + + private static void OnCreateNotification(Dictionary data) { + if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) && + TryGetObject(data, out LCObject obj)) { + liveQuery.OnCreate?.Invoke(obj); + } + } + + private static void OnUpdateNotification(Dictionary data) { + if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) && + TryGetObject(data, out LCObject obj) && + TryGetUpdatedKeys(data, out ReadOnlyCollection keys)) { + liveQuery.OnUpdate?.Invoke(obj, keys); + } + } + + private static void OnEnterNotification(Dictionary data) { + if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) && + TryGetObject(data, out LCObject obj) && + TryGetUpdatedKeys(data, out ReadOnlyCollection keys)) { + liveQuery.OnEnter?.Invoke(obj, keys); + } + } + + private static void OnLeaveNotification(Dictionary data) { + if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) && + TryGetObject(data, out LCObject obj) && + TryGetUpdatedKeys(data, out ReadOnlyCollection keys)) { + liveQuery.OnLeave?.Invoke(obj, keys); + } + } + + private static void OnDeleteNotification(Dictionary data) { + if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) && + TryGetObject(data, out LCObject obj)) { + liveQuery.OnDelete?.Invoke(obj.ObjectId); + } + } + + private static void OnLoginNotification(Dictionary data) { + if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) && + data.TryGetValue("object", out object obj) && + obj is Dictionary dict) { + LCObjectData objectData = LCObjectData.Decode(dict); + LCUser user = new LCUser(objectData); + liveQuery.OnLogin?.Invoke(user); + } + } + + private static bool TryGetLiveQuery(Dictionary data, out LCLiveQuery liveQuery) { + if (!data.TryGetValue("query_id", out object i) || + !(i is string id)) { + liveQuery = null; + return false; + } + + if (!liveQueries.TryGetValue(id, out WeakReference weakRef) || + !weakRef.TryGetTarget(out LCLiveQuery lq)) { + liveQuery = null; + return false; + } + + liveQuery = lq; + return true; + } + + private static bool TryGetObject(Dictionary data, out LCObject obj) { + if (!data.TryGetValue("object", out object o) || + !(o is Dictionary dict)) { + obj = null; + return false; + } + + LCObjectData objectData = LCObjectData.Decode(dict); + obj = LCObject.Create(dict["className"] as string); + obj.Merge(objectData); + return true; + } + + private static bool TryGetUpdatedKeys(Dictionary data, out ReadOnlyCollection keys) { + if (!data.TryGetValue("updatedKeys", out object uks) || + !(uks is List list)) { + keys = null; + return false; + } + + keys = list.Cast().ToList() + .AsReadOnly(); + return true; + } + } +} From 4b30df0ba0615ccaa35d5ca8d82711f7e8d51b6e Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 17:05:25 +0800 Subject: [PATCH 09/13] =?UTF-8?q?chore:=20=E6=94=AF=E6=8C=81=20LiveQuery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LiveQuery-Unity/LiveQuery-Unity.csproj | 29 +++ .../LiveQuery.Test/LiveQuery.Test.csproj | 19 ++ LiveQuery/LiveQuery.Test/LiveQuery.cs | 180 ++++++++++++++++++ .../Internal/LCLiveQueryHeartBeat.cs | 54 +++++- LiveQuery/LiveQuery/LCQueryExtension.cs | 15 +- LiveQuery/LiveQuery/LiveQuery.csproj | 14 ++ 6 files changed, 304 insertions(+), 7 deletions(-) create mode 100644 LiveQuery/LiveQuery-Unity/LiveQuery-Unity.csproj create mode 100644 LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj create mode 100644 LiveQuery/LiveQuery.Test/LiveQuery.cs create mode 100644 LiveQuery/LiveQuery/LiveQuery.csproj diff --git a/LiveQuery/LiveQuery-Unity/LiveQuery-Unity.csproj b/LiveQuery/LiveQuery-Unity/LiveQuery-Unity.csproj new file mode 100644 index 0000000..05fe632 --- /dev/null +++ b/LiveQuery/LiveQuery-Unity/LiveQuery-Unity.csproj @@ -0,0 +1,29 @@ + + + + netstandard2.0 + + + + + + + + Internal\LCLiveQueryHeartBeat.cs + + + Internal\LCLiveQueryConnection.cs + + + LCLiveQuery.cs + + + LCQueryExtension.cs + + + + + ..\..\UnityLibs\Newtonsoft.Json.dll + + + diff --git a/LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj b/LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj new file mode 100644 index 0000000..7a7440b --- /dev/null +++ b/LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp3.1 + + false + 0.1.0 + + + + + + + + + + + + diff --git a/LiveQuery/LiveQuery.Test/LiveQuery.cs b/LiveQuery/LiveQuery.Test/LiveQuery.cs new file mode 100644 index 0000000..9689daf --- /dev/null +++ b/LiveQuery/LiveQuery.Test/LiveQuery.cs @@ -0,0 +1,180 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Net.Http; +using System.Net.Http.Headers; +using Newtonsoft.Json; +using LeanCloud; +using LeanCloud.Storage; +using LeanCloud.LiveQuery; + +using static NUnit.Framework.TestContext; + +namespace LiveQuery.Test { + internal class Account : LCObject { + internal int Balance { + get { + return (int)this["balance"]; + } + set { + this["balance"] = value; + } + } + + internal Account() : base("Account") { } + } + + public class LiveQuery { + private LCLiveQuery liveQuery; + + private Account account; + + [SetUp] + public async Task Setup() { + LCLogger.LogDelegate += Print; + LCApplication.Initialize("ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz", + "NUKmuRbdAhg1vrb2wexYo1jo", + "https://ikggdre2.lc-cn-n1-shared.com"); + + LCObject.RegisterSubclass("Account", () => new Account()); + + LCQuery query = new LCQuery("Account"); + query.WhereGreaterThan("balance", 100); + liveQuery = await query.Subscribe(); + } + + [TearDown] + public void TearDown() { + LCLogger.LogDelegate -= Print; + } + + [Test] + [Order(0)] + public async Task Create() { + TaskCompletionSource tcs = new TaskCompletionSource(); + + liveQuery.OnCreate = (obj) => { + WriteLine($"******** create: {obj}"); + tcs.SetResult(null); + }; + account = new Account { + Balance = 110 + }; + await account.Save(); + + await tcs.Task; + } + + [Test] + [Order(1)] + public async Task Update() { + TaskCompletionSource tcs = new TaskCompletionSource(); + + liveQuery.OnUpdate = (obj, updatedKeys) => { + WriteLine($"******** update: {obj}"); + tcs.SetResult(null); + }; + account.Balance = 120; + await account.Save(); + + await tcs.Task; + } + + [Test] + [Order(2)] + public async Task Leave() { + TaskCompletionSource tcs = new TaskCompletionSource(); + + liveQuery.OnLeave = (obj, updatedKeys) => { + WriteLine($"******** level: {obj}"); + tcs.SetResult(null); + }; + account.Balance = 80; + await account.Save(); + + await tcs.Task; + } + + [Test] + [Order(3)] + public async Task Enter() { + TaskCompletionSource tcs = new TaskCompletionSource(); + + liveQuery.OnEnter = (obj, updatedKeys) => { + WriteLine($"******** enter: {obj}"); + tcs.SetResult(null); + }; + account.Balance = 120; + await account.Save(); + + await tcs.Task; + } + + [Test] + [Order(4)] + public async Task Delete() { + TaskCompletionSource tcs = new TaskCompletionSource(); + + liveQuery.OnDelete = (objId) => { + WriteLine($"******** delete: {objId}"); + tcs.SetResult(null); + }; + await account.Delete(); + + await tcs.Task; + } + + [Test] + [Order(5)] + public async Task Login() { + TaskCompletionSource tcs = new TaskCompletionSource(); + + await LCUser.Login("hello", "world"); + LCQuery userQuery = LCUser.GetQuery(); + userQuery.WhereEqualTo("username", "hello"); + LCLiveQuery userLiveQuery = await userQuery.Subscribe(); + userLiveQuery.OnLogin = (user) => { + WriteLine($"login: {user}"); + tcs.SetResult(null); + }; + + // 模拟 REST API + string url = "https://ikggdre2.lc-cn-n1-shared.com/1.1/login"; + HttpRequestMessage request = new HttpRequestMessage { + RequestUri = new Uri(url), + Method = HttpMethod.Post + }; + request.Headers.Add("X-LC-Id", "ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz"); + request.Headers.Add("X-LC-Key", "NUKmuRbdAhg1vrb2wexYo1jo"); + string content = JsonConvert.SerializeObject(new Dictionary { + { "username", "hello" }, + { "password", "world" } + }); + StringContent requestContent = new StringContent(content); + requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + request.Content = requestContent; + HttpClient client = new HttpClient(); + await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + + await tcs.Task; + } + + private static void Print(LCLogLevel level, string info) { + switch (level) { + case LCLogLevel.Debug: + WriteLine($"[DEBUG] {info}\n"); + break; + case LCLogLevel.Warn: + WriteLine($"[WARNING] {info}\n"); + break; + case LCLogLevel.Error: + WriteLine($"[ERROR] {info}\n"); + break; + default: + WriteLine(info); + break; + } + } + } +} \ No newline at end of file diff --git a/LiveQuery/LiveQuery/Internal/LCLiveQueryHeartBeat.cs b/LiveQuery/LiveQuery/Internal/LCLiveQueryHeartBeat.cs index a856d91..a0fec18 100644 --- a/LiveQuery/LiveQuery/Internal/LCLiveQueryHeartBeat.cs +++ b/LiveQuery/LiveQuery/Internal/LCLiveQueryHeartBeat.cs @@ -1,7 +1,55 @@ using System; -namespace LiveQuery.Internal { - public class LCLiveQueryHeartBeat { - public LCLiveQueryHeartBeat() { +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace LeanCloud.LiveQuery.Internal { + /// + /// LiveQuery 心跳控制器 + /// + internal class LCLiveQueryHeartBeat { + private const int PING_INTERVAL = 5000; + private const int PONG_INTERVAL = 5000; + + private readonly LCLiveQueryConnection connection; + + private CancellationTokenSource pingCTS; + private CancellationTokenSource pongCTS; + + internal LCLiveQueryHeartBeat(LCLiveQueryConnection connection) { + this.connection = connection; + } + + internal async Task Refresh(Action onTimeout) { + LCLogger.Debug("LiveQuery HeartBeat refresh"); + Stop(); + + pingCTS = new CancellationTokenSource(); + Task delayTask = Task.Delay(PING_INTERVAL, pingCTS.Token); + await delayTask; + if (delayTask.IsCanceled) { + return; + } + // 发送 Ping 包 + LCLogger.Debug("Ping ~~~"); + _ = connection.SendText("{}"); + + // 等待 Pong + pongCTS = new CancellationTokenSource(); + Task timeoutTask = Task.Delay(PONG_INTERVAL, pongCTS.Token); + await timeoutTask; + if (timeoutTask.IsCanceled) { + return; + } + + // 超时 + LCLogger.Debug("Ping timeout"); + onTimeout?.Invoke(); + } + + internal void Stop() { + pingCTS?.Cancel(); + pongCTS?.Cancel(); } } } diff --git a/LiveQuery/LiveQuery/LCQueryExtension.cs b/LiveQuery/LiveQuery/LCQueryExtension.cs index 5877435..66c7ec9 100644 --- a/LiveQuery/LiveQuery/LCQueryExtension.cs +++ b/LiveQuery/LiveQuery/LCQueryExtension.cs @@ -1,7 +1,14 @@ -using System; -namespace LiveQuery { - public class LCQueryExtension { - public LCQueryExtension() { +using System.Threading.Tasks; +using LeanCloud.Storage; + +namespace LeanCloud.LiveQuery { + public static class LCQueryExtension { + public static async Task Subscribe(this LCQuery query) { + LCLiveQuery liveQuery = new LCLiveQuery { + Query = query + }; + await liveQuery.Subscribe(); + return liveQuery; } } } diff --git a/LiveQuery/LiveQuery/LiveQuery.csproj b/LiveQuery/LiveQuery/LiveQuery.csproj new file mode 100644 index 0000000..37bce0c --- /dev/null +++ b/LiveQuery/LiveQuery/LiveQuery.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + 0.1.0 + + + + + + + + + From aeaf848a9f46094b81bf92a64d68987bb50bba1f Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 17:06:43 +0800 Subject: [PATCH 10/13] =?UTF-8?q?chore:=20=E5=A2=9E=E5=8A=A0=20LiveQuery?= =?UTF-8?q?=20=E6=B5=8B=E8=AF=95=E5=B7=A5=E7=A8=8B=EF=BC=8C=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=96=AD=E7=BA=BF=E9=87=8D=E8=BF=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sample/LiveQueryApp/LiveQueryApp.csproj | 4 ++ Sample/LiveQueryApp/Program.cs | 62 ++++++++++++++++++- .../SingleThreadSynchronizationContext.cs | 2 +- Sample/RealtimeApp/RealtimeApp.csproj | 1 + csharp-sdk.sln | 30 +++++++++ 5 files changed, 97 insertions(+), 2 deletions(-) diff --git a/Sample/LiveQueryApp/LiveQueryApp.csproj b/Sample/LiveQueryApp/LiveQueryApp.csproj index c73e0d1..67089e1 100644 --- a/Sample/LiveQueryApp/LiveQueryApp.csproj +++ b/Sample/LiveQueryApp/LiveQueryApp.csproj @@ -3,6 +3,10 @@ Exe netcoreapp3.1 + 0.1.0 + + + diff --git a/Sample/LiveQueryApp/Program.cs b/Sample/LiveQueryApp/Program.cs index 2888d80..cc42aab 100644 --- a/Sample/LiveQueryApp/Program.cs +++ b/Sample/LiveQueryApp/Program.cs @@ -1,9 +1,69 @@ using System; +using System.Threading.Tasks; +using LeanCloud; +using LeanCloud.Storage; +using LeanCloud.LiveQuery; + +using static System.Console; namespace LiveQueryApp { class Program { static void Main(string[] args) { - Console.WriteLine("Hello World!"); + WriteLine("Hello World!"); + + SingleThreadSynchronizationContext.Run(async () => { + LCLogger.LogDelegate += Print; + LCApplication.Initialize("ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz", + "NUKmuRbdAhg1vrb2wexYo1jo", + "https://ikggdre2.lc-cn-n1-shared.com"); + + await LCUser.Login("hello", "world"); + LCQuery userQuery = LCUser.GetQuery(); + userQuery.WhereEqualTo("username", "hello"); + LCLiveQuery userLiveQuery = await userQuery.Subscribe(); + userLiveQuery.OnLogin = (user) => { + WriteLine($"login: {user.Username}"); + }; + + LCQuery query = new LCQuery("Account"); + query.WhereGreaterThan("balance", 100); + LCLiveQuery liveQuery = await query.Subscribe(); + liveQuery.OnCreate = (obj) => { + WriteLine($"create: {obj}"); + }; + liveQuery.OnUpdate = (obj, keys) => { + WriteLine($"update: {obj}"); + WriteLine(keys.Count); + }; + liveQuery.OnDelete = (objId) => { + WriteLine($"delete: {objId}"); + }; + liveQuery.OnEnter = (obj, keys) => { + WriteLine($"enter: {obj}"); + WriteLine(keys.Count); + }; + liveQuery.OnLeave = (obj, keys) => { + WriteLine($"leave: {obj}"); + WriteLine(keys.Count); + }; + }); + } + + private static void Print(LCLogLevel level, string info) { + switch (level) { + case LCLogLevel.Debug: + WriteLine($"[DEBUG] {info}\n"); + break; + case LCLogLevel.Warn: + WriteLine($"[WARNING] {info}\n"); + break; + case LCLogLevel.Error: + WriteLine($"[ERROR] {info}\n"); + break; + default: + WriteLine(info); + break; + } } } } diff --git a/Sample/LiveQueryApp/SingleThreadSynchronizationContext.cs b/Sample/LiveQueryApp/SingleThreadSynchronizationContext.cs index fdfb2c9..d881cca 100644 --- a/Sample/LiveQueryApp/SingleThreadSynchronizationContext.cs +++ b/Sample/LiveQueryApp/SingleThreadSynchronizationContext.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using System.Collections.Concurrent; using System.Collections.Generic; -namespace RealtimeApp { +namespace LiveQueryApp { /// /// 单线程环境,用于控制台应用 await 返回 /// diff --git a/Sample/RealtimeApp/RealtimeApp.csproj b/Sample/RealtimeApp/RealtimeApp.csproj index d96ba13..a6a6535 100644 --- a/Sample/RealtimeApp/RealtimeApp.csproj +++ b/Sample/RealtimeApp/RealtimeApp.csproj @@ -3,6 +3,7 @@ Exe netcoreapp3.0 + 0.1.0 diff --git a/csharp-sdk.sln b/csharp-sdk.sln index 4493742..205f883 100644 --- a/csharp-sdk.sln +++ b/csharp-sdk.sln @@ -27,6 +27,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Realtime", "Realtime\Realti EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealtimeApp", "Sample\RealtimeApp\RealtimeApp.csproj", "{A716EFC7-9220-4A9A-9F73-B816A0787F77}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LiveQuery", "LiveQuery", "{A1A24E0F-6901-4A9A-9BB8-4F586BC7EE17}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQuery", "LiveQuery\LiveQuery\LiveQuery.csproj", "{659BA438-1DA7-4A32-92A4-DD0FAE142259}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQuery.Test", "LiveQuery\LiveQuery.Test\LiveQuery.Test.csproj", "{7F770CE0-593E-486A-96E8-8903BC27C6FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQueryApp", "Sample\LiveQueryApp\LiveQueryApp.csproj", "{CF72C053-5DB9-4E9C-BF9D-6664672F4916}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveQuery-Unity", "LiveQuery\LiveQuery-Unity\LiveQuery-Unity.csproj", "{FF11B077-93F1-45FD-A3C7-020D316EB5A4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +79,22 @@ Global {A716EFC7-9220-4A9A-9F73-B816A0787F77}.Debug|Any CPU.Build.0 = Debug|Any CPU {A716EFC7-9220-4A9A-9F73-B816A0787F77}.Release|Any CPU.ActiveCfg = Release|Any CPU {A716EFC7-9220-4A9A-9F73-B816A0787F77}.Release|Any CPU.Build.0 = Release|Any CPU + {659BA438-1DA7-4A32-92A4-DD0FAE142259}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {659BA438-1DA7-4A32-92A4-DD0FAE142259}.Debug|Any CPU.Build.0 = Debug|Any CPU + {659BA438-1DA7-4A32-92A4-DD0FAE142259}.Release|Any CPU.ActiveCfg = Release|Any CPU + {659BA438-1DA7-4A32-92A4-DD0FAE142259}.Release|Any CPU.Build.0 = Release|Any CPU + {7F770CE0-593E-486A-96E8-8903BC27C6FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F770CE0-593E-486A-96E8-8903BC27C6FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F770CE0-593E-486A-96E8-8903BC27C6FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F770CE0-593E-486A-96E8-8903BC27C6FB}.Release|Any CPU.Build.0 = Release|Any CPU + {CF72C053-5DB9-4E9C-BF9D-6664672F4916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF72C053-5DB9-4E9C-BF9D-6664672F4916}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF72C053-5DB9-4E9C-BF9D-6664672F4916}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF72C053-5DB9-4E9C-BF9D-6664672F4916}.Release|Any CPU.Build.0 = Release|Any CPU + {FF11B077-93F1-45FD-A3C7-020D316EB5A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF11B077-93F1-45FD-A3C7-020D316EB5A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF11B077-93F1-45FD-A3C7-020D316EB5A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF11B077-93F1-45FD-A3C7-020D316EB5A4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {26CDAE2A-6D79-4981-8D80-3EA34FDFB134} = {319A9989-3B69-4AD0-9E43-F6D31C1D2A4A} @@ -80,6 +106,10 @@ Global {882A9419-CC5E-4CFB-B076-7561989B0A4A} = {319A9989-3B69-4AD0-9E43-F6D31C1D2A4A} {75A3A4EC-93B8-40C9-AE04-DF14A72525CC} = {319A9989-3B69-4AD0-9E43-F6D31C1D2A4A} {A716EFC7-9220-4A9A-9F73-B816A0787F77} = {2D980281-F060-4363-AB7A-D4B6C30ADDBB} + {659BA438-1DA7-4A32-92A4-DD0FAE142259} = {A1A24E0F-6901-4A9A-9BB8-4F586BC7EE17} + {7F770CE0-593E-486A-96E8-8903BC27C6FB} = {A1A24E0F-6901-4A9A-9BB8-4F586BC7EE17} + {CF72C053-5DB9-4E9C-BF9D-6664672F4916} = {2D980281-F060-4363-AB7A-D4B6C30ADDBB} + {FF11B077-93F1-45FD-A3C7-020D316EB5A4} = {A1A24E0F-6901-4A9A-9BB8-4F586BC7EE17} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution version = 0.1.0 From 2210d7576439f9fc444946fc5a664b080d85bbea Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 17:18:49 +0800 Subject: [PATCH 11/13] chore --- LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj b/LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj index 7a7440b..70a5ff8 100644 --- a/LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj +++ b/LiveQuery/LiveQuery.Test/LiveQuery.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + netcoreapp2.2 false 0.1.0 From 2ac01d886ced41be6a923b77e0cc2fd9090a1f77 Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 17:18:59 +0800 Subject: [PATCH 12/13] chore --- Sample/LiveQueryApp/LiveQueryApp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sample/LiveQueryApp/LiveQueryApp.csproj b/Sample/LiveQueryApp/LiveQueryApp.csproj index 67089e1..dfe693c 100644 --- a/Sample/LiveQueryApp/LiveQueryApp.csproj +++ b/Sample/LiveQueryApp/LiveQueryApp.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + netcoreapp3.0 0.1.0 From 1c2345d3b0cdde65aebf758b5d90f5353259f804 Mon Sep 17 00:00:00 2001 From: oneRain Date: Wed, 13 May 2020 17:38:33 +0800 Subject: [PATCH 13/13] =?UTF-8?q?chore:=20LiveQuery=20=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Common/Common/Common.csproj | 2 +- script/package.sh | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Common/Common/Common.csproj b/Common/Common/Common.csproj index 6c06788..dc91fba 100644 --- a/Common/Common/Common.csproj +++ b/Common/Common/Common.csproj @@ -3,7 +3,7 @@ netstandard2.0 0.1.0 - LeanCloud.Common + Common diff --git a/script/package.sh b/script/package.sh index 40d3c8a..96dea75 100644 --- a/script/package.sh +++ b/script/package.sh @@ -16,4 +16,8 @@ pack ./Storage/Storage-Unity/bin/Release/netstandard2.0/ ./Plugins LeanCloud-SDK # Realtime pack ./Realtime/Realtime/bin/Release/netstandard2.0/ ./DLLs LeanCloud-SDK-Realtime-Standard.zip -pack ./Realtime/Realtime-Unity/bin/Release/netstandard2.0/ ./Plugins LeanCloud-SDK-Realtime-Unity.zip \ No newline at end of file +pack ./Realtime/Realtime-Unity/bin/Release/netstandard2.0/ ./Plugins LeanCloud-SDK-Realtime-Unity.zip + +# LiveQuery +pack ./LiveQuery/LiveQuery/bin/Release/netstandard2.0/ ./DLLs LeanCloud-SDK-LiveQuery-Standard.zip +pack ./LiveQuery/LiveQuery-Unity/bin/Release/netstandard2.0/ ./Plugins LeanCloud-SDK-LiveQuery-Unity.zip \ No newline at end of file