chore: 框架

oneRain 2020-02-19 18:50:51 +08:00
parent 148c4d6ae2
commit e4381667c8
95 changed files with 1346 additions and 124 deletions

View File

@ -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)]

View File

@ -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");
}
}
}

View File

@ -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;
}
}
}

View File

@ -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 }
};
}
}
}

View File

@ -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.");
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal {
public class LCAWSUploader {
public LCAWSUploader() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal {
public class LCQiniuUploader {
public LCQiniuUploader() {
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Object {
public class LCBatch {
public LCBatch() {
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Object {
public class LCSubClassInfo {
public LCSubClassInfo() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Operation {
public class LCAddOperation {
public LCAddOperation() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Operation {
public class LCAddRelationOperation {
public LCAddRelationOperation() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Operation {
public class LCAddUniqueOperation {
public LCAddUniqueOperation() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Operation {
public class LCDecrementOperation {
public LCDecrementOperation() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Operation {
public class LCDeleteOperation {
public LCDeleteOperation() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Operation {
public class LCIncrementOperation {
public LCIncrementOperation() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Operation {
public class LCOperation {
public LCOperation() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Operation {
public class LCRemoveOperation {
public LCRemoveOperation() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal.Operation {
public class LCRemoveRelationOperation {
public LCRemoveRelationOperation() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage.Internal {
public class LCSetOperation {
public LCSetOperation() {
}
}
}

View File

@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace LeanCloud.Storage.Internal.Query {
internal interface ILCQueryCondition {
bool Equals(ILCQueryCondition other);
Dictionary<string, object> Encode();
}
}

View File

@ -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;
}
}
}

View File

@ -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) }
};
}
}
}

View File

@ -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) }
} }
};
}
}
}

View File

@ -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 }
} }
};
}
}
}

View File

@ -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
}
}

View File

@ -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));
}

137
Storage/Storage/LCACL.cs Normal file
View File

@ -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);
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage {
public class LCFile : LCObject {
public LCFile() {
}
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage {
public class LCQuery {
public LCQuery() {
}
}
}

View File

@ -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() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage {
public class LCRole : LCObject {
public LCRole() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage {
public class LCUser : LCObject {
public LCUser() {
}
}
}

View File

@ -0,0 +1,7 @@
using System;
namespace LeanCloud.Storage {
public class LCUserAuthDataLoginOption {
public LCUserAuthDataLoginOption() {
}
}
}

View File

@ -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);
}
}
}

View File

@ -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,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
#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 {
@ -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>

View File

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

View File

@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
using NUnit.Framework;
using LeanCloud.Common;
using LeanCloud;
namespace Common.Test {
public class AppRouterTest {