diff --git a/Storage/Storage.Test/ObjectControllerTests.cs b/Storage/Storage.Test/ObjectControllerTests.cs index 1e5b193..d8042bf 100644 --- a/Storage/Storage.Test/ObjectControllerTests.cs +++ b/Storage/Storage.Test/ObjectControllerTests.cs @@ -13,11 +13,9 @@ namespace LeanCloudTests { [Test] public async Task Save() { - TestContext.Out.WriteLine($"before at {Thread.CurrentThread.ManagedThreadId}"); AVObject obj = AVObject.Create("Foo"); obj["content"] = "hello, world"; await obj.SaveAsync(); - TestContext.Out.WriteLine($"{obj.ObjectId} saved at {Thread.CurrentThread.ManagedThreadId}"); Assert.NotNull(obj.ObjectId); Assert.NotNull(obj.CreatedAt); Assert.NotNull(obj.UpdatedAt); @@ -26,6 +24,8 @@ namespace LeanCloudTests { [Test] public async Task SaveWithOptions() { AVObject account = AVObject.CreateWithoutData("Account", "5d65fa5330863b008065e476"); + account["balance"] = 100; + await account.SaveAsync(); AVQuery query = new AVQuery("Account"); query.WhereGreaterThan("balance", 80); account["balance"] = 50; @@ -33,6 +33,20 @@ namespace LeanCloudTests { TestContext.Out.WriteLine($"balance: {account["balance"]}"); } + [Test] + public async Task SaveBatch() { + List objList = new List(); + for (int i = 0; i < 5; i++) { + AVObject obj = AVObject.Create("Foo"); + obj["content"] = "batch object"; + objList.Add(obj); + } + await objList.SaveAllAsync(); + objList.ForEach(obj => { + Assert.NotNull(obj.ObjectId); + }); + } + [Test] public async Task Fetch() { AVObject obj = AVObject.CreateWithoutData("Todo", "5d5f6039d5de2b006cf29c8f"); @@ -56,5 +70,40 @@ namespace LeanCloudTests { AVObject tag = obj["tag"] as AVObject; TestContext.Out.WriteLine($"{tag["name"]}"); } + + [Test] + public async Task FetchAll() { + List objList = new List { + AVObject.CreateWithoutData("Tag", "5d64e5ebc05a8000730340ba"), + AVObject.CreateWithoutData("Tag", "5d64e5eb12215f0073db271c"), + AVObject.CreateWithoutData("Tag", "5d64e57f43e78c0068a14315") + }; + await objList.FetchAllAsync(); + objList.ForEach(obj => { + Assert.NotNull(obj.ObjectId); + TestContext.Out.WriteLine($"{obj.ObjectId}, {obj["name"]}"); + }); + } + + [Test] + public async Task Delete() { + AVObject obj = AVObject.Create("Foo"); + obj["content"] = "hello, world"; + await obj.SaveAsync(); + Assert.NotNull(obj); + await obj.DeleteAsync(); + } + + [Test] + public async Task DeleteAll() { + List objList = new List(); + for (int i = 0; i < 5; i++) { + AVObject obj = AVObject.Create("Foo"); + obj["content"] = "batch object"; + objList.Add(obj); + } + await objList.SaveAllAsync(); + await AVObject.DeleteAllAsync(objList); + } } } diff --git a/Storage/Storage/Internal/Object/Controller/AVObjectController.cs b/Storage/Storage/Internal/Object/Controller/AVObjectController.cs index b7f4be7..8295487 100644 --- a/Storage/Storage/Internal/Object/Controller/AVObjectController.cs +++ b/Storage/Storage/Internal/Object/Controller/AVObjectController.cs @@ -15,7 +15,7 @@ namespace LeanCloud.Storage.Internal { Path = $"classes/{Uri.EscapeDataString(state.ClassName)}/{Uri.EscapeDataString(state.ObjectId)}?{AVClient.BuildQueryString(queryString)}", Method = HttpMethod.Get }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken: cancellationToken).OnSuccess(t => { + return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken).OnSuccess(t => { return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); }); } @@ -44,7 +44,7 @@ namespace LeanCloud.Storage.Internal { string encode = AVClient.BuildQueryString(args); command.Path = $"{command.Path}?{encode}"; } - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken: cancellationToken).OnSuccess(t => { + return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken).OnSuccess(t => { var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); serverState = serverState.MutatedClone(mutableClone => { mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; @@ -82,7 +82,7 @@ namespace LeanCloud.Storage.Internal { Path = $"classes/{state.ClassName}/{state.ObjectId}", Method = HttpMethod.Delete }; - return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken: cancellationToken); + return AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); } public IList DeleteAllAsync(IList states, @@ -131,8 +131,8 @@ namespace LeanCloud.Storage.Internal { var encodedRequests = requests.Select(r => { var results = new Dictionary { - { "method", r.Method }, - { "path", r.Path }, + { "method", r.Method.Method }, + { "path", $"/{AVClient.APIVersion}/{r.Path}" }, }; if (r.Content != null) { @@ -147,7 +147,7 @@ namespace LeanCloud.Storage.Internal { { "requests", encodedRequests } } }; - AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken: cancellationToken).ContinueWith(t => { + AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken).ContinueWith(t => { if (t.IsFaulted || t.IsCanceled) { foreach (var tcs in tcss) { if (t.IsFaulted) { @@ -159,7 +159,7 @@ namespace LeanCloud.Storage.Internal { return; } - var resultsArray = Conversion.As>(t.Result.Item2["results"]); + var resultsArray = t.Result.Item2; int resultLength = resultsArray.Count; if (resultLength != batchSize) { foreach (var tcs in tcss) { diff --git a/Storage/Storage/Internal/Object/State/MutableObjectState.cs b/Storage/Storage/Internal/Object/State/MutableObjectState.cs index ff9be3a..5718d07 100644 --- a/Storage/Storage/Internal/Object/State/MutableObjectState.cs +++ b/Storage/Storage/Internal/Object/State/MutableObjectState.cs @@ -2,10 +2,8 @@ using System.Linq; using System.Collections.Generic; -namespace LeanCloud.Storage.Internal -{ - public class MutableObjectState : IObjectState - { +namespace LeanCloud.Storage.Internal { + public class MutableObjectState : IObjectState { public bool IsNew { get; set; } public string ClassName { get; set; } public string ObjectId { get; set; } @@ -14,84 +12,65 @@ namespace LeanCloud.Storage.Internal // Initialize serverData to avoid further null checking. private IDictionary serverData = new Dictionary(); - public IDictionary ServerData - { - get - { + public IDictionary ServerData { + get { return serverData; } - set - { + set { serverData = value; } } - public object this[string key] - { - get - { + public object this[string key] { + get { return ServerData[key]; } } - public bool ContainsKey(string key) - { + public bool ContainsKey(string key) { return ServerData.ContainsKey(key); } - public void Apply(IDictionary operationSet) - { + public void Apply(IDictionary operationSet) { // Apply operationSet - foreach (var pair in operationSet) - { + foreach (var pair in operationSet) { object oldValue; ServerData.TryGetValue(pair.Key, out oldValue); var newValue = pair.Value.Apply(oldValue, pair.Key); - if (newValue != AVDeleteOperation.DeleteToken) - { + if (newValue != AVDeleteOperation.DeleteToken) { ServerData[pair.Key] = newValue; - } - else - { + } else { ServerData.Remove(pair.Key); } } } - public void Apply(IObjectState other) - { + public void Apply(IObjectState other) { IsNew = other.IsNew; - if (other.ObjectId != null) - { + if (other.ObjectId != null) { ObjectId = other.ObjectId; } - if (other.UpdatedAt != null) - { + if (other.UpdatedAt != null) { UpdatedAt = other.UpdatedAt; } - if (other.CreatedAt != null) - { + if (other.CreatedAt != null) { CreatedAt = other.CreatedAt; } - foreach (var pair in other) - { + foreach (var pair in other) { ServerData[pair.Key] = pair.Value; } } - public IObjectState MutatedClone(Action func) - { + public IObjectState MutatedClone(Action func) { var clone = MutableClone(); func(clone); return clone; } - protected virtual MutableObjectState MutableClone() - { - return new MutableObjectState - { + protected virtual MutableObjectState MutableClone() { + return new MutableObjectState { IsNew = IsNew, ClassName = ClassName, ObjectId = ObjectId, @@ -101,13 +80,11 @@ namespace LeanCloud.Storage.Internal }; } - IEnumerator> IEnumerable>.GetEnumerator() - { + IEnumerator> IEnumerable>.GetEnumerator() { return ServerData.GetEnumerator(); } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((IEnumerable>)this).GetEnumerator(); } } diff --git a/Storage/Storage/Internal/Object/Subclassing/ObjectSubclassInfo.cs b/Storage/Storage/Internal/Object/Subclassing/ObjectSubclassInfo.cs index 860a3e5..95be34c 100644 --- a/Storage/Storage/Internal/Object/Subclassing/ObjectSubclassInfo.cs +++ b/Storage/Storage/Internal/Object/Subclassing/ObjectSubclassInfo.cs @@ -1,18 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using System.Reflection; -using LeanCloud.Storage.Internal; -namespace LeanCloud.Storage.Internal -{ - internal class ObjectSubclassInfo - { - public ObjectSubclassInfo(Type type, ConstructorInfo constructor) - { +namespace LeanCloud.Storage.Internal { + internal class ObjectSubclassInfo { + public ObjectSubclassInfo(Type type, ConstructorInfo constructor) { TypeInfo = type.GetTypeInfo(); ClassName = GetClassName(TypeInfo); Constructor = constructor; @@ -28,15 +21,13 @@ namespace LeanCloud.Storage.Internal public IDictionary PropertyMappings { get; private set; } private ConstructorInfo Constructor { get; set; } - public AVObject Instantiate() - { + public AVObject Instantiate() { return (AVObject)Constructor.Invoke(null); } - internal static string GetClassName(TypeInfo type) - { + internal static string GetClassName(TypeInfo type) { var attribute = type.GetCustomAttribute(); - return attribute != null ? attribute.ClassName : null; + return attribute?.ClassName; } } } diff --git a/Storage/Storage/Internal/Object/Subclassing/ObjectSubclassingController.cs b/Storage/Storage/Internal/Object/Subclassing/ObjectSubclassingController.cs index 919f053..3458459 100644 --- a/Storage/Storage/Internal/Object/Subclassing/ObjectSubclassingController.cs +++ b/Storage/Storage/Internal/Object/Subclassing/ObjectSubclassingController.cs @@ -11,7 +11,7 @@ namespace LeanCloud.Storage.Internal { private readonly ReaderWriterLockSlim mutex; private readonly IDictionary registeredSubclasses; - private Dictionary registerActions; + private readonly Dictionary registerActions; public ObjectSubclassingController() { mutex = new ReaderWriterLockSlim(); @@ -38,10 +38,8 @@ namespace LeanCloud.Storage.Internal { } public bool IsTypeValid(string className, Type type) { - ObjectSubclassInfo subclassInfo = null; - mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out subclassInfo); + registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo subclassInfo); mutex.ExitReadLock(); return subclassInfo == null @@ -63,12 +61,12 @@ namespace LeanCloud.Storage.Internal { // TOCTTOU bug. mutex.EnterWriteLock(); - ObjectSubclassInfo previousInfo = null; - if (registeredSubclasses.TryGetValue(className, out previousInfo)) { + if (registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo previousInfo)) { if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo)) { // Previous subclass is more specific or equal to the current type, do nothing. return; - } else if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo)) { + } + if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo)) { // Previous subclass is parent of new child, fallthrough and actually register // this class. /* Do nothing */ @@ -91,10 +89,9 @@ namespace LeanCloud.Storage.Internal { mutex.ExitWriteLock(); } - Action toPerform; mutex.EnterReadLock(); - registerActions.TryGetValue(className, out toPerform); + registerActions.TryGetValue(className, out Action toPerform); mutex.ExitReadLock(); toPerform?.Invoke(); @@ -113,10 +110,9 @@ namespace LeanCloud.Storage.Internal { } public AVObject Instantiate(string className) { - ObjectSubclassInfo info = null; mutex.EnterReadLock(); - registeredSubclasses.TryGetValue(className, out info); + registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info); mutex.ExitReadLock(); return info != null diff --git a/Storage/Storage/Internal/Utilities/AVFileExtensions.cs b/Storage/Storage/Internal/Utilities/AVFileExtensions.cs deleted file mode 100644 index ae502d5..0000000 --- a/Storage/Storage/Internal/Utilities/AVFileExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LeanCloud.Storage.Internal { - /// - /// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc. - /// - /// These cannot be 'internal' anymore if we are fully modularizing things out, because - /// they are no longer a part of the same library, especially as we create things like - /// Installation inside push library. - /// - /// So this class contains a bunch of extension methods that can live inside another - /// namespace, which 'wrap' the intenral APIs that already exist. - /// - public static class AVFileExtensions { - public static AVFile Create(string name, Uri uri, string mimeType = null) { - return new AVFile(name, uri, mimeType); - } - } -} diff --git a/Storage/Storage/Public/AVObject.cs b/Storage/Storage/Public/AVObject.cs index fa31756..fc2d0af 100644 --- a/Storage/Storage/Public/AVObject.cs +++ b/Storage/Storage/Public/AVObject.cs @@ -17,7 +17,7 @@ namespace LeanCloud { private static readonly string AutoClassName = "_Automatic"; #if UNITY - private static readonly bool isCompiledByIL2CPP = AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain"); + private static readonly bool isCompiledByIL2CPP = AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain"); #else private static readonly bool isCompiledByIL2CPP = false; #endif @@ -192,7 +192,7 @@ namespace LeanCloud { #endregion - public static IDictionary GetPropertyMappings(string className) { + public static IDictionary GetPropertyMappings(string className) { return SubclassingController.GetPropertyMappings(className); } @@ -246,7 +246,7 @@ string propertyName string propertyName #endif ) { - return GetProperty(default(T), propertyName); + return GetProperty(default, propertyName); } /// @@ -262,9 +262,8 @@ string propertyName #else string propertyName #endif -) { - T result; - if (TryGetValue(GetFieldForPropertyName(ClassName, propertyName), out result)) { + ) { + if (TryGetValue(GetFieldForPropertyName(ClassName, propertyName), out T result)) { return result; } return defaultValue;