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 + + + + + + + + +