using System; using System.Linq; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using LeanCloud.Utilities; using LeanCloud.Storage.Internal; namespace LeanCloud.Storage.Internal { public class AVObjectController : IAVObjectController { private readonly IAVCommandRunner commandRunner; public AVObjectController(IAVCommandRunner commandRunner) { this.commandRunner = commandRunner; } public Task FetchAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) { var command = new AVCommand(string.Format("classes/{0}/{1}", Uri.EscapeDataString(state.ClassName), Uri.EscapeDataString(state.ObjectId)), method: "GET", sessionToken: sessionToken, data: null); return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => { return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); }); } public Task FetchAsync(IObjectState state, IDictionary queryString, string sessionToken, CancellationToken cancellationToken) { var command = new AVCommand(string.Format("classes/{0}/{1}?{2}", Uri.EscapeDataString(state.ClassName), Uri.EscapeDataString(state.ObjectId), AVClient.BuildQueryString(queryString)), method: "GET", sessionToken: sessionToken, data: null); return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => { return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); }); } public Task SaveAsync(IObjectState state, IDictionary operations, string sessionToken, CancellationToken cancellationToken) { var objectJSON = AVObject.ToJSONObjectForSaving(operations); var command = new AVCommand((state.ObjectId == null ? string.Format("classes/{0}", Uri.EscapeDataString(state.ClassName)) : string.Format("classes/{0}/{1}", Uri.EscapeDataString(state.ClassName), state.ObjectId)), method: (state.ObjectId == null ? "POST" : "PUT"), sessionToken: sessionToken, data: objectJSON); return commandRunner.RunCommandAsync(command, cancellationToken: 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; }); return serverState; }); } public IList> SaveAllAsync(IList states, IList> operationsList, string sessionToken, CancellationToken cancellationToken) { var requests = states .Zip(operationsList, (item, ops) => new AVCommand( item.ObjectId == null ? string.Format("classes/{0}", Uri.EscapeDataString(item.ClassName)) : string.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), method: item.ObjectId == null ? "POST" : "PUT", data: AVObject.ToJSONObjectForSaving(ops))) .ToList(); var batchTasks = ExecuteBatchRequests(requests, sessionToken, cancellationToken); var stateTasks = new List>(); foreach (var task in batchTasks) { stateTasks.Add(task.OnSuccess(t => { return AVObjectCoder.Instance.Decode(t.Result, AVDecoder.Instance); })); } return stateTasks; } public Task DeleteAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) { var command = new AVCommand(string.Format("classes/{0}/{1}", state.ClassName, state.ObjectId), method: "DELETE", sessionToken: sessionToken, data: null); return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken); } public IList DeleteAllAsync(IList states, string sessionToken, CancellationToken cancellationToken) { var requests = states .Where(item => item.ObjectId != null) .Select(item => new AVCommand( string.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), method: "DELETE", data: null)) .ToList(); return ExecuteBatchRequests(requests, sessionToken, cancellationToken).Cast().ToList(); } // TODO (hallucinogen): move this out to a class to be used by Analytics private const int MaximumBatchSize = 50; internal IList>> ExecuteBatchRequests(IList requests, string sessionToken, 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, sessionToken, cancellationToken)); batchSize = remaining.Count(); } tasks.AddRange(ExecuteBatchRequest(remaining.ToList(), sessionToken, cancellationToken)); return tasks; } private IList>> ExecuteBatchRequest(IList requests, string sessionToken, 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 }, { "path", r.Uri.AbsolutePath }, }; if (r.DataObject != null) { results["body"] = r.DataObject; } return results; }).Cast().ToList(); var command = new AVCommand("batch", method: "POST", sessionToken: sessionToken, data: new Dictionary { { "requests", encodedRequests } }); commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t => { if (t.IsFaulted || t.IsCanceled) { foreach (var tcs in tcss) { if (t.IsFaulted) { tcs.TrySetException(t.Exception); } else if (t.IsCanceled) { tcs.TrySetCanceled(); } } return; } var resultsArray = Conversion.As>(t.Result.Item2["results"]); 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 + ".")); } return; } 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.")); } } }); return tasks; } } }