Merge pull request #63 from onerain88/livequery
Livequery
commit
6e042ab754
|
@ -27,6 +27,9 @@
|
||||||
<Compile Include="..\Common\Task\LCTaskExtensions.cs">
|
<Compile Include="..\Common\Task\LCTaskExtensions.cs">
|
||||||
<Link>Task\LCTaskExtensions.cs</Link>
|
<Link>Task\LCTaskExtensions.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\Common\Json\LCJsonConverter.cs">
|
||||||
|
<Link>Json\LCJsonConverter.cs</Link>
|
||||||
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Newtonsoft.Json">
|
<Reference Include="Newtonsoft.Json">
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||||
<AssemblyName>LeanCloud.Common</AssemblyName>
|
<AssemblyName>Common</AssemblyName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace LeanCloud.Storage.Internal {
|
namespace LeanCloud.Common {
|
||||||
public class LCJsonConverter : JsonConverter {
|
public class LCJsonConverter : JsonConverter {
|
||||||
public override bool CanConvert(Type objectType) {
|
public override bool CanConvert(Type objectType) {
|
||||||
return objectType == typeof(object);
|
return objectType == typeof(object);
|
||||||
|
@ -31,5 +31,7 @@ namespace LeanCloud.Storage.Internal {
|
||||||
|
|
||||||
return serializer.Deserialize(reader);
|
return serializer.Deserialize(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly static LCJsonConverter Default = new LCJsonConverter();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp2.2</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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
/// <summary>
|
||||||
|
/// 发送超时
|
||||||
|
/// </summary>
|
||||||
|
private const int SEND_TIMEOUT = 10000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 最大重连次数,超过后重置 Router 缓存后再次尝试重连
|
||||||
|
/// </summary>
|
||||||
|
private const int MAX_RECONNECT_TIMES = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重连间隔
|
||||||
|
/// </summary>
|
||||||
|
private const int RECONNECT_INTERVAL = 10000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 子协议
|
||||||
|
/// </summary>
|
||||||
|
private const string SUB_PROTOCOL = "lc.json.3";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通知事件
|
||||||
|
/// </summary>
|
||||||
|
internal Action<Dictionary<string, object>> OnNotification;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 断线事件
|
||||||
|
/// </summary>
|
||||||
|
internal Action OnDisconnect;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重连成功事件
|
||||||
|
/// </summary>
|
||||||
|
internal Action OnReconnected;
|
||||||
|
|
||||||
|
internal string id;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求回调缓存
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<int, TaskCompletionSource<Dictionary<string, object>>> responses;
|
||||||
|
|
||||||
|
private int requestI = 1;
|
||||||
|
|
||||||
|
private LCRTMRouter router;
|
||||||
|
|
||||||
|
private LCLiveQueryHeartBeat heartBeat;
|
||||||
|
|
||||||
|
private LCWebSocketClient client;
|
||||||
|
|
||||||
|
public LCLiveQueryConnection(string id) {
|
||||||
|
this.id = id;
|
||||||
|
responses = new Dictionary<int, TaskCompletionSource<Dictionary<string, object>>>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重置连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task Reset() {
|
||||||
|
// 关闭就连接
|
||||||
|
await client.Close();
|
||||||
|
// 重新创建连接组件
|
||||||
|
heartBeat = new LCLiveQueryHeartBeat(this);
|
||||||
|
router = new LCRTMRouter();
|
||||||
|
client = new LCWebSocketClient {
|
||||||
|
OnMessage = OnClientMessage,
|
||||||
|
OnClose = OnClientDisconnect
|
||||||
|
};
|
||||||
|
await Reconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送请求,会在收到应答后返回
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal async Task<Dictionary<string, object>> SendRequest(Dictionary<string, object> request) {
|
||||||
|
TaskCompletionSource<Dictionary<string, object>> tcs = new TaskCompletionSource<Dictionary<string, object>>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送文本消息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭连接
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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<string, object> msg = JsonConvert.DeserializeObject<Dictionary<string, object>>(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<Dictionary<string, object>> tcs)) {
|
||||||
|
if (msg.TryGetValue("error", out object error)) {
|
||||||
|
// 错误
|
||||||
|
if (error is Dictionary<string, object> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
using System;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
/// <summary>
|
||||||
|
/// LiveQuery
|
||||||
|
/// </summary>
|
||||||
|
public class LCLiveQuery {
|
||||||
|
/// <summary>
|
||||||
|
/// 新对象创建事件
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCObject> OnCreate;
|
||||||
|
/// <summary>
|
||||||
|
/// 对象更新事件
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCObject, ReadOnlyCollection<string>> OnUpdate;
|
||||||
|
/// <summary>
|
||||||
|
/// 对象被删除
|
||||||
|
/// </summary>
|
||||||
|
public Action<string> OnDelete;
|
||||||
|
/// <summary>
|
||||||
|
/// 有新的满足条件的对象产生
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCObject, ReadOnlyCollection<string>> OnEnter;
|
||||||
|
/// <summary>
|
||||||
|
/// 不再满足条件
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCObject, ReadOnlyCollection<string>> OnLeave;
|
||||||
|
/// <summary>
|
||||||
|
/// 当一个用户登录成功
|
||||||
|
/// </summary>
|
||||||
|
public Action<LCUser> OnLogin;
|
||||||
|
|
||||||
|
public string Id {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCQuery Query {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LCLiveQueryConnection connection;
|
||||||
|
|
||||||
|
private static Dictionary<string, WeakReference<LCLiveQuery>> liveQueries = new Dictionary<string, WeakReference<LCLiveQuery>>();
|
||||||
|
|
||||||
|
internal LCLiveQuery() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly string DeviceId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 订阅
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task Subscribe() {
|
||||||
|
// TODO 判断当前连接情况
|
||||||
|
if (connection == null) {
|
||||||
|
connection = new LCLiveQueryConnection(DeviceId) {
|
||||||
|
OnReconnected = OnReconnected,
|
||||||
|
OnNotification = OnNotification
|
||||||
|
};
|
||||||
|
await connection.Connect();
|
||||||
|
await Login();
|
||||||
|
}
|
||||||
|
Dictionary<string, object> queryData = new Dictionary<string, object> {
|
||||||
|
{ "className", Query.ClassName },
|
||||||
|
{ "where", Query.Condition.Encode() }
|
||||||
|
};
|
||||||
|
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||||
|
{ "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<string, object> result = await LCApplication.HttpClient.Post<Dictionary<string, object>>(path,
|
||||||
|
data: data);
|
||||||
|
if (result.TryGetValue("query_id", out object id)) {
|
||||||
|
Id = id as string;
|
||||||
|
WeakReference<LCLiveQuery> weakRef = new WeakReference<LCLiveQuery>(this);
|
||||||
|
liveQueries[Id] = weakRef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 取消订阅
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task Unsubscribe() {
|
||||||
|
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||||
|
{ "id", DeviceId },
|
||||||
|
{ "query_id", Id }
|
||||||
|
};
|
||||||
|
string path = "LiveQuery/unsubscribe";
|
||||||
|
await LCApplication.HttpClient.Post<Dictionary<string, object>>(path,
|
||||||
|
data: data);
|
||||||
|
// 移除
|
||||||
|
liveQueries.Remove(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task Login() {
|
||||||
|
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||||
|
{ "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<string, WeakReference<LCLiveQuery>> oldLiveQueries = liveQueries;
|
||||||
|
liveQueries = new Dictionary<string, WeakReference<LCLiveQuery>>();
|
||||||
|
foreach (WeakReference<LCLiveQuery> weakRef in oldLiveQueries.Values) {
|
||||||
|
if (weakRef.TryGetTarget(out LCLiveQuery liveQuery)) {
|
||||||
|
await liveQuery.Subscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnNotification(Dictionary<string, object> notification) {
|
||||||
|
if (!notification.TryGetValue("cmd", out object cmd) ||
|
||||||
|
!"data".Equals(cmd)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!notification.TryGetValue("msg", out object msg) ||
|
||||||
|
!(msg is IEnumerable<object> list)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (object item in list) {
|
||||||
|
if (item is Dictionary<string, object> 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<string, object> data) {
|
||||||
|
if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) &&
|
||||||
|
TryGetObject(data, out LCObject obj)) {
|
||||||
|
liveQuery.OnCreate?.Invoke(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnUpdateNotification(Dictionary<string, object> data) {
|
||||||
|
if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) &&
|
||||||
|
TryGetObject(data, out LCObject obj) &&
|
||||||
|
TryGetUpdatedKeys(data, out ReadOnlyCollection<string> keys)) {
|
||||||
|
liveQuery.OnUpdate?.Invoke(obj, keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnEnterNotification(Dictionary<string, object> data) {
|
||||||
|
if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) &&
|
||||||
|
TryGetObject(data, out LCObject obj) &&
|
||||||
|
TryGetUpdatedKeys(data, out ReadOnlyCollection<string> keys)) {
|
||||||
|
liveQuery.OnEnter?.Invoke(obj, keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnLeaveNotification(Dictionary<string, object> data) {
|
||||||
|
if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) &&
|
||||||
|
TryGetObject(data, out LCObject obj) &&
|
||||||
|
TryGetUpdatedKeys(data, out ReadOnlyCollection<string> keys)) {
|
||||||
|
liveQuery.OnLeave?.Invoke(obj, keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDeleteNotification(Dictionary<string, object> data) {
|
||||||
|
if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) &&
|
||||||
|
TryGetObject(data, out LCObject obj)) {
|
||||||
|
liveQuery.OnDelete?.Invoke(obj.ObjectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnLoginNotification(Dictionary<string, object> data) {
|
||||||
|
if (TryGetLiveQuery(data, out LCLiveQuery liveQuery) &&
|
||||||
|
data.TryGetValue("object", out object obj) &&
|
||||||
|
obj is Dictionary<string, object> dict) {
|
||||||
|
LCObjectData objectData = LCObjectData.Decode(dict);
|
||||||
|
LCUser user = new LCUser(objectData);
|
||||||
|
liveQuery.OnLogin?.Invoke(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetLiveQuery(Dictionary<string, object> 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<LCLiveQuery> weakRef) ||
|
||||||
|
!weakRef.TryGetTarget(out LCLiveQuery lq)) {
|
||||||
|
liveQuery = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
liveQuery = lq;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetObject(Dictionary<string, object> data, out LCObject obj) {
|
||||||
|
if (!data.TryGetValue("object", out object o) ||
|
||||||
|
!(o is Dictionary<string, object> 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<string, object> data, out ReadOnlyCollection<string> keys) {
|
||||||
|
if (!data.TryGetValue("updatedKeys", out object uks) ||
|
||||||
|
!(uks is List<object> list)) {
|
||||||
|
keys = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
keys = list.Cast<string>().ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -33,6 +33,11 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int HEART_BEAT_INTERVAL = 30000;
|
private const int HEART_BEAT_INTERVAL = 30000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 子协议
|
||||||
|
/// </summary>
|
||||||
|
private const string SUB_PROTOCOL = "lc.protobuf2.3";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通知事件
|
/// 通知事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -79,11 +84,11 @@ namespace LeanCloud.Realtime.Internal.Connection {
|
||||||
LCRTMServer rtmServer = await router.GetServer();
|
LCRTMServer rtmServer = await router.GetServer();
|
||||||
try {
|
try {
|
||||||
LCLogger.Debug($"Primary Server");
|
LCLogger.Debug($"Primary Server");
|
||||||
await client.Connect(rtmServer.Primary);
|
await client.Connect(rtmServer.Primary, SUB_PROTOCOL);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e);
|
LCLogger.Error(e);
|
||||||
LCLogger.Debug($"Secondary Server");
|
LCLogger.Debug($"Secondary Server");
|
||||||
await client.Connect(rtmServer.Secondary);
|
await client.Connect(rtmServer.Secondary, SUB_PROTOCOL);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw e;
|
throw e;
|
||||||
|
|
|
@ -5,7 +5,6 @@ using System.Collections.ObjectModel;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using LeanCloud.Realtime.Internal.Protocol;
|
using LeanCloud.Realtime.Internal.Protocol;
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using LeanCloud.Storage.Internal.Codec;
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
using LeanCloud.Common;
|
using LeanCloud.Common;
|
||||||
|
|
||||||
|
@ -441,7 +440,8 @@ namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
command.ConvMessage = convMessage;
|
command.ConvMessage = convMessage;
|
||||||
GenericCommand response = await Connection.SendRequest(command);
|
GenericCommand response = await Connection.SendRequest(command);
|
||||||
JsonObjectMessage results = response.ConvMessage.Results;
|
JsonObjectMessage results = response.ConvMessage.Results;
|
||||||
List<object> convs = JsonConvert.DeserializeObject<List<object>>(results.Data, new LCJsonConverter());
|
List<object> convs = JsonConvert.DeserializeObject<List<object>>(results.Data,
|
||||||
|
LCJsonConverter.Default);
|
||||||
return convs.Select(item => {
|
return convs.Select(item => {
|
||||||
Dictionary<string, object> conv = item as Dictionary<string, object>;
|
Dictionary<string, object> conv = item as Dictionary<string, object>;
|
||||||
string convId = conv["objectId"] as string;
|
string convId = conv["objectId"] as string;
|
||||||
|
@ -478,7 +478,8 @@ namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
request.ConvMessage = convMessage;
|
request.ConvMessage = convMessage;
|
||||||
GenericCommand response = await Connection.SendRequest(request);
|
GenericCommand response = await Connection.SendRequest(request);
|
||||||
JsonObjectMessage results = response.ConvMessage.Results;
|
JsonObjectMessage results = response.ConvMessage.Results;
|
||||||
List<object> convs = JsonConvert.DeserializeObject<List<object>>(results.Data, new LCJsonConverter());
|
List<object> convs = JsonConvert.DeserializeObject<List<object>>(results.Data,
|
||||||
|
LCJsonConverter.Default);
|
||||||
List<LCIMTemporaryConversation> convList = convs.Select(item => {
|
List<LCIMTemporaryConversation> convList = convs.Select(item => {
|
||||||
LCIMTemporaryConversation temporaryConversation = new LCIMTemporaryConversation(Client);
|
LCIMTemporaryConversation temporaryConversation = new LCIMTemporaryConversation(Client);
|
||||||
temporaryConversation.MergeFrom(item as Dictionary<string, object>);
|
temporaryConversation.MergeFrom(item as Dictionary<string, object>);
|
||||||
|
@ -800,7 +801,7 @@ namespace LeanCloud.Realtime.Internal.Controller {
|
||||||
private async Task OnPropertiesUpdated(ConvCommand conv) {
|
private async Task OnPropertiesUpdated(ConvCommand conv) {
|
||||||
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
LCIMConversation conversation = await Client.GetOrQueryConversation(conv.Cid);
|
||||||
Dictionary<string, object> updatedAttr = JsonConvert.DeserializeObject<Dictionary<string, object>>(conv.AttrModified.Data,
|
Dictionary<string, object> updatedAttr = JsonConvert.DeserializeObject<Dictionary<string, object>>(conv.AttrModified.Data,
|
||||||
new LCJsonConverter());
|
LCJsonConverter.Default);
|
||||||
// 更新内存数据
|
// 更新内存数据
|
||||||
conversation.MergeInfo(updatedAttr);
|
conversation.MergeInfo(updatedAttr);
|
||||||
Client.OnConversationInfoUpdated?.Invoke(conversation,
|
Client.OnConversationInfoUpdated?.Invoke(conversation,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using LeanCloud.Storage.Internal;
|
|
||||||
using LeanCloud.Common;
|
using LeanCloud.Common;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
@ -9,7 +8,7 @@ namespace LeanCloud.Realtime.Internal.Router {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// RTM Router
|
/// RTM Router
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class LCRTMRouter {
|
public class LCRTMRouter {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 请求超时
|
/// 请求超时
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -17,14 +16,14 @@ namespace LeanCloud.Realtime.Internal.Router {
|
||||||
|
|
||||||
private LCRTMServer rtmServer;
|
private LCRTMServer rtmServer;
|
||||||
|
|
||||||
internal LCRTMRouter() {
|
public LCRTMRouter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取服务器地址
|
/// 获取服务器地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal async Task<LCRTMServer> GetServer() {
|
public async Task<LCRTMServer> GetServer() {
|
||||||
if (rtmServer == null || !rtmServer.IsValid) {
|
if (rtmServer == null || !rtmServer.IsValid) {
|
||||||
await Fetch();
|
await Fetch();
|
||||||
}
|
}
|
||||||
|
@ -53,7 +52,7 @@ namespace LeanCloud.Realtime.Internal.Router {
|
||||||
response.Dispose();
|
response.Dispose();
|
||||||
LCHttpUtils.PrintResponse(response, resultString);
|
LCHttpUtils.PrintResponse(response, resultString);
|
||||||
|
|
||||||
rtmServer = JsonConvert.DeserializeObject<LCRTMServer>(resultString, new LCJsonConverter());
|
rtmServer = JsonConvert.DeserializeObject<LCRTMServer>(resultString, LCJsonConverter.Default);
|
||||||
|
|
||||||
return rtmServer;
|
return rtmServer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,38 +2,38 @@
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal.Router {
|
namespace LeanCloud.Realtime.Internal.Router {
|
||||||
internal class LCRTMServer {
|
public class LCRTMServer {
|
||||||
[JsonProperty("groupId")]
|
[JsonProperty("groupId")]
|
||||||
internal string GroupId {
|
public string GroupId {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("groupUrl")]
|
[JsonProperty("groupUrl")]
|
||||||
internal string GroupUrl {
|
public string GroupUrl {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("server")]
|
[JsonProperty("server")]
|
||||||
internal string Primary {
|
public string Primary {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("secondary")]
|
[JsonProperty("secondary")]
|
||||||
internal string Secondary {
|
public string Secondary {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("ttl")]
|
[JsonProperty("ttl")]
|
||||||
internal int Ttl {
|
public int Ttl {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTimeOffset createdAt;
|
DateTimeOffset createdAt;
|
||||||
|
|
||||||
internal LCRTMServer() {
|
public LCRTMServer() {
|
||||||
createdAt = DateTimeOffset.Now;
|
createdAt = DateTimeOffset.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool IsValid => DateTimeOffset.Now < createdAt + TimeSpan.FromSeconds(Ttl);
|
public bool IsValid => DateTimeOffset.Now < createdAt + TimeSpan.FromSeconds(Ttl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace LeanCloud.Realtime.Internal.WebSocket {
|
namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// WebSocket 客户端,负责底层连接和事件,只与通信协议相关
|
/// WebSocket 客户端,负责底层连接和事件,只与通信协议相关
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class LCWebSocketClient {
|
public class LCWebSocketClient {
|
||||||
// .net standard 2.0 好像在拼合 Frame 时有 bug,所以将这个值调整大一些
|
// .net standard 2.0 好像在拼合 Frame 时有 bug,所以将这个值调整大一些
|
||||||
private const int RECV_BUFFER_SIZE = 1024 * 5;
|
private const int RECV_BUFFER_SIZE = 1024 * 5;
|
||||||
|
|
||||||
|
@ -23,12 +24,12 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 消息事件
|
/// 消息事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Action<byte[]> OnMessage;
|
public Action<byte[]> OnMessage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 连接关闭
|
/// 连接关闭
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Action OnClose;
|
public Action OnClose;
|
||||||
|
|
||||||
private ClientWebSocket ws;
|
private ClientWebSocket ws;
|
||||||
|
|
||||||
|
@ -37,11 +38,14 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="server"></param>
|
/// <param name="server"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal async Task Connect(string server) {
|
public async Task Connect(string server,
|
||||||
|
string subProtocol = null) {
|
||||||
LCLogger.Debug($"Connecting WebSocket: {server}");
|
LCLogger.Debug($"Connecting WebSocket: {server}");
|
||||||
Task timeoutTask = Task.Delay(CONNECT_TIMEOUT);
|
Task timeoutTask = Task.Delay(CONNECT_TIMEOUT);
|
||||||
ws = new ClientWebSocket();
|
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);
|
Task connectTask = ws.ConnectAsync(new Uri(server), default);
|
||||||
if (await Task.WhenAny(connectTask, timeoutTask) == connectTask) {
|
if (await Task.WhenAny(connectTask, timeoutTask) == connectTask) {
|
||||||
LCLogger.Debug($"Connected WebSocket: {server}");
|
LCLogger.Debug($"Connected WebSocket: {server}");
|
||||||
|
@ -57,7 +61,7 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
/// 主动关闭连接
|
/// 主动关闭连接
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal async Task Close() {
|
public async Task Close() {
|
||||||
LCLogger.Debug("Closing WebSocket");
|
LCLogger.Debug("Closing WebSocket");
|
||||||
OnMessage = null;
|
OnMessage = null;
|
||||||
OnClose = null;
|
OnClose = null;
|
||||||
|
@ -81,11 +85,12 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"></param>
|
/// <param name="data"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
internal async Task Send(byte[] data) {
|
public async Task Send(byte[] data,
|
||||||
|
WebSocketMessageType messageType = WebSocketMessageType.Binary) {
|
||||||
ArraySegment<byte> bytes = new ArraySegment<byte>(data);
|
ArraySegment<byte> bytes = new ArraySegment<byte>(data);
|
||||||
if (ws.State == WebSocketState.Open) {
|
if (ws.State == WebSocketState.Open) {
|
||||||
try {
|
try {
|
||||||
await ws.SendAsync(bytes, WebSocketMessageType.Binary, true, default);
|
await ws.SendAsync(bytes, messageType, true, default);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LCLogger.Error(e);
|
LCLogger.Error(e);
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -97,6 +102,15 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送文本数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task Send(string text) {
|
||||||
|
await Send(Encoding.UTF8.GetBytes(text), WebSocketMessageType.Text);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 接收数据
|
/// 接收数据
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -119,14 +133,12 @@ namespace LeanCloud.Realtime.Internal.WebSocket {
|
||||||
HandleExceptionClose();
|
HandleExceptionClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (result.MessageType == WebSocketMessageType.Binary) {
|
} else {
|
||||||
// 拼合 WebSocket Message
|
// 拼合 WebSocket Message
|
||||||
int length = result.Count;
|
int length = result.Count;
|
||||||
byte[] data = new byte[length];
|
byte[] data = new byte[length];
|
||||||
Array.Copy(buffer, data, length);
|
Array.Copy(buffer, data, length);
|
||||||
OnMessage?.Invoke(data);
|
OnMessage?.Invoke(data);
|
||||||
} else {
|
|
||||||
LCLogger.Error($"Error message type: {result.MessageType}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using LeanCloud.Storage.Internal.Codec;
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
using LeanCloud.Storage.Internal;
|
using LeanCloud.Common;
|
||||||
|
|
||||||
namespace LeanCloud.Realtime {
|
namespace LeanCloud.Realtime {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -115,7 +115,7 @@ namespace LeanCloud.Realtime {
|
||||||
|
|
||||||
internal static LCIMTypedMessage Deserialize(string json) {
|
internal static LCIMTypedMessage Deserialize(string json) {
|
||||||
Dictionary<string, object> msgData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json,
|
Dictionary<string, object> msgData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json,
|
||||||
new LCJsonConverter());
|
LCJsonConverter.Default);
|
||||||
LCIMTypedMessage message = null;
|
LCIMTypedMessage message = null;
|
||||||
int msgType = (int)msgData[MessageTypeKey];
|
int msgType = (int)msgData[MessageTypeKey];
|
||||||
if (customMessageDict.TryGetValue(msgType, out Func<LCIMTypedMessage> msgConstructor)) {
|
if (customMessageDict.TryGetValue(msgType, out Func<LCIMTypedMessage> msgConstructor)) {
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\LiveQuery\LiveQuery\LiveQuery.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +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) {
|
||||||
|
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<LCUser> userQuery = LCUser.GetQuery();
|
||||||
|
userQuery.WhereEqualTo("username", "hello");
|
||||||
|
LCLiveQuery userLiveQuery = await userQuery.Subscribe();
|
||||||
|
userLiveQuery.OnLogin = (user) => {
|
||||||
|
WriteLine($"login: {user.Username}");
|
||||||
|
};
|
||||||
|
|
||||||
|
LCQuery<LCObject> query = new LCQuery<LCObject>("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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LiveQueryApp {
|
||||||
|
/// <summary>
|
||||||
|
/// 单线程环境,用于控制台应用 await 返回
|
||||||
|
/// </summary>
|
||||||
|
public class SingleThreadSynchronizationContext : SynchronizationContext {
|
||||||
|
private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
|
||||||
|
|
||||||
|
public override void Post(SendOrPostCallback d, object state) {
|
||||||
|
queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RunOnCurrentThread() {
|
||||||
|
while (queue.TryTake(out KeyValuePair<SendOrPostCallback, object> workItem, Timeout.Infinite)) {
|
||||||
|
workItem.Key(workItem.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Complete() {
|
||||||
|
queue.CompleteAdding();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Run(Func<Task> func) {
|
||||||
|
SynchronizationContext prevContext = Current;
|
||||||
|
try {
|
||||||
|
SingleThreadSynchronizationContext syncContext = new SingleThreadSynchronizationContext();
|
||||||
|
SetSynchronizationContext(syncContext);
|
||||||
|
|
||||||
|
Task t = func();
|
||||||
|
syncContext.RunOnCurrentThread();
|
||||||
|
|
||||||
|
t.GetAwaiter().GetResult();
|
||||||
|
} finally {
|
||||||
|
SetSynchronizationContext(prevContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -99,9 +99,6 @@
|
||||||
<Compile Include="..\Storage\Internal\Http\LCHttpClient.cs">
|
<Compile Include="..\Storage\Internal\Http\LCHttpClient.cs">
|
||||||
<Link>Internal\Http\LCHttpClient.cs</Link>
|
<Link>Internal\Http\LCHttpClient.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="..\Storage\Internal\Http\LCJsonConverter.cs">
|
|
||||||
<Link>Internal\Http\LCJsonConverter.cs</Link>
|
|
||||||
</Compile>
|
|
||||||
<Compile Include="..\Storage\Internal\Query\LCEqualCondition.cs">
|
<Compile Include="..\Storage\Internal\Query\LCEqualCondition.cs">
|
||||||
<Link>Internal\Query\LCEqualCondition.cs</Link>
|
<Link>Internal\Query\LCEqualCondition.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -61,7 +61,8 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
LCHttpUtils.PrintResponse(response, resultString);
|
LCHttpUtils.PrintResponse(response, resultString);
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode) {
|
if (response.IsSuccessStatusCode) {
|
||||||
T ret = JsonConvert.DeserializeObject<T>(resultString, new LCJsonConverter());
|
T ret = JsonConvert.DeserializeObject<T>(resultString,
|
||||||
|
LCJsonConverter.Default);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
throw HandleErrorResponse(response.StatusCode, resultString);
|
throw HandleErrorResponse(response.StatusCode, resultString);
|
||||||
|
@ -94,7 +95,8 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
LCHttpUtils.PrintResponse(response, resultString);
|
LCHttpUtils.PrintResponse(response, resultString);
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode) {
|
if (response.IsSuccessStatusCode) {
|
||||||
T ret = JsonConvert.DeserializeObject<T>(resultString, new LCJsonConverter());
|
T ret = JsonConvert.DeserializeObject<T>(resultString,
|
||||||
|
LCJsonConverter.Default);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
throw HandleErrorResponse(response.StatusCode, resultString);
|
throw HandleErrorResponse(response.StatusCode, resultString);
|
||||||
|
@ -127,7 +129,8 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
LCHttpUtils.PrintResponse(response, resultString);
|
LCHttpUtils.PrintResponse(response, resultString);
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode) {
|
if (response.IsSuccessStatusCode) {
|
||||||
T ret = JsonConvert.DeserializeObject<T>(resultString, new LCJsonConverter());
|
T ret = JsonConvert.DeserializeObject<T>(resultString,
|
||||||
|
LCJsonConverter.Default);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
throw HandleErrorResponse(response.StatusCode, resultString);
|
throw HandleErrorResponse(response.StatusCode, resultString);
|
||||||
|
@ -150,7 +153,8 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
LCHttpUtils.PrintResponse(response, resultString);
|
LCHttpUtils.PrintResponse(response, resultString);
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode) {
|
if (response.IsSuccessStatusCode) {
|
||||||
Dictionary<string, object> ret = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString, new LCJsonConverter());
|
Dictionary<string, object> ret = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString,
|
||||||
|
LCJsonConverter.Default);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw HandleErrorResponse(response.StatusCode, resultString);
|
throw HandleErrorResponse(response.StatusCode, resultString);
|
||||||
|
@ -161,7 +165,8 @@ namespace LeanCloud.Storage.Internal.Http {
|
||||||
string message = responseContent;
|
string message = responseContent;
|
||||||
try {
|
try {
|
||||||
// 尝试获取 LeanCloud 返回错误信息
|
// 尝试获取 LeanCloud 返回错误信息
|
||||||
Dictionary<string, object> error = JsonConvert.DeserializeObject<Dictionary<string, object>>(responseContent, new LCJsonConverter());
|
Dictionary<string, object> error = JsonConvert.DeserializeObject<Dictionary<string, object>>(responseContent,
|
||||||
|
LCJsonConverter.Default);
|
||||||
code = (int)error["code"];
|
code = (int)error["code"];
|
||||||
message = error["error"].ToString();
|
message = error["error"].ToString();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -4,30 +4,30 @@ using System.Collections.Generic;
|
||||||
using LeanCloud.Storage.Internal.Codec;
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
|
|
||||||
namespace LeanCloud.Storage.Internal.Object {
|
namespace LeanCloud.Storage.Internal.Object {
|
||||||
internal class LCObjectData {
|
public class LCObjectData {
|
||||||
internal string ClassName {
|
public string ClassName {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string ObjectId {
|
public string ObjectId {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DateTime CreatedAt {
|
public DateTime CreatedAt {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DateTime UpdatedAt {
|
public DateTime UpdatedAt {
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Dictionary<string, object> CustomPropertyDict;
|
public Dictionary<string, object> CustomPropertyDict;
|
||||||
|
|
||||||
internal LCObjectData() {
|
public LCObjectData() {
|
||||||
CustomPropertyDict = new Dictionary<string, object>();
|
CustomPropertyDict = new Dictionary<string, object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static LCObjectData Decode(IDictionary dict) {
|
public static LCObjectData Decode(IDictionary dict) {
|
||||||
if (dict == null) {
|
if (dict == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ namespace LeanCloud.Storage.Internal.Object {
|
||||||
return objectData;
|
return objectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Dictionary<string, object> Encode(LCObjectData objectData) {
|
public static Dictionary<string, object> Encode(LCObjectData objectData) {
|
||||||
if (objectData == null) {
|
if (objectData == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ namespace LeanCloud.Storage.Internal.Query {
|
||||||
|
|
||||||
public void WhereMatchesQuery<K>(string key, LCQuery<K> query) where K : LCObject {
|
public void WhereMatchesQuery<K>(string key, LCQuery<K> query) where K : LCObject {
|
||||||
Dictionary<string, object> inQuery = new Dictionary<string, object> {
|
Dictionary<string, object> inQuery = new Dictionary<string, object> {
|
||||||
{ "where", query.condition },
|
{ "where", query.Condition },
|
||||||
{ "className", query.ClassName }
|
{ "className", query.ClassName }
|
||||||
};
|
};
|
||||||
AddOperation(key, "$inQuery", inQuery);
|
AddOperation(key, "$inQuery", inQuery);
|
||||||
|
@ -126,7 +126,7 @@ namespace LeanCloud.Storage.Internal.Query {
|
||||||
|
|
||||||
public void WhereDoesNotMatchQuery<K>(string key, LCQuery<K> query) where K : LCObject {
|
public void WhereDoesNotMatchQuery<K>(string key, LCQuery<K> query) where K : LCObject {
|
||||||
Dictionary<string, object> inQuery = new Dictionary<string, object> {
|
Dictionary<string, object> inQuery = new Dictionary<string, object> {
|
||||||
{ "where", query.condition },
|
{ "where", query.Condition },
|
||||||
{ "className", query.ClassName }
|
{ "className", query.ClassName }
|
||||||
};
|
};
|
||||||
AddOperation(key, "$notInQuery", inQuery);
|
AddOperation(key, "$notInQuery", inQuery);
|
||||||
|
|
|
@ -93,7 +93,7 @@ namespace LeanCloud.Storage {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static LCObject Create(string className) {
|
public static LCObject Create(string className) {
|
||||||
if (subclassNameDict.TryGetValue(className, out LCSubclassInfo subclassInfo)) {
|
if (subclassNameDict.TryGetValue(className, out LCSubclassInfo subclassInfo)) {
|
||||||
return subclassInfo.Constructor.Invoke();
|
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.ClassName = objectData.ClassName ?? data.ClassName;
|
||||||
data.ObjectId = objectData.ObjectId ?? data.ObjectId;
|
data.ObjectId = objectData.ObjectId ?? data.ObjectId;
|
||||||
data.CreatedAt = objectData.CreatedAt != null ? objectData.CreatedAt : data.CreatedAt;
|
data.CreatedAt = objectData.CreatedAt != null ? objectData.CreatedAt : data.CreatedAt;
|
||||||
|
|
|
@ -8,20 +8,37 @@ using LeanCloud.Storage.Internal.Query;
|
||||||
using LeanCloud.Storage.Internal.Object;
|
using LeanCloud.Storage.Internal.Object;
|
||||||
|
|
||||||
namespace LeanCloud.Storage {
|
namespace LeanCloud.Storage {
|
||||||
|
public class LCQuery {
|
||||||
|
public string ClassName {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCCompositionalCondition Condition {
|
||||||
|
get; internal set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCQuery(string className) {
|
||||||
|
ClassName = className;
|
||||||
|
Condition = new LCCompositionalCondition();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Dictionary<string, object> BuildParams() {
|
||||||
|
return Condition.BuildParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string BuildWhere() {
|
||||||
|
return Condition.BuildWhere();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询类
|
/// 查询类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
/// <typeparam name="T"></typeparam>
|
||||||
public class LCQuery<T> where T : LCObject {
|
public class LCQuery<T> : LCQuery where T : LCObject {
|
||||||
public string ClassName {
|
public LCQuery(string className) :
|
||||||
get; private set;
|
base(className) {
|
||||||
}
|
|
||||||
|
|
||||||
internal LCCompositionalCondition condition;
|
|
||||||
|
|
||||||
public LCQuery(string className) {
|
|
||||||
ClassName = className;
|
|
||||||
condition = new LCCompositionalCondition();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -31,7 +48,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereEqualTo(string key, object value) {
|
public LCQuery<T> WhereEqualTo(string key, object value) {
|
||||||
condition.WhereEqualTo(key, value);
|
Condition.WhereEqualTo(key, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +59,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereNotEqualTo(string key, object value) {
|
public LCQuery<T> WhereNotEqualTo(string key, object value) {
|
||||||
condition.WhereNotEqualTo(key, value);
|
Condition.WhereNotEqualTo(key, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +70,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="values"></param>
|
/// <param name="values"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereContainedIn(string key, IEnumerable values) {
|
public LCQuery<T> WhereContainedIn(string key, IEnumerable values) {
|
||||||
condition.WhereContainedIn(key, values);
|
Condition.WhereContainedIn(key, values);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +81,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="values"></param>
|
/// <param name="values"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereNotContainedIn(string key, IEnumerable values) {
|
public LCQuery<T> WhereNotContainedIn(string key, IEnumerable values) {
|
||||||
condition.WhereNotContainedIn(key, values);
|
Condition.WhereNotContainedIn(key, values);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +92,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="values"></param>
|
/// <param name="values"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereContainsAll(string key, IEnumerable values) {
|
public LCQuery<T> WhereContainsAll(string key, IEnumerable values) {
|
||||||
condition.WhereContainsAll(key, values);
|
Condition.WhereContainsAll(key, values);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +102,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereExists(string key) {
|
public LCQuery<T> WhereExists(string key) {
|
||||||
condition.WhereExists(key);
|
Condition.WhereExists(key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +112,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereDoesNotExist(string key) {
|
public LCQuery<T> WhereDoesNotExist(string key) {
|
||||||
condition.WhereDoesNotExist(key);
|
Condition.WhereDoesNotExist(key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +123,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="size"></param>
|
/// <param name="size"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereSizeEqualTo(string key, int size) {
|
public LCQuery<T> WhereSizeEqualTo(string key, int size) {
|
||||||
condition.WhereSizeEqualTo(key, size);
|
Condition.WhereSizeEqualTo(key, size);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +134,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereGreaterThan(string key, object value) {
|
public LCQuery<T> WhereGreaterThan(string key, object value) {
|
||||||
condition.WhereGreaterThan(key, value);
|
Condition.WhereGreaterThan(key, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +145,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereGreaterThanOrEqualTo(string key, object value) {
|
public LCQuery<T> WhereGreaterThanOrEqualTo(string key, object value) {
|
||||||
condition.WhereGreaterThanOrEqualTo(key, value);
|
Condition.WhereGreaterThanOrEqualTo(key, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +156,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereLessThan(string key, object value) {
|
public LCQuery<T> WhereLessThan(string key, object value) {
|
||||||
condition.WhereLessThan(key, value);
|
Condition.WhereLessThan(key, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +167,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereLessThanOrEqualTo(string key, object value) {
|
public LCQuery<T> WhereLessThanOrEqualTo(string key, object value) {
|
||||||
condition.WhereLessThanOrEqualTo(key, value);
|
Condition.WhereLessThanOrEqualTo(key, value);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +178,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="point"></param>
|
/// <param name="point"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereNear(string key, LCGeoPoint point) {
|
public LCQuery<T> WhereNear(string key, LCGeoPoint point) {
|
||||||
condition.WhereNear(key, point);
|
Condition.WhereNear(key, point);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,7 +190,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="northeast"></param>
|
/// <param name="northeast"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereWithinGeoBox(string key, LCGeoPoint southwest, LCGeoPoint northeast) {
|
public LCQuery<T> WhereWithinGeoBox(string key, LCGeoPoint southwest, LCGeoPoint northeast) {
|
||||||
condition.WhereWithinGeoBox(key, southwest, northeast);
|
Condition.WhereWithinGeoBox(key, southwest, northeast);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +201,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereRelatedTo(LCObject parent, string key) {
|
public LCQuery<T> WhereRelatedTo(LCObject parent, string key) {
|
||||||
condition.WhereRelatedTo(parent, key);
|
Condition.WhereRelatedTo(parent, key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +212,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="prefix"></param>
|
/// <param name="prefix"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereStartsWith(string key, string prefix) {
|
public LCQuery<T> WhereStartsWith(string key, string prefix) {
|
||||||
condition.WhereStartsWith(key, prefix);
|
Condition.WhereStartsWith(key, prefix);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +223,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="suffix"></param>
|
/// <param name="suffix"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereEndsWith(string key, string suffix) {
|
public LCQuery<T> WhereEndsWith(string key, string suffix) {
|
||||||
condition.WhereEndsWith(key, suffix);
|
Condition.WhereEndsWith(key, suffix);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +234,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="subString"></param>
|
/// <param name="subString"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereContains(string key, string subString) {
|
public LCQuery<T> WhereContains(string key, string subString) {
|
||||||
condition.WhereContains(key, subString);
|
Condition.WhereContains(key, subString);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +246,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="modifiers"></param>
|
/// <param name="modifiers"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereMatches(string key, string regex, string modifiers = null) {
|
public LCQuery<T> WhereMatches(string key, string regex, string modifiers = null) {
|
||||||
condition.WhereMatches(key, regex, modifiers);
|
Condition.WhereMatches(key, regex, modifiers);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +257,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="query"></param>
|
/// <param name="query"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereMatchesQuery<K>(string key, LCQuery<K> query) where K : LCObject {
|
public LCQuery<T> WhereMatchesQuery<K>(string key, LCQuery<K> query) where K : LCObject {
|
||||||
condition.WhereMatchesQuery(key, query);
|
Condition.WhereMatchesQuery(key, query);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +269,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="query"></param>
|
/// <param name="query"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> WhereDoesNotMatchQuery<K>(string key, LCQuery<K> query) where K : LCObject {
|
public LCQuery<T> WhereDoesNotMatchQuery<K>(string key, LCQuery<K> query) where K : LCObject {
|
||||||
condition.WhereDoesNotMatchQuery(key, query);
|
Condition.WhereDoesNotMatchQuery(key, query);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +279,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> OrderByAscending(string key) {
|
public LCQuery<T> OrderByAscending(string key) {
|
||||||
condition.OrderByAscending(key);
|
Condition.OrderByAscending(key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,7 +289,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> OrderByDescending(string key) {
|
public LCQuery<T> OrderByDescending(string key) {
|
||||||
condition.OrderByDescending(key);
|
Condition.OrderByDescending(key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +299,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> AddAscendingOrder(string key) {
|
public LCQuery<T> AddAscendingOrder(string key) {
|
||||||
condition.AddAscendingOrder(key);
|
Condition.AddAscendingOrder(key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,7 +309,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> AddDescendingOrder(string key) {
|
public LCQuery<T> AddDescendingOrder(string key) {
|
||||||
condition.AddDescendingOrder(key);
|
Condition.AddDescendingOrder(key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +319,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> Include(string key) {
|
public LCQuery<T> Include(string key) {
|
||||||
condition.Include(key);
|
Condition.Include(key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,7 +329,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="key"></param>
|
/// <param name="key"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> Select(string key) {
|
public LCQuery<T> Select(string key) {
|
||||||
condition.Select(key);
|
Condition.Select(key);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +339,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> Skip(int value) {
|
public LCQuery<T> Skip(int value) {
|
||||||
condition.Skip = value;
|
Condition.Skip = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,7 +349,7 @@ namespace LeanCloud.Storage {
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public LCQuery<T> Limit(int value) {
|
public LCQuery<T> Limit(int value) {
|
||||||
condition.Limit = value;
|
Condition.Limit = value;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,7 +413,7 @@ namespace LeanCloud.Storage {
|
||||||
throw new Exception("All of the queries in an or query must be on the same class.");
|
throw new Exception("All of the queries in an or query must be on the same class.");
|
||||||
}
|
}
|
||||||
className = query.ClassName;
|
className = query.ClassName;
|
||||||
compositionQuery.condition.Add(query.condition);
|
compositionQuery.Condition.Add(query.Condition);
|
||||||
}
|
}
|
||||||
compositionQuery.ClassName = className;
|
compositionQuery.ClassName = className;
|
||||||
return compositionQuery;
|
return compositionQuery;
|
||||||
|
@ -407,25 +424,17 @@ namespace LeanCloud.Storage {
|
||||||
throw new ArgumentNullException(nameof(queries));
|
throw new ArgumentNullException(nameof(queries));
|
||||||
}
|
}
|
||||||
LCQuery<T> compositionQuery = new LCQuery<T>(null);
|
LCQuery<T> compositionQuery = new LCQuery<T>(null);
|
||||||
compositionQuery.condition = new LCCompositionalCondition(LCCompositionalCondition.Or);
|
compositionQuery.Condition = new LCCompositionalCondition(LCCompositionalCondition.Or);
|
||||||
string className = null;
|
string className = null;
|
||||||
foreach (LCQuery<T> query in queries) {
|
foreach (LCQuery<T> query in queries) {
|
||||||
if (className != null && className != query.ClassName) {
|
if (className != null && className != query.ClassName) {
|
||||||
throw new Exception("All of the queries in an or query must be on the same class.");
|
throw new Exception("All of the queries in an or query must be on the same class.");
|
||||||
}
|
}
|
||||||
className = query.ClassName;
|
className = query.ClassName;
|
||||||
compositionQuery.condition.Add(query.condition);
|
compositionQuery.Condition.Add(query.Condition);
|
||||||
}
|
}
|
||||||
compositionQuery.ClassName = className;
|
compositionQuery.ClassName = className;
|
||||||
return compositionQuery;
|
return compositionQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<string, object> BuildParams() {
|
|
||||||
return condition.BuildParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal string BuildWhere() {
|
|
||||||
return condition.BuildWhere();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ namespace LeanCloud.Storage {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal LCUser(LCObjectData objectData) : this() {
|
public LCUser(LCObjectData objectData) : this() {
|
||||||
Merge(objectData);
|
Merge(objectData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Realtime", "Realtime\Realti
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealtimeApp", "Sample\RealtimeApp\RealtimeApp.csproj", "{A716EFC7-9220-4A9A-9F73-B816A0787F77}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealtimeApp", "Sample\RealtimeApp\RealtimeApp.csproj", "{A716EFC7-9220-4A9A-9F73-B816A0787F77}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{A716EFC7-9220-4A9A-9F73-B816A0787F77}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{26CDAE2A-6D79-4981-8D80-3EA34FDFB134} = {319A9989-3B69-4AD0-9E43-F6D31C1D2A4A}
|
{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}
|
{882A9419-CC5E-4CFB-B076-7561989B0A4A} = {319A9989-3B69-4AD0-9E43-F6D31C1D2A4A}
|
||||||
{75A3A4EC-93B8-40C9-AE04-DF14A72525CC} = {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}
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(MonoDevelopProperties) = preSolution
|
GlobalSection(MonoDevelopProperties) = preSolution
|
||||||
version = 0.1.0
|
version = 0.1.0
|
||||||
|
|
|
@ -16,4 +16,8 @@ pack ./Storage/Storage-Unity/bin/Release/netstandard2.0/ ./Plugins LeanCloud-SDK
|
||||||
|
|
||||||
# Realtime
|
# Realtime
|
||||||
pack ./Realtime/Realtime/bin/Release/netstandard2.0/ ./DLLs LeanCloud-SDK-Realtime-Standard.zip
|
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
|
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
|
Loading…
Reference in New Issue