* AppRouterState.cs: chore: 使用 await/async 替换 ContinueWith/OnSuccess

* QueryTest.cs:
* ObjectTest.cs:
* AVObject.cs:
* ObjectControllerTests.cs:
* AVFileController.cs:
* AVObjectController.cs:
* AVCloudCodeController.cs:
oneRain 2019-09-06 11:51:52 +08:00
parent eda7dd43bb
commit f3ed814d96
8 changed files with 102 additions and 118 deletions

View File

@ -40,17 +40,17 @@ namespace LeanCloud.Storage.Internal {
get; internal set; get; internal set;
} }
public DateTime FetchedAt { public DateTimeOffset FetchedAt {
get; internal set; get; internal set;
} }
public AppRouterState() { public AppRouterState() {
FetchedAt = DateTime.Now; FetchedAt = DateTimeOffset.Now;
} }
public bool IsExpired { public bool IsExpired {
get { get {
return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL); return DateTimeOffset.Now > FetchedAt.AddSeconds(TTL);
} }
} }

View File

@ -33,6 +33,27 @@ namespace LeanCloudTests {
TestContext.Out.WriteLine($"balance: {account["balance"]}"); TestContext.Out.WriteLine($"balance: {account["balance"]}");
} }
[Test]
public async Task SaveWithPointer() {
AVObject comment = AVObject.Create("Comment");
comment["content"] = "Hello, Comment";
AVObject post = AVObject.Create("Post");
post["name"] = "New Post";
AVObject category = AVObject.Create("Category");
post["category"] = category;
comment["post"] = post;
AVObject testPost = AVObject.Create("Post");
testPost["name"] = "Test Post";
comment["test_post"] = testPost;
await comment.SaveAsync();
}
[Test] [Test]
public async Task SaveBatch() { public async Task SaveBatch() {
List<AVObject> objList = new List<AVObject>(); List<AVObject> objList = new List<AVObject>();

View File

@ -45,7 +45,7 @@ namespace LeanCloudTests {
res.Dispose(); res.Dispose();
TestContext.Out.WriteLine($"response at {Thread.CurrentThread.ManagedThreadId}"); TestContext.Out.WriteLine($"response at {Thread.CurrentThread.ManagedThreadId}");
TestContext.Out.WriteLine(data); TestContext.Out.WriteLine(data);
Assert.Pass(); Assert.Pass();
} }
[Test] [Test]

View File

@ -8,12 +8,7 @@ namespace LeanCloudTests {
public class QueryTest { public class QueryTest {
[SetUp] [SetUp]
public void SetUp() { public void SetUp() {
AVClient.Initialize(new AVClient.Configuration { Utils.InitNorthChina();
ApplicationId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz",
ApplicationKey = "pbf6Nk5seyjilexdpyrPwjSp",
ApiServer = "https://avoscloud.com"
});
AVClient.HttpLog(TestContext.Out.WriteLine);
} }
[Test] [Test]

View File

@ -7,7 +7,7 @@ using System.Net.Http;
namespace LeanCloud.Storage.Internal { namespace LeanCloud.Storage.Internal {
public class AVCloudCodeController { public class AVCloudCodeController {
public Task<T> CallFunctionAsync<T>(string name, public async Task<T> CallFunctionAsync<T>(string name,
IDictionary<string, object> parameters, IDictionary<string, object> parameters,
CancellationToken cancellationToken) { CancellationToken cancellationToken) {
var command = new EngineCommand { var command = new EngineCommand {
@ -15,28 +15,26 @@ namespace LeanCloud.Storage.Internal {
Method = HttpMethod.Post, Method = HttpMethod.Post,
Content = parameters ?? new Dictionary<string, object>() Content = parameters ?? new Dictionary<string, object>()
}; };
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken).OnSuccess(t => { var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary<string, object>; var decoded = AVDecoder.Instance.Decode(data.Item2) as IDictionary<string, object>;
if (!decoded.ContainsKey("result")) { if (!decoded.ContainsKey("result")) {
return default; return default;
} }
return Conversion.To<T>(decoded["result"]); return Conversion.To<T>(decoded["result"]);
});
} }
public Task<T> RPCFunction<T>(string name, IDictionary<string, object> parameters, CancellationToken cancellationToken) { public async Task<T> RPCFunction<T>(string name, IDictionary<string, object> parameters, CancellationToken cancellationToken) {
var command = new EngineCommand { var command = new EngineCommand {
Path = $"call/{Uri.EscapeUriString(name)}", Path = $"call/{Uri.EscapeUriString(name)}",
Method = HttpMethod.Post, Method = HttpMethod.Post,
Content = parameters ?? new Dictionary<string, object>() Content = parameters ?? new Dictionary<string, object>()
}; };
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken).OnSuccess(t => { var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary<string, object>; var decoded = AVDecoder.Instance.Decode(data.Item2) as IDictionary<string, object>;
if (!decoded.ContainsKey("result")) { if (!decoded.ContainsKey("result")) {
return default; return default;
} }
return Conversion.To<T>(decoded["result"]); return Conversion.To<T>(decoded["result"]);
});
} }
} }
} }

View File

@ -17,39 +17,36 @@ namespace LeanCloud.Storage.Internal {
const string QCloud = "qcloud"; const string QCloud = "qcloud";
const string AWS = "s3"; const string AWS = "s3";
public Task<FileState> SaveAsync(FileState state, public async Task<FileState> SaveAsync(FileState state,
Stream dataStream, Stream dataStream,
IProgress<AVUploadProgressEventArgs> progress, IProgress<AVUploadProgressEventArgs> progress,
CancellationToken cancellationToken = default) { CancellationToken cancellationToken = default) {
if (state.Url != null) { if (state.Url != null) {
return SaveWithUrl(state); return await SaveWithUrl(state);
} }
return GetFileToken(state, cancellationToken).OnSuccess(t => { var data = await GetFileToken(state, cancellationToken);
// 根据 provider 区分 cdn var fileToken = data.Item2;
var ret = t.Result; var provider = fileToken["provider"] as string;
var fileToken = ret.Item2; switch (provider) {
var provider = fileToken["provider"] as string; case QCloud:
switch (provider) { return await new QCloudUploader().Upload(state, dataStream, fileToken, progress, cancellationToken);
case QCloud: case AWS:
return new QCloudUploader().Upload(state, dataStream, fileToken, progress, cancellationToken); return await new AWSUploader().Upload(state, dataStream, fileToken, progress, cancellationToken);
case AWS: default:
return new AWSUploader().Upload(state, dataStream, fileToken, progress, cancellationToken); return await new QiniuUploader().Upload(state, dataStream, fileToken, progress, cancellationToken);
default: }
return new QiniuUploader().Upload(state, dataStream, fileToken, progress, cancellationToken);
}
}).Unwrap();
} }
public Task DeleteAsync(FileState state, CancellationToken cancellationToken) { public async Task DeleteAsync(FileState state, CancellationToken cancellationToken) {
var command = new AVCommand { var command = new AVCommand {
Path = $"files/{state.ObjectId}", Path = $"files/{state.ObjectId}",
Method = HttpMethod.Delete Method = HttpMethod.Delete
}; };
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken); await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken);
} }
internal Task<FileState> SaveWithUrl(FileState state) { internal async Task<FileState> SaveWithUrl(FileState state) {
Dictionary<string, object> strs = new Dictionary<string, object> { Dictionary<string, object> strs = new Dictionary<string, object> {
{ "url", state.Url.ToString() }, { "url", state.Url.ToString() },
{ "name", state.Name }, { "name", state.Name },
@ -72,14 +69,13 @@ namespace LeanCloud.Storage.Internal {
}; };
} }
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(cmd).OnSuccess(t => { var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(cmd);
var result = t.Result.Item2; var result = data.Item2;
state.ObjectId = result["objectId"].ToString(); state.ObjectId = result["objectId"].ToString();
return state; return state;
});
} }
internal Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetFileToken(FileState fileState, CancellationToken cancellationToken) { internal async Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetFileToken(FileState fileState, CancellationToken cancellationToken) {
string str = fileState.Name; string str = fileState.Name;
IDictionary<string, object> parameters = new Dictionary<string, object> { IDictionary<string, object> parameters = new Dictionary<string, object> {
{ "name", str }, { "name", str },
@ -94,24 +90,22 @@ namespace LeanCloud.Storage.Internal {
Method = HttpMethod.Post, Method = HttpMethod.Post,
Content = parameters Content = parameters
}; };
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command); return await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
} }
public Task<FileState> GetAsync(string objectId, CancellationToken cancellationToken) { public async Task<FileState> GetAsync(string objectId, CancellationToken cancellationToken) {
var command = new AVCommand { var command = new AVCommand {
Path = $"files/{objectId}", Path = $"files/{objectId}",
Method = HttpMethod.Get Method = HttpMethod.Get
}; };
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken).OnSuccess(_ => { var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
var result = _.Result; var jsonData = data.Item2;
var jsonData = result.Item2; cancellationToken.ThrowIfCancellationRequested();
cancellationToken.ThrowIfCancellationRequested(); return new FileState {
return new FileState { ObjectId = jsonData["objectId"] as string,
ObjectId = jsonData["objectId"] as string, Name = jsonData["name"] as string,
Name = jsonData["name"] as string, Url = new Uri(jsonData["url"] as string, UriKind.Absolute),
Url = new Uri(jsonData["url"] as string, UriKind.Absolute), };
};
});
} }
internal static string GetUniqueName(FileState state) { internal static string GetUniqueName(FileState state) {

View File

@ -8,19 +8,19 @@ using System.Net.Http;
namespace LeanCloud.Storage.Internal { namespace LeanCloud.Storage.Internal {
public class AVObjectController { public class AVObjectController {
public Task<IObjectState> FetchAsync(IObjectState state, public async Task<IObjectState> FetchAsync(IObjectState state,
IDictionary<string, object> queryString, IDictionary<string, object> queryString,
CancellationToken cancellationToken) { CancellationToken cancellationToken) {
var command = new AVCommand { var command = new AVCommand {
Path = $"classes/{Uri.EscapeDataString(state.ClassName)}/{Uri.EscapeDataString(state.ObjectId)}?{AVClient.BuildQueryString(queryString)}", Path = $"classes/{Uri.EscapeDataString(state.ClassName)}/{Uri.EscapeDataString(state.ObjectId)}?{AVClient.BuildQueryString(queryString)}",
Method = HttpMethod.Get Method = HttpMethod.Get
}; };
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken).OnSuccess(t => { var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); var objState = AVObjectCoder.Instance.Decode(data.Item2, AVDecoder.Instance);
}); return objState;
} }
public Task<IObjectState> SaveAsync(IObjectState state, public async Task<IObjectState> SaveAsync(IObjectState state,
IDictionary<string, IAVFieldOperation> operations, IDictionary<string, IAVFieldOperation> operations,
bool fetchWhenSave, bool fetchWhenSave,
AVQuery<AVObject> query, AVQuery<AVObject> query,
@ -44,13 +44,12 @@ namespace LeanCloud.Storage.Internal {
string encode = AVClient.BuildQueryString(args); string encode = AVClient.BuildQueryString(args);
command.Path = $"{command.Path}?{encode}"; command.Path = $"{command.Path}?{encode}";
} }
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken).OnSuccess(t => { var data = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance); var serverState = AVObjectCoder.Instance.Decode(data.Item2, AVDecoder.Instance);
serverState = serverState.MutatedClone(mutableClone => { serverState = serverState.MutatedClone(mutableClone => {
mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created; mutableClone.IsNew = data.Item1 == System.Net.HttpStatusCode.Created;
});
return serverState;
}); });
return serverState;
} }
public IList<Task<IObjectState>> SaveAllAsync(IList<IObjectState> states, public IList<Task<IObjectState>> SaveAllAsync(IList<IObjectState> states,
@ -76,13 +75,13 @@ namespace LeanCloud.Storage.Internal {
return stateTasks; return stateTasks;
} }
public Task DeleteAsync(IObjectState state, public async Task DeleteAsync(IObjectState state,
CancellationToken cancellationToken) { CancellationToken cancellationToken) {
var command = new AVCommand { var command = new AVCommand {
Path = $"classes/{state.ClassName}/{state.ObjectId}", Path = $"classes/{state.ClassName}/{state.ObjectId}",
Method = HttpMethod.Delete Method = HttpMethod.Delete
}; };
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken); await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
} }
public IList<Task> DeleteAllAsync(IList<IObjectState> states, public IList<Task> DeleteAllAsync(IList<IObjectState> states,
@ -99,6 +98,7 @@ namespace LeanCloud.Storage.Internal {
// TODO (hallucinogen): move this out to a class to be used by Analytics // TODO (hallucinogen): move this out to a class to be used by Analytics
private const int MaximumBatchSize = 50; private const int MaximumBatchSize = 50;
internal IList<Task<IDictionary<string, object>>> ExecuteBatchRequests(IList<AVCommand> requests, internal IList<Task<IDictionary<string, object>>> ExecuteBatchRequests(IList<AVCommand> requests,
CancellationToken cancellationToken) { CancellationToken cancellationToken) {
var tasks = new List<Task<IDictionary<string, object>>>(); var tasks = new List<Task<IDictionary<string, object>>>();

View File

@ -814,49 +814,26 @@ string propertyName
#region Delete Object #region Delete Object
internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken) { /// <summary>
/// Deletes this object on the server.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
public async Task DeleteAsync(CancellationToken cancellationToken = default) {
if (ObjectId == null) { if (ObjectId == null) {
return Task.FromResult(0); return;
} }
await ObjectController.DeleteAsync(State, cancellationToken);
return toAwait.OnSuccess(_ => { IsDirty = true;
return ObjectController.DeleteAsync(State, cancellationToken);
}).Unwrap().OnSuccess(_ => IsDirty = true);
}
/// <summary>
/// Deletes this object on the server.
/// </summary>
public Task DeleteAsync() {
return DeleteAsync(CancellationToken.None);
}
/// <summary>
/// Deletes this object on the server.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
public Task DeleteAsync(CancellationToken cancellationToken) {
return taskQueue.Enqueue(toAwait => DeleteAsync(toAwait, cancellationToken),
cancellationToken);
} }
/// <summary> /// <summary>
/// Deletes each object in the provided list. /// Deletes each object in the provided list.
/// </summary> /// </summary>
/// <param name="objects">The objects to delete.</param> /// <param name="objects">The objects to delete.</param>
public static Task DeleteAllAsync<T>(IEnumerable<T> objects) where T : AVObject { public static Task DeleteAllAsync<T>(IEnumerable<T> objects, CancellationToken cancellationToken = default)
return DeleteAllAsync(objects, CancellationToken.None); where T : AVObject {
}
/// <summary>
/// Deletes each object in the provided list.
/// </summary>
/// <param name="objects">The objects to delete.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public static Task DeleteAllAsync<T>(
IEnumerable<T> objects, CancellationToken cancellationToken) where T : AVObject {
var uniqueObjects = new HashSet<AVObject>(objects.OfType<AVObject>().ToList(), var uniqueObjects = new HashSet<AVObject>(objects.OfType<AVObject>().ToList(),
new IdentityEqualityComparer<AVObject>()); new IdentityEqualityComparer<AVObject>());
return EnqueueForAll<object>(uniqueObjects, toAwait => { return EnqueueForAll<object>(uniqueObjects, toAwait => {
var states = uniqueObjects.Select(t => t.state).ToList(); var states = uniqueObjects.Select(t => t.state).ToList();
@ -1094,7 +1071,7 @@ string propertyName
/// </summary> /// </summary>
internal virtual void OnSettingValue(ref string key, ref object value) { internal virtual void OnSettingValue(ref string key, ref object value) {
if (key == null) { if (key == null) {
throw new ArgumentNullException("key"); throw new ArgumentNullException(nameof(key));
} }
} }
@ -1151,8 +1128,7 @@ string propertyName
} }
internal void SetIfDifferent<T>(string key, T value) { internal void SetIfDifferent<T>(string key, T value) {
T current; bool hasCurrent = TryGetValue<T>(key, out T current);
bool hasCurrent = TryGetValue<T>(key, out current);
if (value == null) { if (value == null) {
if (hasCurrent) { if (hasCurrent) {
PerformOperation(key, AVDeleteOperation.Instance); PerformOperation(key, AVDeleteOperation.Instance);
@ -1313,10 +1289,10 @@ string propertyName
/// <returns></returns> /// <returns></returns>
public AVQuery<T> GetRelationRevserseQuery<T>(string parentClassName, string key) where T : AVObject { public AVQuery<T> GetRelationRevserseQuery<T>(string parentClassName, string key) where T : AVObject {
if (string.IsNullOrEmpty(parentClassName)) { if (string.IsNullOrEmpty(parentClassName)) {
throw new ArgumentNullException("parentClassName", "can not query a relation without parentClassName."); throw new ArgumentNullException(nameof(parentClassName), "can not query a relation without parentClassName.");
} }
if (string.IsNullOrEmpty(key)) { if (string.IsNullOrEmpty(key)) {
throw new ArgumentNullException("key", "can not query a relation without key."); throw new ArgumentNullException(nameof(key), "can not query a relation without key.");
} }
return new AVQuery<T>(parentClassName).WhereEqualTo(key, this); return new AVQuery<T>(parentClassName).WhereEqualTo(key, this);
} }
@ -1604,7 +1580,7 @@ string propertyName
// types. // types.
if (SubclassingController.GetType(className) != null) { if (SubclassingController.GetType(className) != null) {
throw new ArgumentException( throw new ArgumentException(
"Use the class-specific query properties for class " + className, "className"); "Use the class-specific query properties for class " + className, nameof(className));
} }
return new AVQuery<AVObject>(className); return new AVQuery<AVObject>(className);
} }
@ -1665,7 +1641,7 @@ string propertyName
} }
private SynchronizedEventHandler<PropertyUpdatedEventArgs> propertyUpdated = private SynchronizedEventHandler<PropertyUpdatedEventArgs> propertyUpdated =
new SynchronizedEventHandler<PropertyUpdatedEventArgs>(); new SynchronizedEventHandler<PropertyUpdatedEventArgs>();
public event PropertyUpdatedEventHandler PropertyUpdated { public event PropertyUpdatedEventHandler PropertyUpdated {
add { add {
@ -1681,7 +1657,7 @@ string propertyName
} }
private SynchronizedEventHandler<CollectionPropertyUpdatedEventArgs> collectionUpdated = private SynchronizedEventHandler<CollectionPropertyUpdatedEventArgs> collectionUpdated =
new SynchronizedEventHandler<CollectionPropertyUpdatedEventArgs>(); new SynchronizedEventHandler<CollectionPropertyUpdatedEventArgs>();
public event CollectionPropertyUpdatedEventHandler CollectionPropertyUpdated { public event CollectionPropertyUpdatedEventHandler CollectionPropertyUpdated {
add { add {