diff --git a/Storage/Storage.Test/AppRouterTest.cs b/Storage/Storage.Test/AppRouterTest.cs deleted file mode 100644 index 5d4d5c2..0000000 --- a/Storage/Storage.Test/AppRouterTest.cs +++ /dev/null @@ -1,12 +0,0 @@ -using NUnit.Framework; -using System.Threading.Tasks; -using LeanCloud.Storage.Internal; - -namespace LeanCloud.Test { - public class AppRouterTest { - [Test] - public async Task GetServers() { - - } - } -} diff --git a/Storage/Storage/Internal/AVCorePlugins.cs b/Storage/Storage/Internal/AVCorePlugins.cs index 4b94af5..3f7b9f4 100644 --- a/Storage/Storage/Internal/AVCorePlugins.cs +++ b/Storage/Storage/Internal/AVCorePlugins.cs @@ -1,4 +1,5 @@ - +using LeanCloud.Common; + namespace LeanCloud.Storage.Internal { public class AVPlugins { private static readonly object instanceMutex = new object(); @@ -52,7 +53,8 @@ namespace LeanCloud.Storage.Internal { public AppRouterController AppRouterController { get { lock (mutex) { - appRouterController = appRouterController ?? new AppRouterController(); + var conf = AVClient.CurrentConfiguration; + appRouterController = appRouterController ?? new AppRouterController(conf.ApplicationId, conf.ApiServer); return appRouterController; } } diff --git a/Storage/Storage/Internal/Command/AVCommandRunner.cs b/Storage/Storage/Internal/Command/AVCommandRunner.cs index c40bec5..bbd6887 100644 --- a/Storage/Storage/Internal/Command/AVCommandRunner.cs +++ b/Storage/Storage/Internal/Command/AVCommandRunner.cs @@ -5,9 +5,9 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; -using System.Text; using System.Linq; using Newtonsoft.Json; +using LeanCloud.Common; namespace LeanCloud.Storage.Internal { /// @@ -44,6 +44,7 @@ namespace LeanCloud.Storage.Internal { httpClient.DefaultRequestHeaders.Add("X-LC-Prod", AVClient.UseProduction ? USE_PRODUCTION : USE_DEVELOPMENT); } + /// /// /// @@ -71,14 +72,14 @@ namespace LeanCloud.Storage.Internal { !string.IsNullOrEmpty(AVUser.CurrentUser.SessionToken)) { request.Headers.Add("X-LC-Session", AVUser.CurrentUser.SessionToken); } - PrintRequest(httpClient, request, content); + HttpUtils.PrintRequest(httpClient, request, content); var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); request.Dispose(); var resultString = await response.Content.ReadAsStringAsync(); response.Dispose(); - PrintResponse(response, resultString); + HttpUtils.PrintResponse(response, resultString); var ret = new Tuple(response.StatusCode, resultString); @@ -115,33 +116,65 @@ namespace LeanCloud.Storage.Internal { return new Tuple(responseCode, default); } - static void PrintRequest(HttpClient client, HttpRequestMessage request, string content) { - StringBuilder sb = new StringBuilder(); - sb.AppendLine("=== HTTP Request Start ==="); - sb.AppendLine($"URL: {request.RequestUri}"); - sb.AppendLine($"Method: {request.Method}"); - sb.AppendLine($"Headers: "); - foreach (var header in client.DefaultRequestHeaders) { - sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); + + // TODO (hallucinogen): move this out to a class to be used by Analytics + private const int MaximumBatchSize = 50; + + internal async Task>> ExecuteBatchRequests(IList requests, + CancellationToken cancellationToken) { + var results = new List>(); + int batchSize = requests.Count; + + IEnumerable remaining = requests; + while (batchSize > MaximumBatchSize) { + var process = remaining.Take(MaximumBatchSize).ToList(); + remaining = remaining.Skip(MaximumBatchSize); + + results.AddRange(await ExecuteBatchRequest(process, cancellationToken)); + + batchSize = remaining.Count(); } - foreach (var header in request.Headers) { - sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); - } - foreach (var header in request.Content.Headers) { - sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); - } - sb.AppendLine($"Content: {content}"); - sb.AppendLine("=== HTTP Request End ==="); - AVClient.PrintLog(sb.ToString()); + results.AddRange(await ExecuteBatchRequest(remaining.ToList(), cancellationToken)); + + return results; } - static void PrintResponse(HttpResponseMessage response, string content) { - StringBuilder sb = new StringBuilder(); - sb.AppendLine("=== HTTP Response Start ==="); - sb.AppendLine($"URL: {response.RequestMessage.RequestUri}"); - sb.AppendLine($"Content: {content}"); - sb.AppendLine("=== HTTP Response End ==="); - AVClient.PrintLog(sb.ToString()); + internal async Task>> ExecuteBatchRequest(IList requests, CancellationToken cancellationToken) { + var tasks = new List>>(); + int batchSize = requests.Count; + var tcss = new List>>(); + for (int i = 0; i < batchSize; ++i) { + var tcs = new TaskCompletionSource>(); + tcss.Add(tcs); + tasks.Add(tcs.Task); + } + + var encodedRequests = requests.Select(r => { + var results = new Dictionary { + { "method", r.Method.Method }, + { "path", $"/{AVClient.APIVersion}/{r.Path}" }, + }; + + if (r.Content != null) { + results["body"] = r.Content; + } + return results; + }).Cast().ToList(); + var command = new AVCommand { + Path = "batch", + Method = HttpMethod.Post, + Content = new Dictionary { + { "requests", encodedRequests } + } + }; + + try { + List> result = new List>(); + var response = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); + return response.Item2.Cast>().ToList(); + } catch (Exception e) { + throw e; + } } } } diff --git a/Storage/Storage/Internal/File/Controller/QCloudUploader.cs b/Storage/Storage/Internal/File/Controller/QCloudUploader.cs index 25e1e6b..bfea7aa 100644 --- a/Storage/Storage/Internal/File/Controller/QCloudUploader.cs +++ b/Storage/Storage/Internal/File/Controller/QCloudUploader.cs @@ -7,6 +7,7 @@ using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; +using LeanCloud.Common; namespace LeanCloud.Storage.Internal { internal class QCloudUploader { diff --git a/Storage/Storage/Internal/File/Controller/QiniuUploader.cs b/Storage/Storage/Internal/File/Controller/QiniuUploader.cs index 1606f5e..aa2f8bb 100644 --- a/Storage/Storage/Internal/File/Controller/QiniuUploader.cs +++ b/Storage/Storage/Internal/File/Controller/QiniuUploader.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using System.Net.Http; using System.Net.Http.Headers; +using LeanCloud.Common; namespace LeanCloud.Storage.Internal { internal enum CommonSize : long { diff --git a/Storage/Storage/Internal/Object/Controller/AVObjectController.cs b/Storage/Storage/Internal/Object/Controller/AVObjectController.cs index 2269658..6dc00e7 100644 --- a/Storage/Storage/Internal/Object/Controller/AVObjectController.cs +++ b/Storage/Storage/Internal/Object/Controller/AVObjectController.cs @@ -51,10 +51,9 @@ namespace LeanCloud.Storage.Internal { return serverState; } - public IList> SaveAllAsync(IList states, + public async Task> SaveAllAsync(IList states, IList> operationsList, CancellationToken cancellationToken) { - var requests = states .Zip(operationsList, (item, ops) => new AVCommand { Path = item.ObjectId == null ? $"classes/{Uri.EscapeDataString(item.ClassName)}" : $"classes/{Uri.EscapeDataString(item.ClassName)}/{Uri.EscapeDataString(item.ObjectId)}", @@ -62,16 +61,15 @@ namespace LeanCloud.Storage.Internal { Content = AVObject.ToJSONObjectForSaving(ops) }) .ToList(); - - var batchTasks = ExecuteBatchRequests(requests, cancellationToken); - var stateTasks = new List>(); - foreach (var task in batchTasks) { - stateTasks.Add(task.OnSuccess(t => { - return AVObjectCoder.Instance.Decode(t.Result, AVDecoder.Instance); - })); + IList list = new List(); + var result = await AVPlugins.Instance.CommandRunner.ExecuteBatchRequests(requests, cancellationToken); + foreach (var data in result) { + if (data.TryGetValue("success", out object val)) { + IObjectState obj = AVObjectCoder.Instance.Decode(val as IDictionary, AVDecoder.Instance); + list.Add(obj); + } } - - return stateTasks; + return list; } public async Task DeleteAsync(IObjectState state, AVQuery query, CancellationToken cancellationToken) { @@ -88,7 +86,7 @@ namespace LeanCloud.Storage.Internal { await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); } - public IList DeleteAllAsync(IList states, + public async Task DeleteAllAsync(IList states, CancellationToken cancellationToken) { var requests = states .Where(item => item.ObjectId != null) @@ -97,92 +95,9 @@ namespace LeanCloud.Storage.Internal { Method = HttpMethod.Delete }) .ToList(); - return ExecuteBatchRequests(requests, cancellationToken).Cast().ToList(); - } + await AVPlugins.Instance.CommandRunner.ExecuteBatchRequests(requests, cancellationToken); + // TODO 判断是否全部失败或者网络错误 - // TODO (hallucinogen): move this out to a class to be used by Analytics - private const int MaximumBatchSize = 50; - - internal IList>> ExecuteBatchRequests(IList requests, - CancellationToken cancellationToken) { - var tasks = new List>>(); - int batchSize = requests.Count; - - IEnumerable remaining = requests; - while (batchSize > MaximumBatchSize) { - var process = remaining.Take(MaximumBatchSize).ToList(); - remaining = remaining.Skip(MaximumBatchSize); - - tasks.AddRange(ExecuteBatchRequest(process, cancellationToken)); - - batchSize = remaining.Count(); - } - tasks.AddRange(ExecuteBatchRequest(remaining.ToList(), cancellationToken)); - - return tasks; - } - - private async Task>>> ExecuteBatchRequest(IList requests, - CancellationToken cancellationToken) { - var tasks = new List>>(); - int batchSize = requests.Count; - var tcss = new List>>(); - for (int i = 0; i < batchSize; ++i) { - var tcs = new TaskCompletionSource>(); - tcss.Add(tcs); - tasks.Add(tcs.Task); - } - - var encodedRequests = requests.Select(r => { - var results = new Dictionary { - { "method", r.Method.Method }, - { "path", $"/{AVClient.APIVersion}/{r.Path}" }, - }; - - if (r.Content != null) { - results["body"] = r.Content; - } - return results; - }).Cast().ToList(); - var command = new AVCommand { - Path = "batch", - Method = HttpMethod.Post, - Content = new Dictionary { - { "requests", encodedRequests } - } - }; - - try { - var response = await AVPlugins.Instance.CommandRunner.RunCommandAsync>(command, cancellationToken); - var resultsArray = response.Item2; - int resultLength = resultsArray.Count; - if (resultLength != batchSize) { - foreach (var tcs in tcss) { - tcs.TrySetException(new InvalidOperationException( - "Batch command result count expected: " + batchSize + " but was: " + resultLength + ".")); - } - } - - for (int i = 0; i < batchSize; ++i) { - var result = resultsArray[i] as Dictionary; - var tcs = tcss[i]; - - if (result.ContainsKey("success")) { - tcs.TrySetResult(result["success"] as IDictionary); - } else if (result.ContainsKey("error")) { - var error = result["error"] as IDictionary; - long errorCode = long.Parse(error["code"].ToString()); - tcs.TrySetException(new AVException((AVException.ErrorCode)errorCode, error["error"] as string)); - } else { - tcs.TrySetException(new InvalidOperationException( - "Invalid batch command response.")); - } - } - } catch (Exception e) { - foreach (var tcs in tcss) { - tcs.TrySetException(e); - } - } } } } diff --git a/Storage/Storage/Public/AVObject.cs b/Storage/Storage/Public/AVObject.cs index 4ce96a9..be4f90d 100644 --- a/Storage/Storage/Public/AVObject.cs +++ b/Storage/Storage/Public/AVObject.cs @@ -610,12 +610,11 @@ string propertyName var operationsList = (from item in current select item.StartSave()).ToList(); - var saveTasks = ObjectController.SaveAllAsync(states, + var serverStates = await ObjectController.SaveAllAsync(states, operationsList, cancellationToken); try { - var serverStates = await Task.WhenAll(saveTasks); foreach (var pair in current.Zip(serverStates, (item, state) => new { item, state })) { pair.item.HandleSave(pair.state); } @@ -802,25 +801,16 @@ string propertyName /// Deletes each object in the provided list. /// /// The objects to delete. - public static Task DeleteAllAsync(IEnumerable objects, CancellationToken cancellationToken = default) + public static async Task DeleteAllAsync(IEnumerable objects, CancellationToken cancellationToken = default) where T : AVObject { var uniqueObjects = new HashSet(objects.OfType().ToList(), new IdentityEqualityComparer()); - return EnqueueForAll(uniqueObjects, toAwait => { - var states = uniqueObjects.Select(t => t.state).ToList(); - return toAwait.OnSuccess(_ => { - var deleteTasks = ObjectController.DeleteAllAsync(states, cancellationToken); - return Task.WhenAll(deleteTasks); - }).Unwrap().OnSuccess(t => { - // Dirty all objects in memory. - foreach (var obj in uniqueObjects) { - obj.IsDirty = true; - } - - return (object)null; - }); - }, cancellationToken); + var states = uniqueObjects.Select(t => t.state).ToList(); + await ObjectController.DeleteAllAsync(states, cancellationToken); + foreach (var obj in uniqueObjects) { + obj.IsDirty = true; + } } #endregion diff --git a/Storage/Storage/Public/AVObject2.cs b/Storage/Storage/Public/AVObject2.cs deleted file mode 100644 index fe68ca3..0000000 --- a/Storage/Storage/Public/AVObject2.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Concurrent; -using System.Collections; - -namespace Storage.Public { - public class AVObject2 : IDictionary { - ConcurrentDictionary data; - - public object this[string key] { get => ((IDictionary)data)[key]; set => ((IDictionary)data)[key] = value; } - - public ICollection Keys => ((IDictionary)data).Keys; - - public ICollection Values => ((IDictionary)data).Values; - - public int Count => ((IDictionary)data).Count; - - public bool IsReadOnly => ((IDictionary)data).IsReadOnly; - - public void Add(string key, object value) { - ((IDictionary)data).Add(key, value); - } - - public void Add(KeyValuePair item) { - ((IDictionary)data).Add(item); - } - - public void Clear() { - ((IDictionary)data).Clear(); - } - - public bool Contains(KeyValuePair item) { - return ((IDictionary)data).Contains(item); - } - - public bool ContainsKey(string key) { - return ((IDictionary)data).ContainsKey(key); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) { - ((IDictionary)data).CopyTo(array, arrayIndex); - } - - public IEnumerator> GetEnumerator() { - return ((IDictionary)data).GetEnumerator(); - } - - public bool Remove(string key) { - return ((IDictionary)data).Remove(key); - } - - public bool Remove(KeyValuePair item) { - return ((IDictionary)data).Remove(item); - } - - public bool TryGetValue(string key, out object value) { - return ((IDictionary)data).TryGetValue(key, out value); - } - - IEnumerator IEnumerable.GetEnumerator() { - return ((IDictionary)data).GetEnumerator(); - } - } -} diff --git a/Test/Common.Test/Test.cs b/Test/Common.Test/Test.cs new file mode 100644 index 0000000..0951d12 --- /dev/null +++ b/Test/Common.Test/Test.cs @@ -0,0 +1,7 @@ +using System; +namespace Common.Test { + public class Test { + public Test() { + } + } +}