* 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;
}
public DateTime FetchedAt {
public DateTimeOffset FetchedAt {
get; internal set;
}
public AppRouterState() {
FetchedAt = DateTime.Now;
FetchedAt = DateTimeOffset.Now;
}
public bool IsExpired {
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"]}");
}
[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]
public async Task SaveBatch() {
List<AVObject> objList = new List<AVObject>();

View File

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

View File

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

View File

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

View File

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

View File

@ -814,49 +814,26 @@ string propertyName
#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) {
return Task.FromResult(0);
return;
}
return toAwait.OnSuccess(_ => {
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);
await ObjectController.DeleteAsync(State, cancellationToken);
IsDirty = true;
}
/// <summary>
/// Deletes each object in the provided list.
/// </summary>
/// <param name="objects">The objects to delete.</param>
public static Task DeleteAllAsync<T>(IEnumerable<T> objects) where T : AVObject {
return DeleteAllAsync(objects, CancellationToken.None);
}
/// <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 {
public static Task DeleteAllAsync<T>(IEnumerable<T> objects, CancellationToken cancellationToken = default)
where T : AVObject {
var uniqueObjects = new HashSet<AVObject>(objects.OfType<AVObject>().ToList(),
new IdentityEqualityComparer<AVObject>());
new IdentityEqualityComparer<AVObject>());
return EnqueueForAll<object>(uniqueObjects, toAwait => {
var states = uniqueObjects.Select(t => t.state).ToList();
@ -1094,7 +1071,7 @@ string propertyName
/// </summary>
internal virtual void OnSettingValue(ref string key, ref object value) {
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) {
T current;
bool hasCurrent = TryGetValue<T>(key, out current);
bool hasCurrent = TryGetValue<T>(key, out T current);
if (value == null) {
if (hasCurrent) {
PerformOperation(key, AVDeleteOperation.Instance);
@ -1313,10 +1289,10 @@ string propertyName
/// <returns></returns>
public AVQuery<T> GetRelationRevserseQuery<T>(string parentClassName, string key) where T : AVObject {
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)) {
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);
}
@ -1604,7 +1580,7 @@ string propertyName
// types.
if (SubclassingController.GetType(className) != null) {
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);
}
@ -1665,7 +1641,7 @@ string propertyName
}
private SynchronizedEventHandler<PropertyUpdatedEventArgs> propertyUpdated =
new SynchronizedEventHandler<PropertyUpdatedEventArgs>();
new SynchronizedEventHandler<PropertyUpdatedEventArgs>();
public event PropertyUpdatedEventHandler PropertyUpdated {
add {
@ -1681,7 +1657,7 @@ string propertyName
}
private SynchronizedEventHandler<CollectionPropertyUpdatedEventArgs> collectionUpdated =
new SynchronizedEventHandler<CollectionPropertyUpdatedEventArgs>();
new SynchronizedEventHandler<CollectionPropertyUpdatedEventArgs>();
public event CollectionPropertyUpdatedEventHandler CollectionPropertyUpdated {
add {