chore: 支持 LiveQuery

oneRain 2020-05-13 17:05:25 +08:00
parent 51fe69d7c0
commit 4b30df0ba0
6 changed files with 304 additions and 7 deletions

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Realtime\Realtime-Unity\Realtime-Unity.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\LiveQuery\Internal\LCLiveQueryHeartBeat.cs">
<Link>Internal\LCLiveQueryHeartBeat.cs</Link>
</Compile>
<Compile Include="..\LiveQuery\Internal\LCLiveQueryConnection.cs">
<Link>Internal\LCLiveQueryConnection.cs</Link>
</Compile>
<Compile Include="..\LiveQuery\LCLiveQuery.cs">
<Link>LCLiveQuery.cs</Link>
</Compile>
<Compile Include="..\LiveQuery\LCQueryExtension.cs">
<Link>LCQueryExtension.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\..\UnityLibs\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<ReleaseVersion>0.1.0</ReleaseVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LiveQuery\LiveQuery.csproj" />
</ItemGroup>
</Project>

View File

@ -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<LCObject> query = new LCQuery<LCObject>("Account");
query.WhereGreaterThan("balance", 100);
liveQuery = await query.Subscribe();
}
[TearDown]
public void TearDown() {
LCLogger.LogDelegate -= Print;
}
[Test]
[Order(0)]
public async Task Create() {
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
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<object> tcs = new TaskCompletionSource<object>();
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<object> tcs = new TaskCompletionSource<object>();
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<object> tcs = new TaskCompletionSource<object>();
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<object> tcs = new TaskCompletionSource<object>();
liveQuery.OnDelete = (objId) => {
WriteLine($"******** delete: {objId}");
tcs.SetResult(null);
};
await account.Delete();
await tcs.Task;
}
[Test]
[Order(5)]
public async Task Login() {
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
await LCUser.Login("hello", "world");
LCQuery<LCUser> 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<string, object> {
{ "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;
}
}
}
}

View File

@ -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 {
/// <summary>
/// LiveQuery 心跳控制器
/// </summary>
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();
}
}
}

View File

@ -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<LCLiveQuery> Subscribe(this LCQuery query) {
LCLiveQuery liveQuery = new LCLiveQuery {
Query = query
};
await liveQuery.Subscribe();
return liveQuery;
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ReleaseVersion>0.1.0</ReleaseVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Realtime\Realtime\Realtime.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Internal\" />
</ItemGroup>
</Project>