chore: 框架
parent
148c4d6ae2
commit
e4381667c8
|
@ -10,10 +10,10 @@ namespace LeanCloud.Test {
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() {
|
public void SetUp() {
|
||||||
Utils.InitNorthChina();
|
//Utils.InitNorthChina();
|
||||||
//Utils.InitEastChina();
|
//Utils.InitEastChina();
|
||||||
//Utils.InitOldEastChina();
|
//Utils.InitOldEastChina();
|
||||||
//Utils.InitUS();
|
Utils.InitUS();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test, Order(0)]
|
[Test, Order(0)]
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using LeanCloud.Storage;
|
||||||
|
|
||||||
|
namespace LeanCloud.Test {
|
||||||
|
public class HelloTest {
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() {
|
||||||
|
LeanCloud.Initialize("ikGGdRE2YcVOemAaRbgp1xGJ-gzGzoHsz", "NUKmuRbdAhg1vrb2wexYo1jo", "https://ikggdre2.lc-cn-n1-shared.com");
|
||||||
|
Logger.LogDelegate += Utils.Print;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Run() {
|
||||||
|
Dictionary<string, object> parameters = new Dictionary<string, object> {
|
||||||
|
{ "name", "world" }
|
||||||
|
};
|
||||||
|
Dictionary<string, object> response = await LCCloud.Run("hello", parameters);
|
||||||
|
string ret = response["result"] as string;
|
||||||
|
TestContext.WriteLine($"ret: {ret}");
|
||||||
|
Assert.AreEqual(ret, "hello, world");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using LeanCloud.Storage.Internal.Object;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal.Codec {
|
||||||
|
internal static class LCDecoder {
|
||||||
|
internal static object Decode(object obj) {
|
||||||
|
if (obj is IDictionary dict) {
|
||||||
|
if (dict.Contains("__type")) {
|
||||||
|
string type = dict["__type"].ToString();
|
||||||
|
if (type == "Date") {
|
||||||
|
return DecodeDate(dict);
|
||||||
|
} else if (type == "Bytes") {
|
||||||
|
return DecodeBytes(dict);
|
||||||
|
} else if (type == "Object") {
|
||||||
|
return DecodeObject(dict);
|
||||||
|
} else if (type == "Pointer") {
|
||||||
|
return DecodeObject(dict);
|
||||||
|
} else if (type == "Relation") {
|
||||||
|
return DecodeRelation(dict);
|
||||||
|
} else if (type == "GeoPoint") {
|
||||||
|
return DecodeGeoPoint(dict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Dictionary<string, object> d = new Dictionary<string, object>();
|
||||||
|
foreach (KeyValuePair<string, object> kv in dict) {
|
||||||
|
string key = kv.Key;
|
||||||
|
object value = kv.Value;
|
||||||
|
d[key] = Decode(value);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
} else if (obj is IList list) {
|
||||||
|
List<object> l = new List<object>();
|
||||||
|
foreach (object o in list) {
|
||||||
|
object v = Decode(o);
|
||||||
|
l.Add(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime DecodeDate(IDictionary dict) {
|
||||||
|
string str = dict["iso"].ToString();
|
||||||
|
DateTime dateTime = DateTime.Parse(str);
|
||||||
|
return dateTime.ToLocalTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] DecodeBytes(IDictionary dict) {
|
||||||
|
string str = dict["base64"].ToString();
|
||||||
|
byte[] bytes = Convert.FromBase64String(str);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LCObject DecodeObject(IDictionary dict) {
|
||||||
|
string className = dict["className"].ToString();
|
||||||
|
LCObject obj = LCObject.Create(className);
|
||||||
|
LCObjectData objectData = LCObjectData.Decode(dict as Dictionary<string, object>);
|
||||||
|
// TODO merge
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LCRelation<LCObject> DecodeRelation(IDictionary dict) {
|
||||||
|
LCRelation<LCObject> relation = new LCRelation<LCObject>();
|
||||||
|
relation.targetClass = dict["className"].ToString();
|
||||||
|
return relation;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LCGeoPoint DecodeGeoPoint(IDictionary data) {
|
||||||
|
double latitude = double.Parse(data["latitude"].ToString());
|
||||||
|
double longitude = double.Parse(data["longitude"].ToString());
|
||||||
|
LCGeoPoint geoPoint = new LCGeoPoint(latitude, longitude);
|
||||||
|
return geoPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using LeanCloud.Storage.Internal.Operation;
|
||||||
|
using LeanCloud.Storage.Internal.Query;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal.Codec {
|
||||||
|
internal static class LCEncoder {
|
||||||
|
internal static object Encode(object obj) {
|
||||||
|
if (obj is DateTime dateTime) {
|
||||||
|
return EncodeDateTime(dateTime);
|
||||||
|
} else if (obj is byte[] bytes) {
|
||||||
|
return EncodeBytes(bytes);
|
||||||
|
} else if (obj is IList list) {
|
||||||
|
return EncodeList(list);
|
||||||
|
} else if (obj is IDictionary dict) {
|
||||||
|
return EncodeDictionary(dict);
|
||||||
|
} else if (obj is LCObject lcObj) {
|
||||||
|
return EncodeLCObject(lcObj);
|
||||||
|
} else if (obj is LCOperation op) {
|
||||||
|
return EncodeOperation(op);
|
||||||
|
} else if (obj is ILCQueryCondition cond) {
|
||||||
|
return EncodeQueryCondition(cond);
|
||||||
|
} else if (obj is LCACL acl) {
|
||||||
|
return EncodeACL(acl);
|
||||||
|
} else if (obj is LCRelation<LCObject> relation) {
|
||||||
|
return EncodeRelation(relation);
|
||||||
|
} else if (obj is LCGeoPoint geoPoint) {
|
||||||
|
return EncodeGeoPoint(geoPoint);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeDateTime(DateTime dateTime) {
|
||||||
|
DateTime utc = dateTime.ToUniversalTime();
|
||||||
|
string str = utc.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ "__type", "Date" },
|
||||||
|
{ "iso", str }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeBytes(byte[] bytes) {
|
||||||
|
string str = Convert.ToBase64String(bytes);
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ "__type", "Bytes" },
|
||||||
|
{ "base64", str }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeList(IList list) {
|
||||||
|
List<object> l = new List<object>();
|
||||||
|
foreach (object obj in list) {
|
||||||
|
l.Add(Encode(obj));
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeDictionary(IDictionary dict) {
|
||||||
|
Dictionary<string, object> d = new Dictionary<string, object>();
|
||||||
|
foreach (KeyValuePair<string, object> kv in dict) {
|
||||||
|
string key = kv.Key;
|
||||||
|
object value = kv.Value;
|
||||||
|
d[key] = value;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeLCObject(LCObject obj) {
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ "__type", "Pointer" },
|
||||||
|
{ "className", obj.ClassName },
|
||||||
|
{ "objectId", obj.ObjectId }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeOperation(LCOperation operation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeQueryCondition(ILCQueryCondition cond) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeACL(LCACL acl) {
|
||||||
|
HashSet<string> readers = acl.readers;
|
||||||
|
HashSet<string> writers = acl.writers;
|
||||||
|
HashSet<string> union = new HashSet<string>(readers);
|
||||||
|
union.UnionWith(writers);
|
||||||
|
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||||
|
foreach (string k in union) {
|
||||||
|
dict[k] = new Dictionary<string, object> {
|
||||||
|
{ "read", readers.Contains(k) },
|
||||||
|
{ "write", writers.Contains(k) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeRelation(LCRelation<LCObject> relation) {
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ "__type", "Relation" },
|
||||||
|
{ "className", relation.targetClass }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static object EncodeGeoPoint(LCGeoPoint geoPoint) {
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ "__type", "GeoPoint" },
|
||||||
|
{ "latitude", geoPoint.Latitude },
|
||||||
|
{ "longitude", geoPoint.Longitude }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace LeanCloud.Storage.Internal {
|
|
||||||
/// <summary>
|
|
||||||
/// A <see cref="AVEncoder"/> that throws an exception if it attempts to encode
|
|
||||||
/// a <see cref="AVObject"/>
|
|
||||||
/// </summary>
|
|
||||||
public class NoObjectsEncoder : AVEncoder {
|
|
||||||
// This class isn't really a Singleton, but since it has no state, it's more efficient to get
|
|
||||||
// the default instance.
|
|
||||||
private static readonly NoObjectsEncoder instance = new NoObjectsEncoder();
|
|
||||||
public static NoObjectsEncoder Instance {
|
|
||||||
get {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IDictionary<string, object> EncodeAVObject(AVObject value) {
|
|
||||||
throw new ArgumentException("AVObjects not allowed here.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal {
|
||||||
|
public class LCAWSUploader {
|
||||||
|
public LCAWSUploader() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal {
|
||||||
|
public class LCQiniuUploader {
|
||||||
|
public LCQiniuUploader() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using LeanCloud.Common;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal.Http {
|
||||||
|
internal class LCHttpClient {
|
||||||
|
string appId;
|
||||||
|
|
||||||
|
string appKey;
|
||||||
|
|
||||||
|
string server;
|
||||||
|
|
||||||
|
string sdkVersion;
|
||||||
|
|
||||||
|
string apiVersion;
|
||||||
|
|
||||||
|
HttpClient client;
|
||||||
|
|
||||||
|
internal LCHttpClient(string appId, string appKey, string server, string sdkVersion, string apiVersion) {
|
||||||
|
this.appId = appId;
|
||||||
|
this.appKey = appKey;
|
||||||
|
this.server = server;
|
||||||
|
this.sdkVersion = sdkVersion;
|
||||||
|
this.apiVersion = apiVersion;
|
||||||
|
|
||||||
|
client = new HttpClient();
|
||||||
|
ProductHeaderValue product = new ProductHeaderValue("LeanCloud-CSharp-SDK", LeanCloud.SDKVersion);
|
||||||
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(product));
|
||||||
|
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
client.DefaultRequestHeaders.Add("X-LC-Id", appId);
|
||||||
|
// TODO
|
||||||
|
client.DefaultRequestHeaders.Add("X-LC-Key", appKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Task Get() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<Dictionary<string, object>> Post(string path,
|
||||||
|
Dictionary<string, object> headers = null,
|
||||||
|
Dictionary<string, object> data = null,
|
||||||
|
Dictionary<string, object> queryParams = null) {
|
||||||
|
string content = (data != null) ? JsonConvert.SerializeObject(data) : null;
|
||||||
|
HttpRequestMessage request = new HttpRequestMessage {
|
||||||
|
RequestUri = new Uri($"{server}/{apiVersion}/{path}"),
|
||||||
|
Method = HttpMethod.Post,
|
||||||
|
Content = new StringContent(content)
|
||||||
|
};
|
||||||
|
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||||
|
HttpUtils.PrintRequest(client, request, content);
|
||||||
|
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
request.Dispose();
|
||||||
|
|
||||||
|
string resultString = await response.Content.ReadAsStringAsync();
|
||||||
|
response.Dispose();
|
||||||
|
HttpUtils.PrintResponse(response, resultString);
|
||||||
|
|
||||||
|
HttpStatusCode statusCode = response.StatusCode;
|
||||||
|
if (response.IsSuccessStatusCode) {
|
||||||
|
Dictionary<string, object> ret = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString, new LeanCloudJsonConverter());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
int code = (int)statusCode;
|
||||||
|
string message = resultString;
|
||||||
|
try {
|
||||||
|
// 尝试获取 LeanCloud 返回错误信息
|
||||||
|
Dictionary<string, object> error = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString, new LeanCloudJsonConverter());
|
||||||
|
code = (int)error["code"];
|
||||||
|
message = error["error"].ToString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.Error(e.Message);
|
||||||
|
} finally {
|
||||||
|
throw new LCException(code, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Object {
|
||||||
|
public class LCBatch {
|
||||||
|
public LCBatch() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal.Object {
|
||||||
|
internal class LCObjectData {
|
||||||
|
internal string ClassName {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string ObjectId {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DateTime CreatedAt {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DateTime UpdatedAt {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Dictionary<string, object> CustomPropertyDict;
|
||||||
|
|
||||||
|
internal LCObjectData() {
|
||||||
|
CustomPropertyDict = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static LCObjectData Decode(IDictionary dict) {
|
||||||
|
if (dict == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LCObjectData objectData = new LCObjectData();
|
||||||
|
foreach (KeyValuePair<string, object> kv in dict) {
|
||||||
|
string key = kv.Key;
|
||||||
|
object value = kv.Value;
|
||||||
|
if (key == "className") {
|
||||||
|
objectData.ClassName = value.ToString();
|
||||||
|
} else if (key == "objectId") {
|
||||||
|
objectData.ObjectId = value.ToString();
|
||||||
|
} else if (key == "createdAt" && DateTime.TryParse(value.ToString(), out DateTime createdAt)) {
|
||||||
|
objectData.CreatedAt = createdAt;
|
||||||
|
} else if (key == "updatedAt" && DateTime.TryParse(value.ToString(), out DateTime updatedAt)) {
|
||||||
|
objectData.UpdatedAt = updatedAt;
|
||||||
|
} else {
|
||||||
|
objectData.CustomPropertyDict[key] = LCDecoder.Decode(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objectData;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Dictionary<string, object> Encode(LCObjectData objectData) {
|
||||||
|
if (objectData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Dictionary<string, object> dict = new Dictionary<string, object> {
|
||||||
|
{ "className", objectData.ClassName },
|
||||||
|
{ "objectId", objectData.ObjectId },
|
||||||
|
{ "createdAt", objectData.CreatedAt },
|
||||||
|
{ "updatedAt", objectData.UpdatedAt },
|
||||||
|
};
|
||||||
|
if (objectData.CustomPropertyDict != null) {
|
||||||
|
foreach (KeyValuePair<string, object> kv in objectData.CustomPropertyDict) {
|
||||||
|
string key = kv.Key;
|
||||||
|
object value = kv.Value;
|
||||||
|
dict[key] = LCEncoder.Encode(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Object {
|
||||||
|
public class LCSubClassInfo {
|
||||||
|
public LCSubClassInfo() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
|
public class LCAddOperation {
|
||||||
|
public LCAddOperation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
|
public class LCAddRelationOperation {
|
||||||
|
public LCAddRelationOperation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
|
public class LCAddUniqueOperation {
|
||||||
|
public LCAddUniqueOperation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
|
public class LCDecrementOperation {
|
||||||
|
public LCDecrementOperation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
|
public class LCDeleteOperation {
|
||||||
|
public LCDeleteOperation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
|
public class LCIncrementOperation {
|
||||||
|
public LCIncrementOperation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
|
public class LCOperation {
|
||||||
|
public LCOperation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
|
public class LCRemoveOperation {
|
||||||
|
public LCRemoveOperation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal.Operation {
|
||||||
|
public class LCRemoveRelationOperation {
|
||||||
|
public LCRemoveRelationOperation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage.Internal {
|
||||||
|
public class LCSetOperation {
|
||||||
|
public LCSetOperation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal.Query {
|
||||||
|
internal interface ILCQueryCondition {
|
||||||
|
bool Equals(ILCQueryCondition other);
|
||||||
|
Dictionary<string, object> Encode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal.Query {
|
||||||
|
internal class LCCompositionalCondition : ILCQueryCondition {
|
||||||
|
internal const string And = "$and";
|
||||||
|
internal const string Or = "$or";
|
||||||
|
|
||||||
|
readonly string composition;
|
||||||
|
|
||||||
|
List<ILCQueryCondition> conditionList;
|
||||||
|
|
||||||
|
List<string> orderByList;
|
||||||
|
HashSet<string> includes;
|
||||||
|
HashSet<string> selectedKeys;
|
||||||
|
|
||||||
|
internal int Skip {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int Limit {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal LCCompositionalCondition(string composition = And) {
|
||||||
|
this.composition = composition;
|
||||||
|
Skip = 0;
|
||||||
|
Limit = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询条件
|
||||||
|
internal void WhereEqualTo(string key, object value) {
|
||||||
|
Add(new LCEqualCondition(key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereNotEqualTo(string key, object value) {
|
||||||
|
AddOperation(key, "$ne", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereContainedIn(string key, IEnumerable values) {
|
||||||
|
AddOperation(key, "$in", values);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereNotContainedIn(string key, IEnumerable values) {
|
||||||
|
AddOperation(key, "nin", values);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereContainsAll(string key, IEnumerable values) {
|
||||||
|
AddOperation(key, "$all", values);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereExists(string key) {
|
||||||
|
AddOperation(key, "$exists", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereDoesNotExist(string key) {
|
||||||
|
AddOperation(key, "$exists", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereSizeEqualTo(string key, int size) {
|
||||||
|
AddOperation(key, "$size", size);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereGreaterThan(string key, object value) {
|
||||||
|
AddOperation(key, "$gt", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereGreaterThanOrEqualTo(string key, object value) {
|
||||||
|
AddOperation(key, "$gte", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereLessThan(string key, object value) {
|
||||||
|
AddOperation(key, "$lt$lt", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereLessThanOrEqualTo(string key, object value) {
|
||||||
|
AddOperation(key, "$lte", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereNear(string key, LCGeoPoint point) {
|
||||||
|
AddOperation(key, "$nearSphere", point);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereWithinGeoBox(string key, LCGeoPoint southwest, LCGeoPoint northeast) {
|
||||||
|
Dictionary<string, object> value = new Dictionary<string, object> {
|
||||||
|
{ "$box", new List<object> { southwest, northeast } }
|
||||||
|
};
|
||||||
|
AddOperation(key, "$within", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereRelatedTo(LCObject parent, string key) {
|
||||||
|
Add(new LCRelatedCondition(parent, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereStartsWith(string key, string prefix) {
|
||||||
|
AddOperation(key, "$regex", $"^{prefix}.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereEndsWith(string key, string suffix) {
|
||||||
|
AddOperation(key, "$regex", $".*{suffix}$");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WhereContains(string key, string subString) {
|
||||||
|
AddOperation(key, "$regex", $".*{subString}.*");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddOperation(string key, string op, object value) {
|
||||||
|
LCOperationCondition cond = new LCOperationCondition(key, op, value);
|
||||||
|
Add(cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Add(ILCQueryCondition cond) {
|
||||||
|
if (cond == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (conditionList == null) {
|
||||||
|
conditionList = new List<ILCQueryCondition>();
|
||||||
|
}
|
||||||
|
conditionList.RemoveAll(item => item.Equals(cond));
|
||||||
|
conditionList.Add(cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选条件
|
||||||
|
internal void OrderBy(string key) {
|
||||||
|
if (orderByList == null) {
|
||||||
|
orderByList = new List<string>();
|
||||||
|
}
|
||||||
|
orderByList.Add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OrderByDesending(string key) {
|
||||||
|
OrderBy($"-{key}");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Include(string key) {
|
||||||
|
if (includes == null) {
|
||||||
|
includes = new HashSet<string>();
|
||||||
|
}
|
||||||
|
includes.Add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Select(string key) {
|
||||||
|
if (selectedKeys == null) {
|
||||||
|
selectedKeys = new HashSet<string>();
|
||||||
|
}
|
||||||
|
selectedKeys.Add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ILCQueryCondition other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, object> Encode() {
|
||||||
|
if (conditionList == null || conditionList.Count == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (conditionList.Count == 1) {
|
||||||
|
ILCQueryCondition cond = conditionList[0];
|
||||||
|
return cond.Encode();
|
||||||
|
}
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ composition, LCEncoder.Encode(conditionList) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Dictionary<string, object> BuildParams(string className) {
|
||||||
|
Dictionary<string, object> dict = new Dictionary<string, object> {
|
||||||
|
{ "className", className },
|
||||||
|
{ "skip", Skip },
|
||||||
|
{ "limit", Limit }
|
||||||
|
};
|
||||||
|
if (conditionList != null && conditionList.Count > 0) {
|
||||||
|
// TODO json
|
||||||
|
dict["where"] = Encode();
|
||||||
|
}
|
||||||
|
if (orderByList != null && orderByList.Count > 0) {
|
||||||
|
dict["order"] = string.Join(",", orderByList);
|
||||||
|
}
|
||||||
|
if (includes != null && includes.Count > 0) {
|
||||||
|
dict["include"] = string.Join(",", includes);
|
||||||
|
}
|
||||||
|
if (selectedKeys != null && selectedKeys.Count > 0) {
|
||||||
|
dict["keys"] = string.Join(",", selectedKeys);
|
||||||
|
}
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string BuildWhere() {
|
||||||
|
if (conditionList == null || conditionList.Count == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal.Query {
|
||||||
|
internal class LCEqualCondition : ILCQueryCondition {
|
||||||
|
readonly string key;
|
||||||
|
readonly object value;
|
||||||
|
|
||||||
|
internal LCEqualCondition(string key, object value) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ILCQueryCondition other) {
|
||||||
|
if (other is LCEqualCondition cond) {
|
||||||
|
return cond.key == key;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, object> Encode() {
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ key, LCEncoder.Encode(value) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal.Query {
|
||||||
|
internal class LCOperationCondition : ILCQueryCondition {
|
||||||
|
readonly string key;
|
||||||
|
readonly string op;
|
||||||
|
readonly object value;
|
||||||
|
|
||||||
|
internal LCOperationCondition(string key, string op, object value) {
|
||||||
|
this.key = key;
|
||||||
|
this.op = op;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ILCQueryCondition other) {
|
||||||
|
if (other is LCOperationCondition cond) {
|
||||||
|
return cond.key == key && cond.op == op;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, object> Encode() {
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ key, new Dictionary<string, object> {
|
||||||
|
{ op, LCEncoder.Encode(value) }
|
||||||
|
} }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using LeanCloud.Storage.Internal.Codec;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal.Query {
|
||||||
|
internal class LCRelatedCondition : ILCQueryCondition {
|
||||||
|
readonly LCObject parent;
|
||||||
|
readonly string key;
|
||||||
|
|
||||||
|
internal LCRelatedCondition(LCObject parent, string key) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ILCQueryCondition other) {
|
||||||
|
if (other is LCRelatedCondition cond) {
|
||||||
|
return cond.key == key;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, object> Encode() {
|
||||||
|
return new Dictionary<string, object> {
|
||||||
|
{ "$relatedTo", new Dictionary<string, object> {
|
||||||
|
{ "object", LCEncoder.Encode(parent) },
|
||||||
|
{ "key", key }
|
||||||
|
} }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage.Internal {
|
||||||
|
internal class ObjectData : IEnumerable<KeyValuePair<string, object>> {
|
||||||
|
internal string ClassName {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string ObjectId {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal AVACL ACL {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DateTime? CreatedAt {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal DateTime? UpdatedAt {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Dictionary<string, object> CustomProperties {
|
||||||
|
get; set;
|
||||||
|
} = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
internal object this[string key] {
|
||||||
|
get {
|
||||||
|
return CustomProperties[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool ContainsKey(string key) {
|
||||||
|
return CustomProperties.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Apply(Dictionary<string, IAVFieldOperation> operations) {
|
||||||
|
foreach (KeyValuePair<string, IAVFieldOperation> entry in operations) {
|
||||||
|
string propKey = entry.Key;
|
||||||
|
object propVal = entry.Value;
|
||||||
|
if (!CustomProperties.TryGetValue(propKey, out object oldVal)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
object newVal = entry.Value.Apply(oldVal, propKey);
|
||||||
|
if (newVal == AVDeleteOperation.DeleteToken) {
|
||||||
|
CustomProperties.Remove(propKey);
|
||||||
|
} else {
|
||||||
|
CustomProperties[propKey] = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IEnumerable
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
|
||||||
|
return ((IEnumerable<KeyValuePair<string, object>>)CustomProperties).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() {
|
||||||
|
return ((IEnumerable<KeyValuePair<string, object>>)CustomProperties).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ namespace LeanCloud.Storage.Internal {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CollectionsEqual<T>(this IEnumerable<T> a, IEnumerable<T> b) {
|
public static bool CollectionsEqual<T>(this IEnumerable<T> a, IEnumerable<T> b) {
|
||||||
return Object.Equals(a, b) ||
|
return Equals(a, b) ||
|
||||||
(a != null && b != null &&
|
(a != null && b != null &&
|
||||||
a.SequenceEqual(b));
|
a.SequenceEqual(b));
|
||||||
}
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
/// <summary>
|
||||||
|
/// 访问控制类
|
||||||
|
/// </summary>
|
||||||
|
public class LCACL {
|
||||||
|
const string PublicKey = "*";
|
||||||
|
|
||||||
|
const string RoleKeyPrefix = "role:";
|
||||||
|
|
||||||
|
internal HashSet<string> readers;
|
||||||
|
internal HashSet<string> writers;
|
||||||
|
|
||||||
|
public bool PublicReadAccess {
|
||||||
|
get {
|
||||||
|
return GetAccess(readers, PublicKey);
|
||||||
|
} set {
|
||||||
|
SetAccess(readers, PublicKey, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PublicWriteAccess {
|
||||||
|
get {
|
||||||
|
return GetAccess(writers, PublicKey);
|
||||||
|
} set {
|
||||||
|
SetAccess(writers, PublicKey, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetUserIdReadAccess(string userId) {
|
||||||
|
if (string.IsNullOrEmpty(userId)) {
|
||||||
|
throw new ArgumentNullException(nameof(userId));
|
||||||
|
}
|
||||||
|
return GetAccess(readers, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUserIdReadAccess(string userId, bool value) {
|
||||||
|
if (string.IsNullOrEmpty(userId)) {
|
||||||
|
throw new ArgumentNullException(nameof(userId));
|
||||||
|
}
|
||||||
|
SetAccess(readers, userId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetUserIdWriteAccess(string userId) {
|
||||||
|
if (string.IsNullOrEmpty(userId)) {
|
||||||
|
throw new ArgumentNullException(nameof(userId));
|
||||||
|
}
|
||||||
|
return GetAccess(writers, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUserIdWriteAccess(string userId, bool value) {
|
||||||
|
if (string.IsNullOrEmpty(userId)) {
|
||||||
|
throw new ArgumentNullException(nameof(userId));
|
||||||
|
}
|
||||||
|
SetAccess(writers, userId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetUserReadAccess(LCUser user) {
|
||||||
|
if (user == null) {
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
return GetUserIdReadAccess(user.ObjectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUserReadAccess(LCUser user, bool value) {
|
||||||
|
if (user == null) {
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
SetUserIdReadAccess(user.ObjectId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetUserWriteAccess(LCUser user) {
|
||||||
|
if (user == null) {
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
return GetUserIdWriteAccess(user.ObjectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUserWriteAccess(LCUser user, bool value) {
|
||||||
|
if (user == null) {
|
||||||
|
throw new ArgumentNullException(nameof(user));
|
||||||
|
}
|
||||||
|
SetUserIdWriteAccess(user.ObjectId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetRoleReadAccess(LCRole role) {
|
||||||
|
if (role == null) {
|
||||||
|
throw new ArgumentNullException(nameof(role));
|
||||||
|
}
|
||||||
|
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||||
|
return GetAccess(readers, roleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRoleReadAccess(LCRole role, bool value) {
|
||||||
|
if (role == null) {
|
||||||
|
throw new ArgumentNullException(nameof(role));
|
||||||
|
}
|
||||||
|
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||||
|
SetAccess(readers, roleKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetRoleWriteAccess(LCRole role) {
|
||||||
|
if (role == null) {
|
||||||
|
throw new ArgumentNullException(nameof(role));
|
||||||
|
}
|
||||||
|
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||||
|
return GetAccess(writers, roleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetRoleWriteAccess(LCRole role, bool value) {
|
||||||
|
if (role == null) {
|
||||||
|
throw new ArgumentNullException(nameof(role));
|
||||||
|
}
|
||||||
|
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||||
|
SetAccess(writers, roleKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCACL() {
|
||||||
|
readers = new HashSet<string>();
|
||||||
|
writers = new HashSet<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetAccess(HashSet<string> set, string key) {
|
||||||
|
return set.Contains(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAccess(HashSet<string> set, string key, bool value) {
|
||||||
|
if (value) {
|
||||||
|
set.Add(key);
|
||||||
|
} else {
|
||||||
|
set.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
/// <summary>
|
||||||
|
/// 云引擎
|
||||||
|
/// </summary>
|
||||||
|
public static class LCCloud {
|
||||||
|
/// <summary>
|
||||||
|
/// 调用云函数,结果为 Dictionary 类型
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name"></param>
|
||||||
|
/// <param name="parameters"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static async Task<Dictionary<string, object>> Run(string name, Dictionary<string, object> parameters = null) {
|
||||||
|
string path = $"functions/{name}";
|
||||||
|
Dictionary<string, object> response = await LeanCloud.HttpClient.Post(path, data: parameters);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<object> RPC(string name, Dictionary<string, object> parameters = null) {
|
||||||
|
string path = $"call/{name}";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
public class LCException : Exception {
|
||||||
|
public int Code {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Message {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCException(int code, string message) {
|
||||||
|
Code = code;
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
public class LCFile : LCObject {
|
||||||
|
public LCFile() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
public class LCGeoPoint {
|
||||||
|
/// <summary>
|
||||||
|
/// 纬度
|
||||||
|
/// </summary>
|
||||||
|
public double Latitude {
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 经度
|
||||||
|
/// </summary>
|
||||||
|
public double Longitude {
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCGeoPoint(double latitude, double longtitude) {
|
||||||
|
Latitude = latitude;
|
||||||
|
Longitude = longtitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LCGeoPoint Origin {
|
||||||
|
get {
|
||||||
|
return new LCGeoPoint(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 据某点的距离(单位:千米)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="point"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public double KilometersTo(LCGeoPoint point) {
|
||||||
|
if (point == null) {
|
||||||
|
throw new ArgumentNullException(nameof(point));
|
||||||
|
}
|
||||||
|
return RadiansTo(point) * 6371.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 据某点的距离(单位:英里)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="point"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public double MilesTo(LCGeoPoint point) {
|
||||||
|
if (point == null) {
|
||||||
|
throw new ArgumentNullException(nameof(point));
|
||||||
|
}
|
||||||
|
return RadiansTo(point) * 3958.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 据某点的距离(单位:弧度)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="point"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public double RadiansTo(LCGeoPoint point) {
|
||||||
|
if (point == null) {
|
||||||
|
throw new ArgumentNullException(nameof(point));
|
||||||
|
}
|
||||||
|
double d2r = Math.PI / 180.0;
|
||||||
|
double lat1rad = Latitude * d2r;
|
||||||
|
double long1rad = Longitude * d2r;
|
||||||
|
double lat2rad = point.Latitude * d2r;
|
||||||
|
double long2rad = point.Longitude * d2r;
|
||||||
|
double deltaLat = lat1rad - lat2rad;
|
||||||
|
double deltaLong = long1rad - long2rad;
|
||||||
|
double sinDeltaLatDiv2 = Math.Sin(deltaLat / 2);
|
||||||
|
double sinDeltaLongDiv2 = Math.Sin(deltaLong / 2);
|
||||||
|
double a = sinDeltaLatDiv2 * sinDeltaLatDiv2 +
|
||||||
|
Math.Cos(lat1rad) * Math.Cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2;
|
||||||
|
a = Math.Min(1.0, a);
|
||||||
|
return 2 * Math.Cos(Math.Sqrt(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using LeanCloud.Storage.Internal.Object;
|
||||||
|
using LeanCloud.Storage.Internal.Operation;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
/// <summary>
|
||||||
|
/// 对象类
|
||||||
|
/// </summary>
|
||||||
|
public class LCObject {
|
||||||
|
/// <summary>
|
||||||
|
/// 最近一次与服务端同步的数据
|
||||||
|
/// </summary>
|
||||||
|
LCObjectData data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预算数据
|
||||||
|
/// </summary>
|
||||||
|
Dictionary<string, object> estimatedData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作字典
|
||||||
|
/// </summary>
|
||||||
|
Dictionary<string, LCOperation> operationDict;
|
||||||
|
|
||||||
|
public string ClassName {
|
||||||
|
get {
|
||||||
|
return data.ClassName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ObjectId {
|
||||||
|
get {
|
||||||
|
return data.ObjectId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCObject() {
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static LCObject Create(string className) {
|
||||||
|
// TODO
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
public class LCQuery {
|
||||||
|
public LCQuery() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
public class LCRelation<T> where T : LCObject {
|
||||||
|
public string Key {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCObject Parent {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string targetClass {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LCRelation() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
public class LCRole : LCObject {
|
||||||
|
public LCRole() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
public class LCUser : LCObject {
|
||||||
|
public LCUser() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
namespace LeanCloud.Storage {
|
||||||
|
public class LCUserAuthDataLoginOption {
|
||||||
|
public LCUserAuthDataLoginOption() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
using LeanCloud.Storage.Internal.Http;
|
||||||
|
|
||||||
|
namespace LeanCloud {
|
||||||
|
/// <summary>
|
||||||
|
/// LeanCloud 全局接口
|
||||||
|
/// </summary>
|
||||||
|
public class LeanCloud {
|
||||||
|
// SDK 版本号,用于 User-Agent 统计
|
||||||
|
internal const string SDKVersion = "0.1.0";
|
||||||
|
|
||||||
|
// 接口版本号,用于接口版本管理
|
||||||
|
internal const string APIVersion = "1.1";
|
||||||
|
|
||||||
|
public static bool UseProduction {
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static LCHttpClient HttpClient {
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Initialize(string appId, string appKey, string server = null) {
|
||||||
|
if (string.IsNullOrEmpty(appId)) {
|
||||||
|
throw new ArgumentException(nameof(appId));
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(appKey)) {
|
||||||
|
throw new ArgumentException(nameof(appKey));
|
||||||
|
}
|
||||||
|
// TODO 注册 LeanCloud 内部子类化类型
|
||||||
|
|
||||||
|
HttpClient = new LCHttpClient(appId, appKey, server, SDKVersion, APIVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,6 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
namespace LeanCloud {
|
namespace LeanCloud {
|
||||||
public class AVObject : IEnumerable<KeyValuePair<string, object>> {
|
public class AVObject : IEnumerable<KeyValuePair<string, object>> {
|
||||||
|
@ -64,15 +63,15 @@ namespace LeanCloud {
|
||||||
|
|
||||||
public ICollection<string> Keys {
|
public ICollection<string> Keys {
|
||||||
get {
|
get {
|
||||||
return estimatedData.Keys.Union(serverData.Keys).ToArray();
|
return estimatedData.Keys;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly string AutoClassName = "_Automatic";
|
private static readonly string AutoClassName = "_Automatic";
|
||||||
|
|
||||||
internal readonly ConcurrentDictionary<string, IAVFieldOperation> operationDict = new ConcurrentDictionary<string, IAVFieldOperation>();
|
internal readonly Dictionary<string, IAVFieldOperation> operationDict = new Dictionary<string, IAVFieldOperation>();
|
||||||
private readonly ConcurrentDictionary<string, object> serverData = new ConcurrentDictionary<string, object>();
|
private Dictionary<string, object> serverData = new Dictionary<string, object>();
|
||||||
private readonly ConcurrentDictionary<string, object> estimatedData = new ConcurrentDictionary<string, object>();
|
private Dictionary<string, object> estimatedData = new Dictionary<string, object>();
|
||||||
|
|
||||||
private bool dirty;
|
private bool dirty;
|
||||||
|
|
||||||
|
@ -232,6 +231,9 @@ namespace LeanCloud {
|
||||||
// newServerData[pair.Key] = value;
|
// newServerData[pair.Key] = value;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
this.serverData = serverData.Concat(this.serverData.Where(d => !serverData.ContainsKey(d.Key)))
|
||||||
|
.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
IsDirty = false;
|
IsDirty = false;
|
||||||
serverState = serverState.MutatedClone(mutableClone => {
|
serverState = serverState.MutatedClone(mutableClone => {
|
||||||
mutableClone.ServerData = newServerData;
|
mutableClone.ServerData = newServerData;
|
||||||
|
@ -248,7 +250,7 @@ namespace LeanCloud {
|
||||||
|
|
||||||
operationDict.Clear();
|
operationDict.Clear();
|
||||||
foreach (KeyValuePair<string, IAVFieldOperation> entry in other.operationDict) {
|
foreach (KeyValuePair<string, IAVFieldOperation> entry in other.operationDict) {
|
||||||
operationDict.AddOrUpdate(entry.Key, entry.Value, (key, value) => value);
|
operationDict.Add(entry.Key, entry.Value);
|
||||||
}
|
}
|
||||||
state = other.State;
|
state = other.State;
|
||||||
|
|
||||||
|
@ -293,7 +295,26 @@ namespace LeanCloud {
|
||||||
as IDictionary<string, object>;
|
as IDictionary<string, object>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Save Object()
|
internal virtual async Task<AVObject> FetchAsyncInternal(IDictionary<string, object> queryString, CancellationToken cancellationToken = default) {
|
||||||
|
if (ObjectId == null) {
|
||||||
|
throw new InvalidOperationException("Cannot refresh an object that hasn't been saved to the server.");
|
||||||
|
}
|
||||||
|
if (queryString == null) {
|
||||||
|
queryString = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var command = new AVCommand {
|
||||||
|
Path = $"classes/{Uri.EscapeDataString(state.ClassName)}/{Uri.EscapeDataString(state.ObjectId)}?{AVClient.BuildQueryString(queryString)}",
|
||||||
|
Method = HttpMethod.Get
|
||||||
|
};
|
||||||
|
var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
|
||||||
|
IObjectState objectState = AVObjectCoder.Instance.Decode(data.Item2, AVDecoder.Instance);
|
||||||
|
|
||||||
|
HandleFetchResult(objectState);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Save
|
||||||
|
|
||||||
public virtual async Task SaveAsync(bool fetchWhenSave = false, AVQuery<AVObject> query = null, CancellationToken cancellationToken = default) {
|
public virtual async Task SaveAsync(bool fetchWhenSave = false, AVQuery<AVObject> query = null, CancellationToken cancellationToken = default) {
|
||||||
if (HasCircleReference(this, new HashSet<AVObject>())) {
|
if (HasCircleReference(this, new HashSet<AVObject>())) {
|
||||||
|
@ -371,38 +392,8 @@ namespace LeanCloud {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal virtual async Task<AVObject> FetchAsyncInternal(IDictionary<string, object> queryString, CancellationToken cancellationToken = default) {
|
|
||||||
if (ObjectId == null) {
|
|
||||||
throw new InvalidOperationException("Cannot refresh an object that hasn't been saved to the server.");
|
|
||||||
}
|
|
||||||
if (queryString == null) {
|
|
||||||
queryString = new Dictionary<string, object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var command = new AVCommand {
|
|
||||||
Path = $"classes/{Uri.EscapeDataString(state.ClassName)}/{Uri.EscapeDataString(state.ObjectId)}?{AVClient.BuildQueryString(queryString)}",
|
|
||||||
Method = HttpMethod.Get
|
|
||||||
};
|
|
||||||
var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
|
|
||||||
IObjectState objectState = AVObjectCoder.Instance.Decode(data.Item2, AVDecoder.Instance);
|
|
||||||
|
|
||||||
HandleFetchResult(objectState);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Fetch Object(s)
|
|
||||||
|
|
||||||
public static Task<IEnumerable<T>> FetchAllIfNeededAsync<T>(
|
|
||||||
IEnumerable<T> objects, CancellationToken cancellationToken = default) where T : AVObject {
|
|
||||||
return FetchAllInternalAsync(objects, false, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task<IEnumerable<T>> FetchAllAsync<T>(IEnumerable<T> objects, CancellationToken cancellationToken = default) where T : AVObject {
|
|
||||||
return FetchAllInternalAsync(objects, true, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Task<IEnumerable<T>> FetchAllInternalAsync<T>(
|
private static Task<IEnumerable<T>> FetchAllInternalAsync<T>(
|
||||||
IEnumerable<T> objects, bool force, CancellationToken cancellationToken) where T : AVObject {
|
IEnumerable<T> objects, bool force, CancellationToken cancellationToken) where T : AVObject {
|
||||||
|
|
||||||
|
@ -441,9 +432,20 @@ namespace LeanCloud {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Fetch
|
||||||
|
|
||||||
|
public static Task<IEnumerable<T>> FetchAllIfNeededAsync<T>(
|
||||||
|
IEnumerable<T> objects, CancellationToken cancellationToken = default) where T : AVObject {
|
||||||
|
return FetchAllInternalAsync(objects, false, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<IEnumerable<T>> FetchAllAsync<T>(IEnumerable<T> objects, CancellationToken cancellationToken = default) where T : AVObject {
|
||||||
|
return FetchAllInternalAsync(objects, true, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Delete Object
|
#region Delete
|
||||||
|
|
||||||
public virtual async Task DeleteAsync(AVQuery<AVObject> query = null, CancellationToken cancellationToken = default) {
|
public virtual async Task DeleteAsync(AVQuery<AVObject> query = null, CancellationToken cancellationToken = default) {
|
||||||
if (ObjectId == null) {
|
if (ObjectId == null) {
|
||||||
|
@ -490,48 +492,6 @@ namespace LeanCloud {
|
||||||
PerformOperation(key, AVDeleteOperation.Instance);
|
PerformOperation(key, AVDeleteOperation.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private IEnumerable<string> ApplyOperations(IDictionary<string, IAVFieldOperation> operations, IDictionary<string, object> map) {
|
|
||||||
List<string> appliedKeys = new List<string>();
|
|
||||||
foreach (var pair in operations) {
|
|
||||||
map.TryGetValue(pair.Key, out object oldValue);
|
|
||||||
var newValue = pair.Value.Apply(oldValue, pair.Key);
|
|
||||||
if (newValue != AVDeleteOperation.DeleteToken) {
|
|
||||||
map[pair.Key] = newValue;
|
|
||||||
} else {
|
|
||||||
map.Remove(pair.Key);
|
|
||||||
}
|
|
||||||
appliedKeys.Add(pair.Key);
|
|
||||||
}
|
|
||||||
return appliedKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void RebuildEstimatedData() {
|
|
||||||
estimatedData.Clear();
|
|
||||||
ApplyOperations(operationDict, estimatedData);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void PerformOperation(string key, IAVFieldOperation operation) {
|
|
||||||
estimatedData.TryGetValue(key, out object oldValue);
|
|
||||||
object newValue = operation.Apply(oldValue, key);
|
|
||||||
if (newValue != AVDeleteOperation.DeleteToken) {
|
|
||||||
estimatedData[key] = newValue;
|
|
||||||
} else {
|
|
||||||
estimatedData.TryRemove(key, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operationDict.TryGetValue(key, out IAVFieldOperation oldOperation)) {
|
|
||||||
operation = operation.MergeWithPrevious(oldOperation);
|
|
||||||
}
|
|
||||||
operationDict[key] = operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal virtual void OnSettingValue(ref string key, ref object value) {
|
|
||||||
if (key == null) {
|
|
||||||
throw new ArgumentNullException(nameof(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual public object this[string key] {
|
virtual public object this[string key] {
|
||||||
get {
|
get {
|
||||||
CheckKeyValid(key);
|
CheckKeyValid(key);
|
||||||
|
@ -553,11 +513,6 @@ namespace LeanCloud {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Set(string key, object value) {
|
|
||||||
OnSettingValue(ref key, ref value);
|
|
||||||
PerformOperation(key, new AVSetOperation(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Atomic Increment
|
#region Atomic Increment
|
||||||
|
|
||||||
public void Increment(string key) {
|
public void Increment(string key) {
|
||||||
|
@ -576,6 +531,8 @@ namespace LeanCloud {
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region List
|
||||||
|
|
||||||
public void AddToList(string key, object value) {
|
public void AddToList(string key, object value) {
|
||||||
CheckKeyValid(key);
|
CheckKeyValid(key);
|
||||||
AddRangeToList(key, new[] { value });
|
AddRangeToList(key, new[] { value });
|
||||||
|
@ -601,6 +558,58 @@ namespace LeanCloud {
|
||||||
PerformOperation(key, new AVRemoveOperation(values.Cast<object>()));
|
PerformOperation(key, new AVRemoveOperation(values.Cast<object>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
private IEnumerable<string> ApplyOperations(IDictionary<string, IAVFieldOperation> operations, IDictionary<string, object> map) {
|
||||||
|
List<string> appliedKeys = new List<string>();
|
||||||
|
foreach (var pair in operations) {
|
||||||
|
map.TryGetValue(pair.Key, out object oldValue);
|
||||||
|
var newValue = pair.Value.Apply(oldValue, pair.Key);
|
||||||
|
if (newValue != AVDeleteOperation.DeleteToken) {
|
||||||
|
map[pair.Key] = newValue;
|
||||||
|
} else {
|
||||||
|
map.Remove(pair.Key);
|
||||||
|
}
|
||||||
|
appliedKeys.Add(pair.Key);
|
||||||
|
}
|
||||||
|
return appliedKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RebuildEstimatedData() {
|
||||||
|
estimatedData.Clear();
|
||||||
|
foreach (KeyValuePair<string, object> entry in serverData) {
|
||||||
|
estimatedData.Add(entry.Key, entry.Value);
|
||||||
|
}
|
||||||
|
ApplyOperations(operationDict, estimatedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void PerformOperation(string key, IAVFieldOperation operation) {
|
||||||
|
estimatedData.TryGetValue(key, out object oldValue);
|
||||||
|
object newValue = operation.Apply(oldValue, key);
|
||||||
|
if (newValue != AVDeleteOperation.DeleteToken) {
|
||||||
|
estimatedData[key] = newValue;
|
||||||
|
} else {
|
||||||
|
estimatedData.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operationDict.TryGetValue(key, out IAVFieldOperation oldOperation)) {
|
||||||
|
operation = operation.MergeWithPrevious(oldOperation);
|
||||||
|
}
|
||||||
|
operationDict[key] = operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal virtual void OnSettingValue(ref string key, ref object value) {
|
||||||
|
if (key == null) {
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Set(string key, object value) {
|
||||||
|
OnSettingValue(ref key, ref value);
|
||||||
|
PerformOperation(key, new AVSetOperation(value));
|
||||||
|
}
|
||||||
|
|
||||||
void CheckKeyValid(string key) {
|
void CheckKeyValid(string key) {
|
||||||
if (string.IsNullOrEmpty(key)) {
|
if (string.IsNullOrEmpty(key)) {
|
||||||
throw new ArgumentNullException(nameof(key));
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
@ -613,6 +622,15 @@ namespace LeanCloud {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void Add(string key, object value) {
|
||||||
|
if (ContainsKey(key)) {
|
||||||
|
throw new ArgumentException("Key already exists", key);
|
||||||
|
}
|
||||||
|
this[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
public bool ContainsKey(string key) {
|
public bool ContainsKey(string key) {
|
||||||
return estimatedData.ContainsKey(key) || state.ContainsKey(key);
|
return estimatedData.ContainsKey(key) || state.ContainsKey(key);
|
||||||
}
|
}
|
||||||
|
@ -675,13 +693,6 @@ namespace LeanCloud {
|
||||||
return dirty || operationDict.Count > 0;
|
return dirty || operationDict.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(string key, object value) {
|
|
||||||
if (ContainsKey(key)) {
|
|
||||||
throw new ArgumentException("Key already exists", key);
|
|
||||||
}
|
|
||||||
this[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator() {
|
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator() {
|
||||||
return estimatedData.GetEnumerator();
|
return estimatedData.GetEnumerator();
|
||||||
}
|
}
|
||||||
|
@ -695,7 +706,6 @@ namespace LeanCloud {
|
||||||
return new AVQuery<T>(className);
|
return new AVQuery<T>(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region refactor
|
|
||||||
|
|
||||||
static bool HasCircleReference(object obj, HashSet<AVObject> parents) {
|
static bool HasCircleReference(object obj, HashSet<AVObject> parents) {
|
||||||
if (parents.Contains(obj)) {
|
if (parents.Contains(obj)) {
|
||||||
|
@ -760,8 +770,6 @@ namespace LeanCloud {
|
||||||
return batches;
|
return batches;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存 AVObject 时用到的辅助批次工具类
|
/// 保存 AVObject 时用到的辅助批次工具类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||||
|
<RootNamespace>LeanCloud.Storage</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -14,4 +15,13 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Common\Common.csproj" />
|
<ProjectReference Include="..\..\Common\Common.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Internal\" />
|
||||||
|
<Folder Include="Internal\Codec\" />
|
||||||
|
<Folder Include="Internal\File\" />
|
||||||
|
<Folder Include="Internal\Http\" />
|
||||||
|
<Folder Include="Internal\Object\" />
|
||||||
|
<Folder Include="Internal\Operation\" />
|
||||||
|
<Folder Include="Internal\Query\" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using LeanCloud.Common;
|
using LeanCloud;
|
||||||
|
|
||||||
namespace Common.Test {
|
namespace Common.Test {
|
||||||
public class AppRouterTest {
|
public class AppRouterTest {
|
||||||
|
|
Loading…
Reference in New Issue