chore: 框架
parent
148c4d6ae2
commit
e4381667c8
|
@ -73,7 +73,7 @@ namespace LeanCloud.Common {
|
|||
response = await client.SendAsync(request);
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
HttpUtils.PrintResponse(response, content);
|
||||
|
||||
|
||||
AppRouter state = JsonConvert.DeserializeObject<AppRouter>(content);
|
||||
state.Source = "router";
|
||||
|
||||
|
|
|
@ -10,10 +10,10 @@ namespace LeanCloud.Test {
|
|||
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
Utils.InitNorthChina();
|
||||
//Utils.InitNorthChina();
|
||||
//Utils.InitEastChina();
|
||||
//Utils.InitOldEastChina();
|
||||
//Utils.InitUS();
|
||||
Utils.InitUS();
|
||||
}
|
||||
|
||||
[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) {
|
||||
return Object.Equals(a, b) ||
|
||||
return Equals(a, b) ||
|
||||
(a != null && b != null &&
|
||||
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.Tasks;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace LeanCloud {
|
||||
public class AVObject : IEnumerable<KeyValuePair<string, object>> {
|
||||
|
@ -64,15 +63,15 @@ namespace LeanCloud {
|
|||
|
||||
public ICollection<string> Keys {
|
||||
get {
|
||||
return estimatedData.Keys.Union(serverData.Keys).ToArray();
|
||||
return estimatedData.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string AutoClassName = "_Automatic";
|
||||
|
||||
internal readonly ConcurrentDictionary<string, IAVFieldOperation> operationDict = new ConcurrentDictionary<string, IAVFieldOperation>();
|
||||
private readonly ConcurrentDictionary<string, object> serverData = new ConcurrentDictionary<string, object>();
|
||||
private readonly ConcurrentDictionary<string, object> estimatedData = new ConcurrentDictionary<string, object>();
|
||||
internal readonly Dictionary<string, IAVFieldOperation> operationDict = new Dictionary<string, IAVFieldOperation>();
|
||||
private Dictionary<string, object> serverData = new Dictionary<string, object>();
|
||||
private Dictionary<string, object> estimatedData = new Dictionary<string, object>();
|
||||
|
||||
private bool dirty;
|
||||
|
||||
|
@ -232,6 +231,9 @@ namespace LeanCloud {
|
|||
// 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;
|
||||
serverState = serverState.MutatedClone(mutableClone => {
|
||||
mutableClone.ServerData = newServerData;
|
||||
|
@ -248,7 +250,7 @@ namespace LeanCloud {
|
|||
|
||||
operationDict.Clear();
|
||||
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;
|
||||
|
||||
|
@ -293,7 +295,26 @@ namespace LeanCloud {
|
|||
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) {
|
||||
if (HasCircleReference(this, new HashSet<AVObject>())) {
|
||||
|
@ -371,45 +392,15 @@ 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
|
||||
|
||||
#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>(
|
||||
IEnumerable<T> objects, bool force, CancellationToken cancellationToken) where T : AVObject {
|
||||
|
||||
if (objects.Any(obj => obj.state.ObjectId == null)) {
|
||||
throw new InvalidOperationException("You cannot fetch objects that haven't already been saved.");
|
||||
}
|
||||
|
||||
|
||||
// Do one Find for each class.
|
||||
var findsByClass =
|
||||
(from obj in objects
|
||||
|
@ -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
|
||||
|
||||
#region Delete Object
|
||||
#region Delete
|
||||
|
||||
public virtual async Task DeleteAsync(AVQuery<AVObject> query = null, CancellationToken cancellationToken = default) {
|
||||
if (ObjectId == null) {
|
||||
|
@ -490,48 +492,6 @@ namespace LeanCloud {
|
|||
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] {
|
||||
get {
|
||||
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
|
||||
|
||||
public void Increment(string key) {
|
||||
|
@ -576,6 +531,8 @@ namespace LeanCloud {
|
|||
|
||||
#endregion
|
||||
|
||||
#region List
|
||||
|
||||
public void AddToList(string key, object value) {
|
||||
CheckKeyValid(key);
|
||||
AddRangeToList(key, new[] { value });
|
||||
|
@ -601,6 +558,58 @@ namespace LeanCloud {
|
|||
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) {
|
||||
if (string.IsNullOrEmpty(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) {
|
||||
return estimatedData.ContainsKey(key) || state.ContainsKey(key);
|
||||
}
|
||||
|
@ -675,13 +693,6 @@ namespace LeanCloud {
|
|||
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() {
|
||||
return estimatedData.GetEnumerator();
|
||||
}
|
||||
|
@ -695,7 +706,6 @@ namespace LeanCloud {
|
|||
return new AVQuery<T>(className);
|
||||
}
|
||||
|
||||
#region refactor
|
||||
|
||||
static bool HasCircleReference(object obj, HashSet<AVObject> parents) {
|
||||
if (parents.Contains(obj)) {
|
||||
|
@ -760,8 +770,6 @@ namespace LeanCloud {
|
|||
return batches;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 保存 AVObject 时用到的辅助批次工具类
|
||||
/// </summary>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
<RootNamespace>LeanCloud.Storage</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -14,4 +15,13 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Common\Common.csproj" />
|
||||
</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>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using LeanCloud.Common;
|
||||
using LeanCloud;
|
||||
|
||||
namespace Common.Test {
|
||||
public class AppRouterTest {
|
||||
|
|
Loading…
Reference in New Issue