Merge pull request #26 from onerain88/net_standard
Net standard
commit
5f153eeca7
|
@ -0,0 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Internal\" />
|
||||
<Folder Include="Internal\AppRouter\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AppRouterController {
|
||||
private AppRouterState currentState;
|
||||
|
||||
private readonly SemaphoreSlim locker = new SemaphoreSlim(1);
|
||||
|
||||
public async Task<AppRouterState> Get(string appId) {
|
||||
if (string.IsNullOrEmpty(appId)) {
|
||||
throw new ArgumentNullException(nameof(appId));
|
||||
}
|
||||
|
||||
if (currentState != null && !currentState.IsExpired) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
await locker.WaitAsync();
|
||||
try {
|
||||
if (currentState == null) {
|
||||
try {
|
||||
currentState = await QueryAsync(appId);
|
||||
} catch (Exception) {
|
||||
currentState = AppRouterState.GetFallbackServers(appId);
|
||||
}
|
||||
}
|
||||
return currentState;
|
||||
} finally {
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
async Task<AppRouterState> QueryAsync(string appId) {
|
||||
Console.WriteLine("QueryAsync");
|
||||
|
||||
string url = string.Format("https://app-router.leancloud.cn/2/route?appId={0}", appId);
|
||||
|
||||
HttpClient client = new HttpClient();
|
||||
HttpRequestMessage request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(url),
|
||||
Method = HttpMethod.Get
|
||||
};
|
||||
|
||||
HttpResponseMessage response = await client.SendAsync(request);
|
||||
client.Dispose();
|
||||
request.Dispose();
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
|
||||
AppRouterState state = JsonConvert.DeserializeObject<AppRouterState>(content);
|
||||
state.Source = "router";
|
||||
return state;
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
currentState = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AppRouterState {
|
||||
const string EAST_CHINA_SUFFIX = "-9Nh9j0Va";
|
||||
const string US_SUFFIX = "-MdYXbMMI";
|
||||
|
||||
[JsonProperty("ttl")]
|
||||
public long TTL {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
[JsonProperty("api_server")]
|
||||
public string ApiServer {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
[JsonProperty("engine_server")]
|
||||
public string EngineServer {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
[JsonProperty("push_server")]
|
||||
public string PushServer {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
[JsonProperty("rtm_router_server")]
|
||||
public string RTMServer {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
[JsonProperty("stats_server")]
|
||||
public string StatsServer {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
public string Source {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
public DateTime FetchedAt {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
public AppRouterState() {
|
||||
FetchedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
public bool IsExpired {
|
||||
get {
|
||||
return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL);
|
||||
}
|
||||
}
|
||||
|
||||
public static AppRouterState GetFallbackServers(string appId) {
|
||||
var prefix = appId.Substring(0, 8).ToLower();
|
||||
var suffix = appId.Substring(appId.Length - 9);
|
||||
switch (suffix) {
|
||||
case EAST_CHINA_SUFFIX:
|
||||
// 华东
|
||||
return new AppRouterState {
|
||||
TTL = -1,
|
||||
ApiServer = $"{prefix}.api.lncldapi.com",
|
||||
EngineServer = $"{prefix}.engine.lncldapi.com",
|
||||
PushServer = $"{prefix}.push.lncldapi.com",
|
||||
RTMServer = $"{prefix}.rtm.lncldapi.com",
|
||||
StatsServer = $"{prefix}.stats.lncldapi.com",
|
||||
Source = "fallback",
|
||||
};
|
||||
case US_SUFFIX:
|
||||
// 美国
|
||||
return new AppRouterState {
|
||||
TTL = -1,
|
||||
ApiServer = $"{prefix}.api.lncldglobal.com",
|
||||
EngineServer = $"{prefix}.engine.lncldglobal.com",
|
||||
PushServer = $"{prefix}.push.lncldglobal.com",
|
||||
RTMServer = $"{prefix}.rtm.lncldglobal.com",
|
||||
StatsServer = $"{prefix}.stats.lncldglobal.com",
|
||||
Source = "fallback",
|
||||
};
|
||||
default:
|
||||
// 华北
|
||||
return new AppRouterState {
|
||||
TTL = -1,
|
||||
ApiServer = $"{prefix}.api.lncld.net",
|
||||
EngineServer = $"{prefix}.engine.lncld.net",
|
||||
PushServer = $"{prefix}.push.lncld.net",
|
||||
RTMServer = $"{prefix}.rtm.lncld.net",
|
||||
StatsServer = $"{prefix}.stats.lncld.net",
|
||||
Source = "fallback",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -179,7 +179,7 @@ namespace LeanCloud.LiveQuery
|
|||
{ "id", AVLiveQuery.InstallationId },
|
||||
{ "clientTimestamp", AVLiveQuery.ClientTs }
|
||||
};
|
||||
string sessionToken = AVUser.CurrentUser != null ? AVUser.CurrentUser.SessionToken : string.Empty;
|
||||
string sessionToken = AVUser.CurrentUser?.SessionToken;
|
||||
if (!string.IsNullOrEmpty(sessionToken)) {
|
||||
data.Add("sessionToken", sessionToken);
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ namespace LeanCloud.LiveQuery
|
|||
{ "id", AVLiveQuery.InstallationId },
|
||||
{ "query_id", Id },
|
||||
};
|
||||
string sessionToken = AVUser.CurrentUser != null ? AVUser.CurrentUser.SessionToken : string.Empty;
|
||||
string sessionToken = AVUser.CurrentUser?.SessionToken;
|
||||
var command = new AVCommand("LiveQuery/unsubscribe",
|
||||
"POST",
|
||||
sessionToken,
|
||||
|
|
|
@ -46,11 +46,11 @@ namespace LeanCloud.Storage.Internal
|
|||
return new AppRouterState()
|
||||
{
|
||||
TTL = -1,
|
||||
ApiServer = String.Format("{0}.api.lncld.net", prefix),
|
||||
EngineServer = String.Format("{0}.engine.lncld.net", prefix),
|
||||
PushServer = String.Format("{0}.push.lncld.net", prefix),
|
||||
RealtimeRouterServer = String.Format("{0}.rtm.lncld.net", prefix),
|
||||
StatsServer = String.Format("{0}.stats.lncld.net", prefix),
|
||||
ApiServer = string.Format("{0}.api.lncld.net", prefix),
|
||||
EngineServer = string.Format("{0}.engine.lncld.net", prefix),
|
||||
PushServer = string.Format("{0}.push.lncld.net", prefix),
|
||||
RealtimeRouterServer = string.Format("{0}.rtm.lncld.net", prefix),
|
||||
StatsServer = string.Format("{0}.stats.lncld.net", prefix),
|
||||
Source = "initial",
|
||||
};
|
||||
case 1:
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace LeanCloud.Storage.Internal
|
|||
{
|
||||
public interface IAVCloudCodeController
|
||||
{
|
||||
Task<T> CallFunctionAsync<T>(String name,
|
||||
Task<T> CallFunctionAsync<T>(string name,
|
||||
IDictionary<string, object> parameters,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace LeanCloud.Storage.Internal
|
|||
public Task<Tuple<HttpStatusCode, IDictionary<string, object>>> RunCommandAsync(AVCommand command,
|
||||
IProgress<AVUploadProgressEventArgs> uploadProgress = null,
|
||||
IProgress<AVDownloadProgressEventArgs> downloadProgress = null,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return PrepareCommand(command).ContinueWith(commandTask =>
|
||||
{
|
||||
|
|
|
@ -17,8 +17,8 @@ namespace LeanCloud.Storage.Internal
|
|||
/// <param name="cancellationToken">The cancellation token for the request.</param>
|
||||
/// <returns></returns>
|
||||
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> RunCommandAsync(AVCommand command,
|
||||
IProgress<AVUploadProgressEventArgs> uploadProgress = null,
|
||||
IProgress<AVDownloadProgressEventArgs> downloadProgress = null,
|
||||
CancellationToken cancellationToken = default(CancellationToken));
|
||||
IProgress<AVUploadProgressEventArgs> uploadProgress = null,
|
||||
IProgress<AVDownloadProgressEventArgs> downloadProgress = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,6 @@ namespace LeanCloud.Storage.Internal {
|
|||
/// <returns>The config async.</returns>
|
||||
/// <param name="sessionToken">Session token.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<AVConfig> FetchConfigAsync(String sessionToken, CancellationToken cancellationToken);
|
||||
Task<AVConfig> FetchConfigAsync(string sessionToken, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,20 +7,17 @@ using System.Net;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// AVF ile controller.
|
||||
/// </summary>
|
||||
public class AVFileController : IAVFileController
|
||||
{
|
||||
public class AVFileController : IAVFileController {
|
||||
private readonly IAVCommandRunner commandRunner;
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Storage.Internal.AVFileController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="commandRunner">Command runner.</param>
|
||||
public AVFileController(IAVCommandRunner commandRunner)
|
||||
{
|
||||
public AVFileController(IAVCommandRunner commandRunner) {
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
/// <summary>
|
||||
|
@ -33,19 +30,16 @@ namespace LeanCloud.Storage.Internal
|
|||
/// <param name="progress">Progress.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public virtual Task<FileState> SaveAsync(FileState state,
|
||||
Stream dataStream,
|
||||
String sessionToken,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (state.Url != null)
|
||||
{
|
||||
Stream dataStream,
|
||||
string sessionToken,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken = default) {
|
||||
if (state.Url != null) {
|
||||
// !isDirty
|
||||
return Task<FileState>.FromResult(state);
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) {
|
||||
var tcs = new TaskCompletionSource<FileState>();
|
||||
tcs.TrySetCanceled();
|
||||
return tcs.Task;
|
||||
|
@ -60,30 +54,25 @@ namespace LeanCloud.Storage.Internal
|
|||
|
||||
return commandRunner.RunCommandAsync(command,
|
||||
uploadProgress: progress,
|
||||
cancellationToken: cancellationToken).OnSuccess(uploadTask =>
|
||||
{
|
||||
cancellationToken: cancellationToken).OnSuccess(uploadTask => {
|
||||
var result = uploadTask.Result;
|
||||
var jsonData = result.Item2;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
return new FileState
|
||||
{
|
||||
return new FileState {
|
||||
Name = jsonData["name"] as string,
|
||||
Url = new Uri(jsonData["url"] as string, UriKind.Absolute),
|
||||
MimeType = state.MimeType
|
||||
};
|
||||
}).ContinueWith(t =>
|
||||
{
|
||||
}).ContinueWith(t => {
|
||||
// Rewind the stream on failure or cancellation (if possible)
|
||||
if ((t.IsFaulted || t.IsCanceled) && dataStream.CanSeek)
|
||||
{
|
||||
if ((t.IsFaulted || t.IsCanceled) && dataStream.CanSeek) {
|
||||
dataStream.Seek(oldPosition, SeekOrigin.Begin);
|
||||
}
|
||||
return t;
|
||||
}).Unwrap();
|
||||
}
|
||||
public Task DeleteAsync(FileState state, string sessionToken, CancellationToken cancellationToken)
|
||||
{
|
||||
public Task DeleteAsync(FileState state, string sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand("files/" + state.ObjectId,
|
||||
method: "DELETE",
|
||||
sessionToken: sessionToken,
|
||||
|
@ -91,8 +80,7 @@ namespace LeanCloud.Storage.Internal
|
|||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken);
|
||||
}
|
||||
internal static Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetFileToken(FileState fileState, CancellationToken cancellationToken)
|
||||
{
|
||||
internal static Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetFileToken(FileState fileState, CancellationToken cancellationToken) {
|
||||
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> rtn;
|
||||
string currentSessionToken = AVUser.CurrentSessionToken;
|
||||
string str = fileState.Name;
|
||||
|
@ -107,43 +95,37 @@ namespace LeanCloud.Storage.Internal
|
|||
|
||||
return rtn;
|
||||
}
|
||||
public Task<FileState> GetAsync(string objectId, string sessionToken, CancellationToken cancellationToken)
|
||||
{
|
||||
public Task<FileState> GetAsync(string objectId, string sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand("files/" + objectId,
|
||||
method: "GET",
|
||||
sessionToken: sessionToken,
|
||||
data: null);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(_ =>
|
||||
{
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(_ => {
|
||||
var result = _.Result;
|
||||
var jsonData = result.Item2;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new FileState
|
||||
{
|
||||
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 fileState)
|
||||
{
|
||||
internal static string GetUniqueName(FileState fileState) {
|
||||
string key = Random(12);
|
||||
string extension = Path.GetExtension(fileState.Name);
|
||||
key += extension;
|
||||
fileState.CloudName = key;
|
||||
return key;
|
||||
}
|
||||
internal static string Random(int length)
|
||||
{
|
||||
internal static string Random(int length) {
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
var random = new Random();
|
||||
return new string(Enumerable.Repeat(chars, length)
|
||||
.Select(s => s[random.Next(s.Length)]).ToArray());
|
||||
}
|
||||
internal static double CalcProgress(double already, double total)
|
||||
{
|
||||
internal static double CalcProgress(double already, double total) {
|
||||
var pv = (1.0 * already / total);
|
||||
return Math.Round(pv, 3);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace LeanCloud.Storage.Internal
|
|||
|
||||
}
|
||||
|
||||
public override Task<FileState> SaveAsync(FileState state, Stream dataStream, string sessionToken, IProgress<AVUploadProgressEventArgs> progress, CancellationToken cancellationToken = default(System.Threading.CancellationToken))
|
||||
public override Task<FileState> SaveAsync(FileState state, Stream dataStream, string sessionToken, IProgress<AVUploadProgressEventArgs> progress, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (state.Url != null)
|
||||
{
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace LeanCloud.Storage.Internal
|
|||
{
|
||||
Task<FileState> SaveAsync(FileState state,
|
||||
Stream dataStream,
|
||||
String sessionToken,
|
||||
string sessionToken,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace LeanCloud.Storage.Internal
|
|||
var statusCode = GetResponseStatusCode(request);
|
||||
// Returns HTTP error if that's the only info we have.
|
||||
// if (!String.IsNullOrEmpty(www.error) && String.IsNullOrEmpty(www.text))
|
||||
if (!String.IsNullOrEmpty(request.error) && String.IsNullOrEmpty(request.downloadHandler.text))
|
||||
if (!string.IsNullOrEmpty(request.error) && string.IsNullOrEmpty(request.downloadHandler.text))
|
||||
{
|
||||
var errorString = string.Format("{{\"error\":\"{0}\"}}", request.error);
|
||||
tcs.TrySetResult(new Tuple<HttpStatusCode, string>(statusCode, errorString));
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace LeanCloud.Storage.Internal {
|
|||
return query.ClassName;
|
||||
}
|
||||
|
||||
public static IDictionary<String, object> BuildParameters<T>(this AVQuery<T> query) where T: AVObject {
|
||||
public static IDictionary<string, object> BuildParameters<T>(this AVQuery<T> query) where T: AVObject {
|
||||
return query.BuildParameters(false);
|
||||
}
|
||||
|
||||
|
|
|
@ -85,17 +85,17 @@ namespace LeanCloud
|
|||
/// <summary>
|
||||
/// The build number of your app.
|
||||
/// </summary>
|
||||
public String BuildVersion { get; set; }
|
||||
public string BuildVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The human friendly version number of your happ.
|
||||
/// </summary>
|
||||
public String DisplayVersion { get; set; }
|
||||
public string DisplayVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The operating system version of the platform the SDK is operating in..
|
||||
/// </summary>
|
||||
public String OSVersion { get; set; }
|
||||
public string OSVersion { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace LeanCloud {
|
|||
/// <param name="sesstionToken"></param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The result of the cloud call.</returns>
|
||||
public static Task<T> CallFunctionAsync<T>(String name, IDictionary<string, object> parameters = null, string sesstionToken = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
public static Task<T> CallFunctionAsync<T>(string name, IDictionary<string, object> parameters = null, string sesstionToken = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var sessionTokenTask = AVUser.TakeSessionToken(sesstionToken);
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace LeanCloud {
|
|||
/// <param name="sesstionToken"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<T> RPCFunctionAsync<T>(String name, IDictionary<string, object> parameters = null, string sesstionToken = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
public static Task<T> RPCFunctionAsync<T>(string name, IDictionary<string, object> parameters = null, string sesstionToken = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var sessionTokenTask = AVUser.TakeSessionToken(sesstionToken);
|
||||
|
||||
|
@ -108,20 +108,6 @@ namespace LeanCloud {
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求短信认证。
|
||||
/// </summary>
|
||||
/// <param name="mobilePhoneNumber">手机号。</param>
|
||||
/// <param name="name">应用名称。</param>
|
||||
/// <param name="op">进行的操作名称。</param>
|
||||
/// <param name="ttl">验证码失效时间。</param>
|
||||
/// <returns></returns>
|
||||
public static Task<bool> RequestSMSCodeAsync(string mobilePhoneNumber, string name, string op, int ttl = 10)
|
||||
{
|
||||
return RequestSMSCodeAsync(mobilePhoneNumber, name, op, ttl, CancellationToken.None);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 请求发送验证码。
|
||||
/// </summary>
|
||||
|
@ -131,7 +117,7 @@ namespace LeanCloud {
|
|||
/// <param name="op">进行的操作名称。</param>
|
||||
/// <param name="ttl">验证码失效时间。</param>
|
||||
/// <param name="cancellationToken">Cancellation token。</param>
|
||||
public static Task<bool> RequestSMSCodeAsync(string mobilePhoneNumber, string name, string op, int ttl = 10, CancellationToken cancellationToken = default(CancellationToken))
|
||||
public static Task<bool> RequestSMSCodeAsync(string mobilePhoneNumber, string name, string op, int ttl = 10, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(mobilePhoneNumber))
|
||||
{
|
||||
|
@ -195,15 +181,13 @@ namespace LeanCloud {
|
|||
/// <param name="template">Sms's template</param>
|
||||
/// <param name="env">Template variables env.</param>
|
||||
/// <param name="sign">Sms's sign.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns></returns>
|
||||
public static Task<bool> RequestSMSCodeAsync(
|
||||
string mobilePhoneNumber,
|
||||
string template,
|
||||
IDictionary<string, object> env,
|
||||
string sign = "",
|
||||
string validateToken = "",
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
string validateToken = "")
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(mobilePhoneNumber))
|
||||
|
@ -215,11 +199,11 @@ namespace LeanCloud {
|
|||
{ "mobilePhoneNumber", mobilePhoneNumber },
|
||||
};
|
||||
strs.Add("template", template);
|
||||
if (String.IsNullOrEmpty(sign))
|
||||
if (string.IsNullOrEmpty(sign))
|
||||
{
|
||||
strs.Add("sign", sign);
|
||||
}
|
||||
if (String.IsNullOrEmpty(validateToken))
|
||||
if (string.IsNullOrEmpty(validateToken))
|
||||
{
|
||||
strs.Add("validate_token", validateToken);
|
||||
}
|
||||
|
@ -318,7 +302,7 @@ namespace LeanCloud {
|
|||
/// <param name="code">User's input of this captcha.</param>
|
||||
/// <param name="cancellationToken">CancellationToken.</param>
|
||||
/// <returns></returns>
|
||||
public Task VerifyAsync(string code, CancellationToken cancellationToken = default(CancellationToken))
|
||||
public Task VerifyAsync(string code)
|
||||
{
|
||||
return AVCloud.VerifyCaptchaAsync(code, Token);
|
||||
}
|
||||
|
@ -331,10 +315,10 @@ namespace LeanCloud {
|
|||
/// <param name="height">captcha image height.</param>
|
||||
/// <param name="cancellationToken">CancellationToken.</param>
|
||||
/// <returns>an instance of Captcha.</returns>
|
||||
public static Task<Captcha> RequestCaptchaAsync(int width = 85, int height = 30, CancellationToken cancellationToken = default(CancellationToken))
|
||||
public static Task<Captcha> RequestCaptchaAsync(int width = 85, int height = 30, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var path = String.Format("requestCaptcha?width={0}&height={1}", width, height);
|
||||
var command = new AVCommand(path, method: "GET", sessionToken: null, data: null);
|
||||
var path = string.Format("requestCaptcha?width={0}&height={1}", width, height);
|
||||
var command = new AVCommand(path, "GET", null, data: null);
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary<string, object>;
|
||||
|
@ -353,14 +337,14 @@ namespace LeanCloud {
|
|||
/// <param name="code">User's input of this captcha.</param>
|
||||
/// <param name="cancellationToken">CancellationToken.</param>
|
||||
/// <returns></returns>
|
||||
public static Task<string> VerifyCaptchaAsync(string code, string token, CancellationToken cancellationToken = default(CancellationToken))
|
||||
public static Task<string> VerifyCaptchaAsync(string code, string token, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var data = new Dictionary<string, object>
|
||||
{
|
||||
{ "captcha_token", token },
|
||||
{ "captcha_code", code },
|
||||
};
|
||||
var command = new AVCommand("verifyCaptcha", method: "POST", sessionToken: null, data: data);
|
||||
var command = new AVCommand("verifyCaptcha", "POST", null, data: data);
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t =>
|
||||
{
|
||||
if (!t.Result.Item2.ContainsKey("validate_token"))
|
||||
|
@ -374,7 +358,7 @@ namespace LeanCloud {
|
|||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<IDictionary<string, object>> GetCustomParametersAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
public static Task<IDictionary<string, object>> GetCustomParametersAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var command = new AVCommand(string.Format("statistics/apps/{0}/sendPolicy", AVClient.CurrentConfiguration.ApplicationId),
|
||||
method: "GET",
|
||||
|
@ -397,7 +381,7 @@ namespace LeanCloud {
|
|||
public string Signature { internal set; get; }
|
||||
}
|
||||
|
||||
public static Task<RealtimeSignature> RequestRealtimeSignatureAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
public static Task<RealtimeSignature> RequestRealtimeSignatureAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return AVUser.GetCurrentUserAsync(cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
|
@ -405,7 +389,7 @@ namespace LeanCloud {
|
|||
}).Unwrap();
|
||||
}
|
||||
|
||||
public static Task<RealtimeSignature> RequestRealtimeSignatureAsync(AVUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
public static Task<RealtimeSignature> RequestRealtimeSignatureAsync(AVUser user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var command = new AVCommand(string.Format("rtm/sign"),
|
||||
method: "POST",
|
||||
|
@ -518,7 +502,7 @@ namespace LeanCloud {
|
|||
var user = t.Result;
|
||||
var encodedParameters = Encode(parameters);
|
||||
var command = new AVCommand(
|
||||
string.Format("call/{0}", Uri.EscapeUriString(this.FunctionName)),
|
||||
string.Format("call/{0}", Uri.EscapeUriString(FunctionName)),
|
||||
method: "POST",
|
||||
sessionToken: user != null ? user.SessionToken : null,
|
||||
data: encodedParameters);
|
||||
|
|
|
@ -386,7 +386,7 @@ namespace LeanCloud
|
|||
{
|
||||
get
|
||||
{
|
||||
String str;
|
||||
string str;
|
||||
lock (this.mutex)
|
||||
{
|
||||
str = state.ObjectId;
|
||||
|
|
|
@ -241,8 +241,7 @@ namespace LeanCloud
|
|||
|
||||
private static string GetFieldForPropertyName(string className, string propertyName)
|
||||
{
|
||||
String fieldName = null;
|
||||
SubclassingController.GetPropertyMappings(className).TryGetValue(propertyName, out fieldName);
|
||||
SubclassingController.GetPropertyMappings(className).TryGetValue(propertyName, out string fieldName);
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ namespace LeanCloud
|
|||
int? limit = null,
|
||||
IEnumerable<string> includes = null,
|
||||
IEnumerable<string> selectedKeys = null,
|
||||
String redirectClassNameForKey = null)
|
||||
string redirectClassNameForKey = null)
|
||||
: base(source, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey)
|
||||
{
|
||||
|
||||
|
@ -114,7 +114,7 @@ namespace LeanCloud
|
|||
int? limit = null,
|
||||
IEnumerable<string> includes = null,
|
||||
IEnumerable<string> selectedKeys = null,
|
||||
String redirectClassNameForKey = null)
|
||||
string redirectClassNameForKey = null)
|
||||
{
|
||||
return new AVQuery<T>(this, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -140,7 +140,7 @@ namespace LeanCloud
|
|||
protected readonly ReadOnlyCollection<string> orderBy;
|
||||
protected readonly ReadOnlyCollection<string> includes;
|
||||
protected readonly ReadOnlyCollection<string> selectedKeys;
|
||||
protected readonly String redirectClassNameForKey;
|
||||
protected readonly string redirectClassNameForKey;
|
||||
protected readonly int? skip;
|
||||
protected readonly int? limit;
|
||||
|
||||
|
@ -184,7 +184,7 @@ namespace LeanCloud
|
|||
int? limit = null,
|
||||
IEnumerable<string> includes = null,
|
||||
IEnumerable<string> selectedKeys = null,
|
||||
String redirectClassNameForKey = null);
|
||||
string redirectClassNameForKey = null);
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor for composition of queries. A Source query is required,
|
||||
|
@ -199,7 +199,7 @@ namespace LeanCloud
|
|||
int? limit = null,
|
||||
IEnumerable<string> includes = null,
|
||||
IEnumerable<string> selectedKeys = null,
|
||||
String redirectClassNameForKey = null)
|
||||
string redirectClassNameForKey = null)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
|
@ -295,13 +295,13 @@ namespace LeanCloud
|
|||
return newIncludes;
|
||||
}
|
||||
|
||||
private HashSet<String> MergeSelectedKeys(IEnumerable<String> selectedKeys)
|
||||
private HashSet<string> MergeSelectedKeys(IEnumerable<string> selectedKeys)
|
||||
{
|
||||
if (this.selectedKeys == null)
|
||||
{
|
||||
return new HashSet<string>(selectedKeys);
|
||||
}
|
||||
var newSelectedKeys = new HashSet<String>(this.selectedKeys);
|
||||
var newSelectedKeys = new HashSet<string>(this.selectedKeys);
|
||||
foreach (var item in selectedKeys)
|
||||
{
|
||||
newSelectedKeys.Add(item);
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
using NUnit.Framework;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class AppRouterTest {
|
||||
[Test]
|
||||
public async Task GetServers() {
|
||||
var appRouter = new AppRouterController();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
var state = await appRouter.Get("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz");
|
||||
TestContext.Out.WriteLine(state.ApiServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using NUnit.Framework;
|
||||
using LeanCloud;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class CloudFunctionTest {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
Utils.InitNorthChina();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Hello() {
|
||||
AVClient.UseProduction = true;
|
||||
string result = await AVCloud.CallFunctionAsync<string>("hello", new Dictionary<string, object> {
|
||||
{ "word", "world" }
|
||||
});
|
||||
Assert.AreEqual(result, "hello, world");
|
||||
TestContext.Out.WriteLine($"resutlt: {result}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetUsernameInCloud() {
|
||||
AVClient.UseProduction = true;
|
||||
await AVUser.LogInAsync("111111", "111111");
|
||||
string result = await AVCloud.CallFunctionAsync<string>("getUsername");
|
||||
Assert.AreEqual(result, "111111");
|
||||
TestContext.Out.WriteLine($"resutlt: {result}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using NUnit.Framework;
|
||||
using LeanCloud;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class FileTest {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
Utils.InitNorthChina();
|
||||
//Utils.InitEastChina();
|
||||
//Utils.InitUS();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveImage() {
|
||||
AVFile file = new AVFile("hello.png", File.ReadAllBytes("../../../assets/hello.png"));
|
||||
await file.SaveAsync();
|
||||
Assert.NotNull(file.ObjectId);
|
||||
TestContext.Out.WriteLine($"file: {file.ObjectId}, {file.Url}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveBigFile() {
|
||||
AVFile file = new AVFile("test.apk", File.ReadAllBytes("../../../assets/test.apk"));
|
||||
await file.SaveAsync();
|
||||
Assert.NotNull(file.ObjectId);
|
||||
TestContext.Out.WriteLine($"file: {file.ObjectId}, {file.Url}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveUrl() {
|
||||
AVFile file = new AVFile("test.jpg", "http://pic33.nipic.com/20131007/13639685_123501617185_2.jpg");
|
||||
await file.SaveAsync();
|
||||
Assert.NotNull(file.ObjectId);
|
||||
TestContext.Out.WriteLine($"file: {file.ObjectId}, {file.Url}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Thumbnail() {
|
||||
AVFile file = await AVFile.GetFileWithObjectIdAsync("5d64ac55d5de2b006c1fe3d8");
|
||||
Assert.NotNull(file);
|
||||
TestContext.Out.WriteLine($"url: {file.Url}");
|
||||
TestContext.Out.WriteLine($"thumbnail url: {file.GetThumbnailUrl(28, 28)}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeleteFile() {
|
||||
await AVUser.LogInAsync("111111", "111111");
|
||||
AVFile file = new AVFile("hello.png", File.ReadAllBytes("../../../assets/hello.png"));
|
||||
await file.SaveAsync();
|
||||
Assert.NotNull(file.ObjectId);
|
||||
await file.DeleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class JsonTest {
|
||||
[Test]
|
||||
public void Deserialize() {
|
||||
// 对象类型
|
||||
var obj = JsonConvert.DeserializeObject("{\"id\": 123}");
|
||||
TestContext.Out.WriteLine(obj.GetType());
|
||||
Assert.AreEqual(obj.GetType(), typeof(JObject));
|
||||
// 数组类型
|
||||
var arr = JsonConvert.DeserializeObject("[1, 2, 3]");
|
||||
TestContext.Out.WriteLine(arr.GetType());
|
||||
Assert.AreEqual(arr.GetType(), typeof(JArray));
|
||||
try {
|
||||
// null
|
||||
var na = JsonConvert.DeserializeObject(null);
|
||||
TestContext.Out.WriteLine(na.GetType());
|
||||
} catch (ArgumentNullException) {
|
||||
|
||||
}
|
||||
Assert.Pass();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializeDictionary() {
|
||||
//var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>("{\"id\": 123, \"nest\": { \"count\": 1 }}",
|
||||
// new DictionaryConverter());
|
||||
var json = "{\"id\": 123, \"nest\": { \"count\": 1 }, \"arr\": [1, 2, 3], \"na\": null}";
|
||||
TestContext.Out.WriteLine(JsonConvert.DeserializeObject(json).GetType());
|
||||
var obj = JsonConvert.DeserializeObject<object>(json, new LeanCloudJsonConverter());
|
||||
if (obj is IDictionary<string, object>) {
|
||||
var dict = obj as Dictionary<string, object>;
|
||||
TestContext.Out.WriteLine(dict.GetType());
|
||||
TestContext.Out.WriteLine(dict["id"]);
|
||||
TestContext.Out.WriteLine(dict["nest"].GetType());
|
||||
TestContext.Out.WriteLine(dict["arr"].GetType());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeserializeList() {
|
||||
var json = "[1, \"hello\", [2, 3, 4], { \"count\": 22 }]";
|
||||
TestContext.Out.WriteLine(JsonConvert.DeserializeObject(json).GetType());
|
||||
var obj = JsonConvert.DeserializeObject<IList<object>>(json, new LeanCloudJsonConverter());
|
||||
if (obj is IList<object>) {
|
||||
var arr = obj as List<object>;
|
||||
TestContext.Out.WriteLine(arr.GetType());
|
||||
TestContext.Out.WriteLine(arr[0]);
|
||||
TestContext.Out.WriteLine(arr[1].GetType());
|
||||
TestContext.Out.WriteLine(arr[2].GetType());
|
||||
TestContext.Out.WriteLine(arr[3].GetType());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
using NUnit.Framework;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class ObjectControllerTests {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
Utils.InitNorthChina();
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveWithOptions() {
|
||||
AVObject account = AVObject.CreateWithoutData("Account", "5d65fa5330863b008065e476");
|
||||
AVQuery<AVObject> query = new AVQuery<AVObject>("Account");
|
||||
query.WhereGreaterThan("balance", 80);
|
||||
account["balance"] = 50;
|
||||
await account.SaveAsync(true, query);
|
||||
TestContext.Out.WriteLine($"balance: {account["balance"]}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Fetch() {
|
||||
AVObject obj = AVObject.CreateWithoutData("Todo", "5d5f6039d5de2b006cf29c8f");
|
||||
await obj.FetchAsync();
|
||||
Assert.NotNull(obj["title"]);
|
||||
Assert.NotNull(obj["content"]);
|
||||
TestContext.Out.WriteLine($"{obj["title"]}, {obj["content"]}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FetchWithKeys() {
|
||||
AVObject obj = AVObject.CreateWithoutData("Post", "5d3abfa530863b0068e1b326");
|
||||
await obj.FetchAsync(new List<string> { "pubUser" });
|
||||
TestContext.Out.WriteLine($"{obj["pubUser"]}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FetchWithIncludes() {
|
||||
AVObject obj = AVObject.CreateWithoutData("Post", "5d3abfa530863b0068e1b326");
|
||||
await obj.FetchAsync(includes: new List<string> { "tag" });
|
||||
AVObject tag = obj["tag"] as AVObject;
|
||||
TestContext.Out.WriteLine($"{tag["name"]}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
using NUnit.Framework;
|
||||
using LeanCloud;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class ObjectTests {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
Utils.InitNorthChina();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAVObjectConstructor() {
|
||||
AVObject obj = new AVObject("Foo");
|
||||
Assert.AreEqual("Foo", obj.ClassName);
|
||||
Assert.Null(obj.CreatedAt);
|
||||
Assert.True(obj.IsDataAvailable);
|
||||
Assert.True(obj.IsDirty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAVObjectCreate() {
|
||||
AVObject obj = AVObject.CreateWithoutData("Foo", "5d356b1cd5de2b00837162ca");
|
||||
Assert.AreEqual("Foo", obj.ClassName);
|
||||
Assert.AreEqual("5d356b1cd5de2b00837162ca", obj.ObjectId);
|
||||
Assert.Null(obj.CreatedAt);
|
||||
Assert.False(obj.IsDataAvailable);
|
||||
Assert.False(obj.IsDirty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestHttp() {
|
||||
if (SynchronizationContext.Current == null) {
|
||||
TestContext.Out.WriteLine("is null");
|
||||
}
|
||||
TestContext.Out.WriteLine($"current {SynchronizationContext.Current}");
|
||||
var client = new HttpClient();
|
||||
TestContext.Out.WriteLine($"request at {Thread.CurrentThread.ManagedThreadId}");
|
||||
string url = $"{AVClient.CurrentConfiguration.RTMServer}/v1/route?appId={AVClient.CurrentConfiguration.ApplicationId}&secure=1";
|
||||
var res = await client.GetAsync(url);
|
||||
TestContext.Out.WriteLine($"get at {Thread.CurrentThread.ManagedThreadId}");
|
||||
var data = await res.Content.ReadAsStringAsync();
|
||||
res.Dispose();
|
||||
TestContext.Out.WriteLine($"response at {Thread.CurrentThread.ManagedThreadId}");
|
||||
TestContext.Out.WriteLine(data);
|
||||
Assert.Pass();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestMassiveRequest() {
|
||||
await Task.Run(() => {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 50; j++) {
|
||||
AVObject obj = AVObject.Create("Foo");
|
||||
obj.SaveAsync().ContinueWith(_ => {
|
||||
TestContext.Out.WriteLine($"{obj.ObjectId} saved");
|
||||
});
|
||||
}
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using LeanCloud;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task BasicQuery() {
|
||||
var query = new AVQuery<AVObject>("Foo");
|
||||
query.WhereEqualTo("content", "hello");
|
||||
var results = await query.FindAsync();
|
||||
foreach (var result in results) {
|
||||
TestContext.Out.WriteLine(result.ObjectId);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Count() {
|
||||
var query = new AVQuery<AVObject>("Foo");
|
||||
query.WhereEqualTo("content", "hello, world");
|
||||
var count = await query.CountAsync();
|
||||
Assert.Greater(count, 8);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Or() {
|
||||
AVQuery<AVObject> q1 = new AVQuery<AVObject>("Foo");
|
||||
q1.WhereEqualTo("content", "hello");
|
||||
AVQuery<AVObject> q2 = new AVQuery<AVObject>("Foo");
|
||||
q2.WhereEqualTo("content", "world");
|
||||
AVQuery<AVObject> query = AVQuery<AVObject>.Or(new List<AVQuery<AVObject>> { q1, q2 });
|
||||
IEnumerable<AVObject> results = await query.FindAsync();
|
||||
foreach (AVObject result in results) {
|
||||
TestContext.Out.WriteLine(result.ObjectId);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task And() {
|
||||
AVQuery<AVObject> q1 = new AVQuery<AVObject>("Foo");
|
||||
q1.WhereContains("content", "hello");
|
||||
AVQuery<AVObject> q2 = new AVQuery<AVObject>("Foo");
|
||||
q2.WhereContains("content", "world");
|
||||
AVQuery<AVObject> query = AVQuery<AVObject>.And(new List<AVQuery<AVObject>> { q1, q2 });
|
||||
IEnumerable<AVObject> results = await query.FindAsync();
|
||||
TestContext.Out.WriteLine($"Count: {results.Count()}");
|
||||
foreach (AVObject result in results) {
|
||||
TestContext.Out.WriteLine(result.ObjectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using NUnit.Framework;
|
||||
using LeanCloud;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class RelationTest {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
Utils.InitNorthChina();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateRelation() {
|
||||
AVObject tag1 = new AVObject("Tag") {
|
||||
["name"] = "hello"
|
||||
};
|
||||
await tag1.SaveAsync();
|
||||
AVObject tag2 = new AVObject("Tag") {
|
||||
{ "name", "world" }
|
||||
};
|
||||
await tag2.SaveAsync();
|
||||
AVObject todo = new AVObject("Todo");
|
||||
AVRelation<AVObject> relation = todo.GetRelation<AVObject>("tags");
|
||||
relation.Add(tag1);
|
||||
relation.Add(tag2);
|
||||
await todo.SaveAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task QueryRelation() {
|
||||
AVQuery<AVObject> query = new AVQuery<AVObject>("Todo");
|
||||
query.OrderByDescending("createdAt");
|
||||
AVObject todo = await query.FirstAsync();
|
||||
AVRelation<AVObject> relation = todo.GetRelation<AVObject>("tags");
|
||||
AVQuery<AVObject> tagQuery = relation.Query;
|
||||
IEnumerable<AVObject> tags = await tagQuery.FindAsync();
|
||||
Assert.Greater(tags.Count(), 0);
|
||||
TestContext.Out.WriteLine($"count: {tags.Count()}");
|
||||
foreach (AVObject tag in tags) {
|
||||
TestContext.Out.WriteLine($"{tag.ObjectId}, {tag["name"]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class RoleTest {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
Utils.InitNorthChina(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetUsersFromRole() {
|
||||
AVQuery<AVRole> query = new AVQuery<AVRole>();
|
||||
AVRole role = await query.FirstAsync();
|
||||
AVQuery<AVUser> userQuery = role.Users.Query;
|
||||
IEnumerable<AVUser> users = await userQuery.FindAsync();
|
||||
Assert.Greater(users.Count(), 0);
|
||||
TestContext.Out.WriteLine($"count: {users.Count()}");
|
||||
foreach (AVUser user in users) {
|
||||
TestContext.Out.WriteLine($"{user.ObjectId}, {user.Username}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\packages\NUnit.3.12.0\build\NUnit.props" Condition="Exists('..\..\packages\NUnit.3.12.0\build\NUnit.props')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{04DA35BB-6473-4D99-8A33-F499D40047E6}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>Storage.Test</RootNamespace>
|
||||
<AssemblyName>Storage.Test</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="nunit.framework">
|
||||
<HintPath>..\..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll</HintPath>
|
||||
</Reference>
|
||||
<PackageReference Include="nunit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Test.cs" />
|
||||
<ProjectReference Include="..\Storage\Storage.csproj" />
|
||||
<ProjectReference Include="..\..\AppRouter\AppRouter\AppRouter.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Storage.PCL\Storage.PCL.csproj">
|
||||
<Project>{659D19F0-9A40-42C0-886C-555E64F16848}</Project>
|
||||
<Name>Storage.PCL</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using LeanCloud;
|
||||
|
||||
namespace Storage.Test {
|
||||
[TestFixture()]
|
||||
public class Test {
|
||||
[Test()]
|
||||
public void TestCase() {
|
||||
Assembly assembly = Assembly.GetEntryAssembly();
|
||||
var attr = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||||
Console.WriteLine(attr.InformationalVersion);
|
||||
|
||||
FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||
String version = versionInfo.FileVersion;
|
||||
Console.WriteLine(version);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using LeanCloud;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class UserTest {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
AVClient.Initialize(new AVClient.Configuration {
|
||||
ApplicationId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz",
|
||||
ApplicationKey = "pbf6Nk5seyjilexdpyrPwjSp",
|
||||
ApiServer = "https://avoscloud.com"
|
||||
});
|
||||
AVClient.HttpLog(TestContext.Out.WriteLine);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Register() {
|
||||
AVUser user = new AVUser {
|
||||
Username = $"hello_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}",
|
||||
Password = "world"
|
||||
};
|
||||
await user.SignUpAsync();
|
||||
TestContext.Out.WriteLine($"{user.ObjectId} registered");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LoginWithUsername() {
|
||||
AVUser user = await AVUser.LogInAsync("hello", "111111");
|
||||
TestContext.Out.WriteLine($"{user.ObjectId}, {user.SessionToken} login");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LoginWithEmail() {
|
||||
AVUser user = await AVUser.LogInWithEmailAsync("111111@qq.com", "111111");
|
||||
Assert.AreEqual(user, AVUser.CurrentUser);
|
||||
TestContext.Out.WriteLine($"{AVUser.CurrentUser.SessionToken} login");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Become() {
|
||||
AVUser user = await AVUser.BecomeAsync("36idbfnt8hlmdo4rki0f5hevq");
|
||||
Assert.AreEqual(user, AVUser.CurrentUser);
|
||||
TestContext.Out.WriteLine($"{AVUser.CurrentUser.SessionToken} login");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task IsAuthenticated() {
|
||||
AVUser user = await AVUser.LogInWithEmailAsync("111111@qq.com", "111111");
|
||||
Assert.IsTrue(user.IsCurrent);
|
||||
Assert.AreEqual(user, AVUser.CurrentUser);
|
||||
bool authenticated = await user.IsAuthenticatedAsync();
|
||||
Assert.IsTrue(authenticated);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RefreshSessionToken() {
|
||||
AVUser user = await AVUser.LogInWithEmailAsync("111111@qq.com", "111111");
|
||||
Assert.IsTrue(user.IsCurrent);
|
||||
await user.RefreshSessionTokenAsync();
|
||||
TestContext.Out.WriteLine(user.SessionToken);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdatePassword() {
|
||||
AVUser user = await AVUser.LogInAsync("111111", "111111");
|
||||
await user.UpdatePasswordAsync("111111", "222222");
|
||||
await user.UpdatePasswordAsync("222222", "111111");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LoginWithAuthData() {
|
||||
AVUser user = await AVUser.LogInWithAuthDataAsync(new Dictionary<string, object> {
|
||||
{ "openid", "0395BA18A5CD6255E5BA185E7BEBA242" },
|
||||
{ "access_token", "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU" },
|
||||
{ "expires_in", 1382686496 }
|
||||
}, "qq");
|
||||
Assert.NotNull(user.SessionToken);
|
||||
TestContext.Out.WriteLine(user.SessionToken);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AssociateAuthData() {
|
||||
AVUser user = await AVUser.LogInAsync("111111", "111111");
|
||||
Assert.NotNull(user.SessionToken);
|
||||
await user.AssociateAuthDataAsync(new Dictionary<string, object> {
|
||||
{ "openid", "0395BA18A5CD6255E5BA185E7BEBA243" },
|
||||
{ "access_token", "12345678-SaMpLeTuo3m2avZxh5cjJmIrAfx4ZYyamdofM7IjU" },
|
||||
{ "expires_in", 1382686496 }
|
||||
}, "qq");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Anonymously() {
|
||||
AVUser user = await AVUser.LogInAnonymouslyAsync();
|
||||
Assert.NotNull(user.SessionToken);
|
||||
TestContext.Out.WriteLine(user.SessionToken);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetRoles() {
|
||||
AVUser user = await AVUser.LogInAsync("111111", "111111");
|
||||
Assert.NotNull(user.SessionToken);
|
||||
IEnumerable<AVRole> roles = await user.GetRolesAsync();
|
||||
Assert.Greater(roles.Count(), 0);
|
||||
foreach (AVRole role in roles) {
|
||||
TestContext.Out.WriteLine(role.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using LeanCloud;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public static class Utils {
|
||||
public static void InitNorthChina(bool master = false) {
|
||||
if (master) {
|
||||
Init("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz", "pbf6Nk5seyjilexdpyrPwjSp", "https://avoscloud.com", "https://avoscloud.com", "qKH9ryRagHKvXeRRVkiUiHeb");
|
||||
} else {
|
||||
Init("BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz", "pbf6Nk5seyjilexdpyrPwjSp", "https://avoscloud.com", "https://avoscloud.com");
|
||||
}
|
||||
}
|
||||
|
||||
public static void InitEastChina(bool master = false) {
|
||||
if (master) {
|
||||
Init("4eTwHdYhMaNBUpl1SrTr7GLC-9Nh9j0Va", "GSD6DtdgGWlWolivN4qhWtlE", "https://4eTwHdYh.api.lncldapi.com", "https://4eTwHdYh.engine.lncldapi.com", "eqEp4n89h4zanWFskDDpIwL4");
|
||||
} else {
|
||||
Init("4eTwHdYhMaNBUpl1SrTr7GLC-9Nh9j0Va", "GSD6DtdgGWlWolivN4qhWtlE", "https://4eTwHdYh.api.lncldapi.com", "https://4eTwHdYh.engine.lncldapi.com");
|
||||
}
|
||||
}
|
||||
|
||||
public static void InitUS(bool master = false) {
|
||||
if (master) {
|
||||
Init("MFAS1GnOyomRLSQYRaxdgdPz-MdYXbMMI", "p42JUxdxb95K5G8187t5ba3l", "https://MFAS1GnO.api.lncldglobal.com", "https://MFAS1GnO.engine.lncldglobal.com", "Ahb1wdFLwMgKwEaEicHRXbCY");
|
||||
} else {
|
||||
Init("MFAS1GnOyomRLSQYRaxdgdPz-MdYXbMMI", "p42JUxdxb95K5G8187t5ba3l", "https://MFAS1GnO.api.lncldglobal.com", "https://MFAS1GnO.engine.lncldglobal.com");
|
||||
}
|
||||
}
|
||||
|
||||
static void Init(string appId, string appKey, string apiServer, string engineServer, string masterKey = null) {
|
||||
AVClient.Initialize(new AVClient.Configuration {
|
||||
ApplicationId = appId,
|
||||
ApplicationKey = appKey,
|
||||
MasterKey = masterKey,
|
||||
ApiServer = apiServer,
|
||||
EngineServer = engineServer,
|
||||
});
|
||||
AVClient.UseMasterKey = !string.IsNullOrEmpty(masterKey);
|
||||
AVClient.HttpLog(TestContext.Out.WriteLine);
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NUnit" version="3.12.0" targetFramework="net47" />
|
||||
</packages>
|
|
@ -0,0 +1,184 @@
|
|||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVPlugins {
|
||||
private static readonly object instanceMutex = new object();
|
||||
private static AVPlugins instance;
|
||||
public static AVPlugins Instance {
|
||||
get {
|
||||
lock (instanceMutex) {
|
||||
instance = instance ?? new AVPlugins();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object mutex = new object();
|
||||
|
||||
#region Server Controllers
|
||||
|
||||
private AppRouterController appRouterController;
|
||||
private AVCommandRunner commandRunner;
|
||||
|
||||
private AVCloudCodeController cloudCodeController;
|
||||
private AVFileController fileController;
|
||||
private AVObjectController objectController;
|
||||
private AVQueryController queryController;
|
||||
private AVUserController userController;
|
||||
private ObjectSubclassingController subclassingController;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Current Instance Controller
|
||||
|
||||
private InstallationIdController installationIdController;
|
||||
|
||||
#endregion
|
||||
|
||||
public void Reset() {
|
||||
lock (mutex) {
|
||||
AppRouterController = null;
|
||||
CommandRunner = null;
|
||||
|
||||
CloudCodeController = null;
|
||||
FileController = null;
|
||||
ObjectController = null;
|
||||
UserController = null;
|
||||
SubclassingController = null;
|
||||
|
||||
InstallationIdController = null;
|
||||
}
|
||||
}
|
||||
|
||||
public AppRouterController AppRouterController {
|
||||
get {
|
||||
lock (mutex) {
|
||||
appRouterController = appRouterController ?? new AppRouterController();
|
||||
return appRouterController;
|
||||
}
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
appRouterController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AVCommandRunner CommandRunner {
|
||||
get {
|
||||
lock (mutex) {
|
||||
commandRunner = commandRunner ?? new AVCommandRunner();
|
||||
return commandRunner;
|
||||
}
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
commandRunner = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AVCloudCodeController CloudCodeController {
|
||||
get {
|
||||
lock (mutex) {
|
||||
cloudCodeController = cloudCodeController ?? new AVCloudCodeController();
|
||||
return cloudCodeController;
|
||||
}
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
cloudCodeController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AVFileController FileController {
|
||||
get {
|
||||
if (fileController != null) {
|
||||
return fileController;
|
||||
}
|
||||
fileController = new AVFileController();
|
||||
return fileController;
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
fileController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AVObjectController ObjectController {
|
||||
get {
|
||||
lock (mutex) {
|
||||
objectController = objectController ?? new AVObjectController();
|
||||
return objectController;
|
||||
}
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
objectController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AVQueryController QueryController {
|
||||
get {
|
||||
lock (mutex) {
|
||||
if (queryController == null) {
|
||||
queryController = new AVQueryController();
|
||||
}
|
||||
return queryController;
|
||||
}
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
queryController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AVUserController UserController {
|
||||
get {
|
||||
lock (mutex) {
|
||||
userController = userController ?? new AVUserController();
|
||||
return userController;
|
||||
}
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
userController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectSubclassingController SubclassingController {
|
||||
get {
|
||||
lock (mutex) {
|
||||
if (subclassingController == null) {
|
||||
subclassingController = new ObjectSubclassingController();
|
||||
//subclassingController.AddRegisterHook(typeof(AVUser), () => CurrentUserController.ClearFromMemory());
|
||||
}
|
||||
return subclassingController;
|
||||
}
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
subclassingController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InstallationIdController InstallationIdController {
|
||||
get {
|
||||
lock (mutex) {
|
||||
installationIdController = installationIdController ?? new InstallationIdController();
|
||||
return installationIdController;
|
||||
}
|
||||
}
|
||||
set {
|
||||
lock (mutex) {
|
||||
installationIdController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Utilities;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVCloudCodeController {
|
||||
public Task<T> CallFunctionAsync<T>(string name,
|
||||
IDictionary<string, object> parameters,
|
||||
CancellationToken cancellationToken) {
|
||||
var command = new EngineCommand {
|
||||
Path = $"functions/{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"]);
|
||||
});
|
||||
}
|
||||
|
||||
public 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"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVCommand {
|
||||
// 不同服务对应的服务器地址不同
|
||||
public virtual string Server => AVClient.CurrentConfiguration.ApiServer;
|
||||
|
||||
public string Path {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public HttpMethod Method {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Dictionary<string, string> Headers {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public object Content {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal Uri Uri {
|
||||
get {
|
||||
return new Uri($"{Server}/{AVClient.APIVersion}/{Path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
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;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Command Runner.
|
||||
/// </summary>
|
||||
public class AVCommandRunner {
|
||||
const string APPLICATION_JSON = "application/json";
|
||||
const string USE_PRODUCTION = "1";
|
||||
const string USE_DEVELOPMENT = "0";
|
||||
|
||||
private readonly HttpClient httpClient;
|
||||
|
||||
public AVCommandRunner() {
|
||||
httpClient = new HttpClient();
|
||||
ProductHeaderValue product = new ProductHeaderValue(AVClient.Name, AVClient.Version);
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(product));
|
||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(APPLICATION_JSON));
|
||||
|
||||
var conf = AVClient.CurrentConfiguration;
|
||||
// App ID
|
||||
httpClient.DefaultRequestHeaders.Add("X-LC-Id", conf.ApplicationId);
|
||||
// App Signature
|
||||
long timestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds;
|
||||
if (!string.IsNullOrEmpty(conf.MasterKey) && AVClient.UseMasterKey) {
|
||||
string sign = MD5.GetMd5String(timestamp + conf.MasterKey);
|
||||
httpClient.DefaultRequestHeaders.Add("X-LC-Sign", $"{sign},{timestamp},master");
|
||||
} else {
|
||||
string sign = MD5.GetMd5String(timestamp + conf.ApplicationKey);
|
||||
httpClient.DefaultRequestHeaders.Add("X-LC-Sign", $"{sign},{timestamp}");
|
||||
}
|
||||
// TODO Session
|
||||
|
||||
// Production
|
||||
httpClient.DefaultRequestHeaders.Add("X-LC-Prod", AVClient.UseProduction ? USE_PRODUCTION : USE_DEVELOPMENT);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Tuple<HttpStatusCode, T>> RunCommandAsync<T>(AVCommand command,CancellationToken cancellationToken = default) {
|
||||
string content = JsonConvert.SerializeObject(command.Content);
|
||||
var request = new HttpRequestMessage {
|
||||
RequestUri = command.Uri,
|
||||
Method = command.Method,
|
||||
Content = new StringContent(content)
|
||||
};
|
||||
|
||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue(APPLICATION_JSON);
|
||||
// 特殊 Headers
|
||||
if (command.Headers != null) {
|
||||
foreach (KeyValuePair<string, string> header in command.Headers) {
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
}
|
||||
// Session Token
|
||||
if (AVUser.CurrentUser != null && !string.IsNullOrEmpty(AVUser.CurrentUser.SessionToken)) {
|
||||
request.Headers.Add("X-LC-Session", AVUser.CurrentUser.SessionToken);
|
||||
}
|
||||
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);
|
||||
|
||||
var ret = new Tuple<HttpStatusCode, string>(response.StatusCode, resultString);
|
||||
|
||||
var responseCode = ret.Item1;
|
||||
var contentString = ret.Item2;
|
||||
|
||||
if (responseCode >= HttpStatusCode.InternalServerError) {
|
||||
// Server error, return InternalServerError.
|
||||
throw new AVException(AVException.ErrorCode.InternalServerError, contentString);
|
||||
}
|
||||
|
||||
if (responseCode < HttpStatusCode.OK || responseCode > HttpStatusCode.PartialContent) {
|
||||
// 错误处理
|
||||
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(contentString, new LeanCloudJsonConverter());
|
||||
if (data.TryGetValue("code", out object codeObj)) {
|
||||
AVException.ErrorCode code = (AVException.ErrorCode)Enum.ToObject(typeof(AVException.ErrorCode), codeObj);
|
||||
string detail = data["error"] as string;
|
||||
throw new AVException(code, detail);
|
||||
} else {
|
||||
throw new AVException(AVException.ErrorCode.OtherCause, contentString);
|
||||
}
|
||||
}
|
||||
|
||||
if (contentString != null) {
|
||||
try {
|
||||
var data = JsonConvert.DeserializeObject<object>(contentString, new LeanCloudJsonConverter());
|
||||
return new Tuple<HttpStatusCode, T>(responseCode, (T)data);
|
||||
} catch (Exception e) {
|
||||
throw new AVException(AVException.ErrorCode.OtherCause,
|
||||
"Invalid response from server", e);
|
||||
}
|
||||
}
|
||||
|
||||
return new Tuple<HttpStatusCode, T>(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())}");
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class EngineCommand : AVCommand {
|
||||
public override string Server => AVClient.CurrentConfiguration.EngineServer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
using LeanCloud;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class RTMCommand : AVCommand {
|
||||
public override string Server => AVClient.CurrentConfiguration.RTMServer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVDecoder
|
||||
{
|
||||
// This class isn't really a Singleton, but since it has no state, it's more efficient to get
|
||||
// the default instance.
|
||||
private static readonly AVDecoder instance = new AVDecoder();
|
||||
public static AVDecoder Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent default constructor.
|
||||
private AVDecoder() { }
|
||||
|
||||
public object Decode(object data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dict = data as IDictionary<string, object>;
|
||||
if (dict != null)
|
||||
{
|
||||
if (dict.ContainsKey("__op"))
|
||||
{
|
||||
return AVFieldOperations.Decode(dict);
|
||||
}
|
||||
|
||||
object type;
|
||||
dict.TryGetValue("__type", out type);
|
||||
var typeString = type as string;
|
||||
|
||||
if (typeString == null)
|
||||
{
|
||||
var newDict = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
newDict[pair.Key] = Decode(pair.Value);
|
||||
}
|
||||
return newDict;
|
||||
}
|
||||
|
||||
if (typeString == "Date")
|
||||
{
|
||||
return ParseDate(dict["iso"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "Bytes")
|
||||
{
|
||||
return Convert.FromBase64String(dict["base64"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "Pointer")
|
||||
{
|
||||
//set a include key to fetch or query.
|
||||
if (dict.Keys.Count > 3)
|
||||
{
|
||||
return DecodeAVObject(dict);
|
||||
}
|
||||
return DecodePointer(dict["className"] as string, dict["objectId"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "File")
|
||||
{
|
||||
return DecodeAVFile(dict);
|
||||
}
|
||||
|
||||
if (typeString == "GeoPoint")
|
||||
{
|
||||
return new AVGeoPoint(Conversion.To<double>(dict["latitude"]),
|
||||
Conversion.To<double>(dict["longitude"]));
|
||||
}
|
||||
|
||||
if (typeString == "Object")
|
||||
{
|
||||
return DecodeAVObject(dict);
|
||||
}
|
||||
|
||||
if (typeString == "Relation")
|
||||
{
|
||||
return AVRelationBase.CreateRelation(null, null, dict["className"] as string);
|
||||
}
|
||||
|
||||
var converted = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
converted[pair.Key] = Decode(pair.Value);
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
var list = data as IList<object>;
|
||||
if (list != null)
|
||||
{
|
||||
return (from item in list
|
||||
select Decode(item)).ToList();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
protected virtual object DecodePointer(string className, string objectId)
|
||||
{
|
||||
if (className == "_File")
|
||||
{
|
||||
return AVFile.CreateWithoutData(objectId);
|
||||
}
|
||||
return AVObject.CreateWithoutData(className, objectId);
|
||||
}
|
||||
protected virtual object DecodeAVObject(IDictionary<string, object> dict)
|
||||
{
|
||||
var className = dict["className"] as string;
|
||||
if (className == "_File")
|
||||
{
|
||||
return DecodeAVFile(dict);
|
||||
}
|
||||
var state = AVObjectCoder.Instance.Decode(dict, this);
|
||||
return AVObject.FromState<AVObject>(state, dict["className"] as string);
|
||||
}
|
||||
protected virtual object DecodeAVFile(IDictionary<string, object> dict)
|
||||
{
|
||||
var objectId = dict["objectId"] as string;
|
||||
var file = AVFile.CreateWithoutData(objectId);
|
||||
file.MergeFromJSON(dict);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
public virtual IList<T> DecodeList<T>(object data)
|
||||
{
|
||||
IList<T> rtn = null;
|
||||
var list = (IList<object>)data;
|
||||
if (list != null)
|
||||
{
|
||||
rtn = new List<T>();
|
||||
foreach (var item in list)
|
||||
{
|
||||
rtn.Add((T)item);
|
||||
}
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public static DateTime ParseDate(string input)
|
||||
{
|
||||
var rtn = DateTime.ParseExact(input,
|
||||
AVClient.DateFormatStrings,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeUniversal);
|
||||
return rtn;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A <c>AVEncoder</c> can be used to transform objects such as <see cref="AVObject"/> into JSON
|
||||
/// data structures.
|
||||
/// </summary>
|
||||
/// <seealso cref="AVDecoder"/>
|
||||
public abstract class AVEncoder
|
||||
{
|
||||
#if UNITY
|
||||
private static readonly bool isCompiledByIL2CPP = AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain");
|
||||
#else
|
||||
private static readonly bool isCompiledByIL2CPP = false;
|
||||
#endif
|
||||
|
||||
public static bool IsValidType(object value)
|
||||
{
|
||||
return value == null ||
|
||||
ReflectionHelpers.IsPrimitive(value.GetType()) ||
|
||||
value is string ||
|
||||
value is AVObject ||
|
||||
value is AVACL ||
|
||||
value is AVFile ||
|
||||
value is AVGeoPoint ||
|
||||
value is AVRelationBase ||
|
||||
value is DateTime ||
|
||||
value is byte[] ||
|
||||
Conversion.As<IDictionary<string, object>>(value) != null ||
|
||||
Conversion.As<IList<object>>(value) != null;
|
||||
}
|
||||
|
||||
public object Encode(object value)
|
||||
{
|
||||
// If this object has a special encoding, encode it and return the
|
||||
// encoded object. Otherwise, just return the original object.
|
||||
if (value is DateTime)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"iso", ((DateTime)value).ToUniversalTime().ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture)
|
||||
},
|
||||
{
|
||||
"__type", "Date"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (value is AVFile)
|
||||
{
|
||||
var file = value as AVFile;
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{"__type", "Pointer"},
|
||||
{ "className", "_File"},
|
||||
{ "objectId", file.ObjectId}
|
||||
};
|
||||
}
|
||||
|
||||
var bytes = value as byte[];
|
||||
if (bytes != null)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "__type", "Bytes"},
|
||||
{ "base64", Convert.ToBase64String(bytes)}
|
||||
};
|
||||
}
|
||||
|
||||
var obj = value as AVObject;
|
||||
if (obj != null)
|
||||
{
|
||||
return EncodeAVObject(obj);
|
||||
}
|
||||
|
||||
var jsonConvertible = value as IJsonConvertible;
|
||||
if (jsonConvertible != null)
|
||||
{
|
||||
return jsonConvertible.ToJSON();
|
||||
}
|
||||
|
||||
var dict = Conversion.As<IDictionary<string, object>>(value);
|
||||
if (dict != null)
|
||||
{
|
||||
var json = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
json[pair.Key] = Encode(pair.Value);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
var list = Conversion.As<IList<object>>(value);
|
||||
if (list != null)
|
||||
{
|
||||
return EncodeList(list);
|
||||
}
|
||||
|
||||
// TODO (hallucinogen): convert IAVFieldOperation to IJsonConvertible
|
||||
var operation = value as IAVFieldOperation;
|
||||
if (operation != null)
|
||||
{
|
||||
return operation.Encode();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected abstract IDictionary<string, object> EncodeAVObject(AVObject value);
|
||||
|
||||
private object EncodeList(IList<object> list)
|
||||
{
|
||||
var newArray = new List<object>();
|
||||
// We need to explicitly cast `list` to `List<object>` rather than
|
||||
// `IList<object>` because IL2CPP is stricter than the usual Unity AOT compiler pipeline.
|
||||
if (isCompiledByIL2CPP && list.GetType().IsArray)
|
||||
{
|
||||
list = new List<object>(list);
|
||||
}
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (!IsValidType(item))
|
||||
{
|
||||
throw new ArgumentException("Invalid type for value in an array");
|
||||
}
|
||||
newArray.Add(Encode(item));
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
// TODO: (richardross) refactor entire LeanCloud coder interfaces.
|
||||
public class AVObjectCoder
|
||||
{
|
||||
private static readonly AVObjectCoder instance = new AVObjectCoder();
|
||||
public static AVObjectCoder Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent default constructor.
|
||||
private AVObjectCoder() { }
|
||||
|
||||
public IDictionary<string, object> Encode<T>(T state,
|
||||
IDictionary<string, IAVFieldOperation> operations,
|
||||
AVEncoder encoder) where T : IObjectState
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
foreach (var pair in operations)
|
||||
{
|
||||
// AVRPCSerialize the data
|
||||
var operation = pair.Value;
|
||||
|
||||
result[pair.Key] = encoder.Encode(operation);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IObjectState Decode(IDictionary<string, object> data,
|
||||
AVDecoder decoder)
|
||||
{
|
||||
IDictionary<string, object> serverData = new Dictionary<string, object>();
|
||||
var mutableData = new Dictionary<string, object>(data);
|
||||
string objectId = extractFromDictionary<string>(mutableData, "objectId", (obj) =>
|
||||
{
|
||||
return obj as string;
|
||||
});
|
||||
DateTime? createdAt = extractFromDictionary<DateTime?>(mutableData, "createdAt", (obj) =>
|
||||
{
|
||||
return (DateTime)obj;
|
||||
});
|
||||
DateTime? updatedAt = extractFromDictionary<DateTime?>(mutableData, "updatedAt", (obj) =>
|
||||
{
|
||||
return (DateTime)obj;
|
||||
});
|
||||
|
||||
if (mutableData.ContainsKey("ACL"))
|
||||
{
|
||||
serverData["ACL"] = extractFromDictionary<AVACL>(mutableData, "ACL", (obj) =>
|
||||
{
|
||||
return new AVACL(obj as IDictionary<string, object>);
|
||||
});
|
||||
}
|
||||
string className = extractFromDictionary<string>(mutableData, "className", obj =>
|
||||
{
|
||||
return obj as string;
|
||||
});
|
||||
if (createdAt != null && updatedAt == null)
|
||||
{
|
||||
updatedAt = createdAt;
|
||||
}
|
||||
|
||||
// Bring in the new server data.
|
||||
foreach (var pair in mutableData)
|
||||
{
|
||||
if (pair.Key == "__type" || pair.Key == "className")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = pair.Value;
|
||||
serverData[pair.Key] = decoder.Decode(value);
|
||||
}
|
||||
|
||||
return new MutableObjectState
|
||||
{
|
||||
ObjectId = objectId,
|
||||
CreatedAt = createdAt,
|
||||
UpdatedAt = updatedAt,
|
||||
ServerData = serverData,
|
||||
ClassName = className
|
||||
};
|
||||
}
|
||||
|
||||
private T extractFromDictionary<T>(IDictionary<string, object> data, string key, Func<object, T> action)
|
||||
{
|
||||
T result = default(T);
|
||||
if (data.ContainsKey(key))
|
||||
{
|
||||
result = action(data[key]);
|
||||
data.Remove(key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// A <see cref="AVEncoder"/> that throws an exception if it attempts to encode
|
||||
/// a <see cref="AVObject"/>
|
||||
/// </summary>
|
||||
public class NoObjectsEncoder : AVEncoder {
|
||||
// This class isn't really a Singleton, but since it has no state, it's more efficient to get
|
||||
// the default instance.
|
||||
private static readonly NoObjectsEncoder instance = new NoObjectsEncoder();
|
||||
public static NoObjectsEncoder Instance {
|
||||
get {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
protected override IDictionary<string, object> EncodeAVObject(AVObject value) {
|
||||
throw new ArgumentException("AVObjects not allowed here.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="AVEncoder"/> that encode <see cref="AVObject"/> as pointers. If the object
|
||||
/// does not have an <see cref="AVObject.ObjectId"/>, uses a local id.
|
||||
/// </summary>
|
||||
public class PointerOrLocalIdEncoder : AVEncoder
|
||||
{
|
||||
// This class isn't really a Singleton, but since it has no state, it's more efficient to get
|
||||
// the default instance.
|
||||
private static readonly PointerOrLocalIdEncoder instance = new PointerOrLocalIdEncoder();
|
||||
public static PointerOrLocalIdEncoder Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
protected override IDictionary<string, object> EncodeAVObject(AVObject value)
|
||||
{
|
||||
if (value.ObjectId == null)
|
||||
{
|
||||
// TODO (hallucinogen): handle local id. For now we throw.
|
||||
throw new ArgumentException("Cannot create a pointer to an object without an objectId");
|
||||
}
|
||||
|
||||
return new Dictionary<string, object> {
|
||||
{"__type", "Pointer"},
|
||||
{ "className", value.ClassName},
|
||||
{ "objectId", value.ObjectId}
|
||||
};
|
||||
}
|
||||
|
||||
public IDictionary<string, object> EncodeAVObject(AVObject value, bool isPointer)
|
||||
{
|
||||
if (isPointer)
|
||||
{
|
||||
return EncodeAVObject(value);
|
||||
}
|
||||
var operations = value.GetCurrentOperations();
|
||||
var operationJSON = AVObject.ToJSONObjectForSaving(operations);
|
||||
var objectJSON = value.ToDictionary(kvp => kvp.Key, kvp => PointerOrLocalIdEncoder.Instance.Encode(kvp.Value));
|
||||
foreach (var kvp in operationJSON)
|
||||
{
|
||||
objectJSON[kvp.Key] = kvp.Value;
|
||||
}
|
||||
if (value.CreatedAt.HasValue)
|
||||
{
|
||||
objectJSON["createdAt"] = value.CreatedAt.Value.ToString(AVClient.DateFormatStrings.First(),
|
||||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
if (value.UpdatedAt.HasValue)
|
||||
{
|
||||
objectJSON["updatedAt"] = value.UpdatedAt.Value.ToString(AVClient.DateFormatStrings.First(),
|
||||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
if(!string.IsNullOrEmpty(value.ObjectId))
|
||||
{
|
||||
objectJSON["objectId"] = value.ObjectId;
|
||||
}
|
||||
objectJSON["className"] = value.ClassName;
|
||||
objectJSON["__type"] = "Object";
|
||||
return objectJSON;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IFileUploader {
|
||||
Task<FileState> Upload(FileState state, Stream dataStream, IDictionary<string, object> fileToken, IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public class AVFileController {
|
||||
const string QCloud = "qcloud";
|
||||
const string AWS = "s3";
|
||||
|
||||
public Task<FileState> SaveAsync(FileState state,
|
||||
Stream dataStream,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken = default) {
|
||||
if (state.Url != null) {
|
||||
return 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();
|
||||
}
|
||||
|
||||
public 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);
|
||||
}
|
||||
|
||||
internal Task<FileState> SaveWithUrl(FileState state) {
|
||||
Dictionary<string, object> strs = new Dictionary<string, object> {
|
||||
{ "url", state.Url.ToString() },
|
||||
{ "name", state.Name },
|
||||
{ "mime_type", state.MimeType },
|
||||
{ "metaData", state.MetaData }
|
||||
};
|
||||
AVCommand cmd = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(state.ObjectId)) {
|
||||
cmd = new AVCommand {
|
||||
Path = $"files/{state.ObjectId}",
|
||||
Method = HttpMethod.Put,
|
||||
Content = strs
|
||||
};
|
||||
} else {
|
||||
cmd = new AVCommand {
|
||||
Path = "files",
|
||||
Method = HttpMethod.Post,
|
||||
Content = strs
|
||||
};
|
||||
}
|
||||
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(cmd).OnSuccess(t => {
|
||||
var result = t.Result.Item2;
|
||||
state.ObjectId = result["objectId"].ToString();
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
internal 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 },
|
||||
{ "key", GetUniqueName(fileState) },
|
||||
{ "__type", "File" },
|
||||
{ "mime_type", AVFile.GetMIMEType(str) },
|
||||
{ "metaData", fileState.MetaData }
|
||||
};
|
||||
|
||||
var command = new AVCommand {
|
||||
Path = "fileTokens",
|
||||
Method = HttpMethod.Post,
|
||||
Content = parameters
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
}
|
||||
|
||||
public 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),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
internal static string GetUniqueName(FileState state) {
|
||||
string key = Guid.NewGuid().ToString();
|
||||
string extension = Path.GetExtension(state.Name);
|
||||
key += extension;
|
||||
return key;
|
||||
}
|
||||
|
||||
internal static string Random(int length) {
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
var random = new Random();
|
||||
return new string(Enumerable.Repeat(chars, length)
|
||||
.Select(s => s[random.Next(s.Length)]).ToArray());
|
||||
}
|
||||
|
||||
internal static double CalcProgress(double already, double total) {
|
||||
var pv = (1.0 * already / total);
|
||||
return Math.Round(pv, 3);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
internal class AWSUploader : IFileUploader {
|
||||
public Task<FileState> Upload(FileState state, Stream dataStream, IDictionary<string, object> fileToken, IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken) {
|
||||
var uploadUrl = fileToken["upload_url"].ToString();
|
||||
state.ObjectId = fileToken["objectId"].ToString();
|
||||
string url = fileToken["url"] as string;
|
||||
state.Url = new Uri(url, UriKind.Absolute);
|
||||
return PutFile(state, uploadUrl, dataStream).OnSuccess(s => {
|
||||
return s.Result;
|
||||
});
|
||||
}
|
||||
|
||||
internal async Task<FileState> PutFile(FileState state, string uploadUrl, Stream dataStream) {
|
||||
HttpClient client = new HttpClient();
|
||||
HttpRequestMessage request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(uploadUrl),
|
||||
Method = HttpMethod.Put,
|
||||
Content = new StreamContent(dataStream)
|
||||
};
|
||||
request.Headers.Add("Cache-Control", "public, max-age=31536000");
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(state.MimeType);
|
||||
request.Content.Headers.ContentLength = dataStream.Length;
|
||||
await client.SendAsync(request);
|
||||
client.Dispose();
|
||||
request.Dispose();
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
internal class QCloudUploader : IFileUploader {
|
||||
private object mutex = new object();
|
||||
|
||||
FileState fileState;
|
||||
Stream data;
|
||||
string bucket;
|
||||
string token;
|
||||
string uploadUrl;
|
||||
bool done;
|
||||
private long sliceSize = (long)CommonSize.KB512;
|
||||
|
||||
public Task<FileState> Upload(FileState state, Stream dataStream, IDictionary<string, object> fileToken, IProgress<AVUploadProgressEventArgs> progress, CancellationToken cancellationToken) {
|
||||
fileState = state;
|
||||
data = dataStream;
|
||||
uploadUrl = fileToken["upload_url"].ToString();
|
||||
token = fileToken["token"].ToString();
|
||||
fileState.ObjectId = fileToken["objectId"].ToString();
|
||||
bucket = fileToken["bucket"].ToString();
|
||||
|
||||
return FileSlice(cancellationToken).OnSuccess(t => {
|
||||
if (done) return Task<FileState>.FromResult(state);
|
||||
var response = t.Result.Item2;
|
||||
var resumeData = response["data"] as IDictionary<string, object>;
|
||||
if (resumeData.ContainsKey("access_url")) return Task<FileState>.FromResult(state);
|
||||
var sliceSession = resumeData["session"].ToString();
|
||||
var sliceOffset = long.Parse(resumeData["offset"].ToString());
|
||||
return UploadSlice(sliceSession, sliceOffset, dataStream, progress, cancellationToken);
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
Task<FileState> UploadSlice(
|
||||
string sessionId,
|
||||
long offset,
|
||||
Stream dataStream,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken) {
|
||||
|
||||
long dataLength = dataStream.Length;
|
||||
if (progress != null) {
|
||||
lock (mutex) {
|
||||
progress.Report(new AVUploadProgressEventArgs() {
|
||||
Progress = AVFileController.CalcProgress(offset, dataLength)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (offset == dataLength) {
|
||||
return Task.FromResult<FileState>(fileState);
|
||||
}
|
||||
|
||||
var sliceFile = GetNextBinary(offset, dataStream);
|
||||
return ExcuteUpload(sessionId, offset, sliceFile, cancellationToken).OnSuccess(_ => {
|
||||
offset += sliceFile.Length;
|
||||
if (offset == dataLength) {
|
||||
done = true;
|
||||
return Task.FromResult<FileState>(fileState);
|
||||
}
|
||||
var response = _.Result.Item2;
|
||||
var resumeData = response["data"] as IDictionary<string, object>;
|
||||
var sliceSession = resumeData["session"].ToString();
|
||||
return UploadSlice(sliceSession, offset, dataStream, progress, cancellationToken);
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> ExcuteUpload(string sessionId, long offset, byte[] sliceFile, CancellationToken cancellationToken) {
|
||||
var body = new Dictionary<string, object>();
|
||||
body.Add("op", "upload_slice");
|
||||
body.Add("session", sessionId);
|
||||
body.Add("offset", offset.ToString());
|
||||
|
||||
return PostToQCloud(body, sliceFile, cancellationToken);
|
||||
}
|
||||
|
||||
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> FileSlice(CancellationToken cancellationToken) {
|
||||
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
|
||||
var body = new Dictionary<string, object>();
|
||||
if (data.Length <= (long)CommonSize.KB512) {
|
||||
body.Add("op", "upload");
|
||||
body.Add("sha", HexStringFromBytes(sha1.ComputeHash(data)));
|
||||
var wholeFile = GetNextBinary(0, data);
|
||||
return PostToQCloud(body, wholeFile, cancellationToken).OnSuccess(_ => {
|
||||
if (_.Result.Item1 == HttpStatusCode.OK) {
|
||||
done = true;
|
||||
}
|
||||
return _.Result;
|
||||
});
|
||||
} else {
|
||||
body.Add("op", "upload_slice");
|
||||
body.Add("filesize", data.Length);
|
||||
body.Add("sha", HexStringFromBytes(sha1.ComputeHash(data)));
|
||||
body.Add("slice_size", (long)CommonSize.KB512);
|
||||
}
|
||||
|
||||
return PostToQCloud(body, null, cancellationToken);
|
||||
}
|
||||
public static string HexStringFromBytes(byte[] bytes) {
|
||||
var sb = new StringBuilder();
|
||||
foreach (byte b in bytes) {
|
||||
var hex = b.ToString("x2");
|
||||
sb.Append(hex);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string SHA1HashStringForUTF8String(string s) {
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(s);
|
||||
|
||||
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
|
||||
byte[] hashBytes = sha1.ComputeHash(bytes);
|
||||
|
||||
return HexStringFromBytes(hashBytes);
|
||||
}
|
||||
|
||||
async Task<Tuple<HttpStatusCode, IDictionary<string, object>>> PostToQCloud(
|
||||
Dictionary<string, object> body,
|
||||
byte[] sliceFile,
|
||||
CancellationToken cancellationToken) {
|
||||
IList<KeyValuePair<string, string>> sliceHeaders = new List<KeyValuePair<string, string>>();
|
||||
sliceHeaders.Add(new KeyValuePair<string, string>("Authorization", this.token));
|
||||
|
||||
string contentType;
|
||||
long contentLength;
|
||||
|
||||
var tempStream = HttpUploadFile(sliceFile, fileState.CloudName, out contentType, out contentLength, body);
|
||||
|
||||
sliceHeaders.Add(new KeyValuePair<string, string>("Content-Type", contentType));
|
||||
|
||||
var client = new HttpClient();
|
||||
var request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(uploadUrl),
|
||||
Method = HttpMethod.Post,
|
||||
Content = new StreamContent(tempStream)
|
||||
};
|
||||
foreach (var header in sliceHeaders) {
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
var response = await client.SendAsync(request);
|
||||
client.Dispose();
|
||||
request.Dispose();
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
// TODO 修改反序列化返回
|
||||
return await JsonUtils.DeserializeObjectAsync<Tuple<HttpStatusCode, IDictionary<string, object>>>(content);
|
||||
}
|
||||
public static Stream HttpUploadFile(byte[] file, string fileName, out string contentType, out long contentLength, IDictionary<string, object> nvc) {
|
||||
string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
|
||||
byte[] boundarybytes = StringToAscii("\r\n--" + boundary + "\r\n");
|
||||
contentType = "multipart/form-data; boundary=" + boundary;
|
||||
|
||||
MemoryStream rs = new MemoryStream();
|
||||
|
||||
string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
|
||||
foreach (string key in nvc.Keys) {
|
||||
rs.Write(boundarybytes, 0, boundarybytes.Length);
|
||||
string formitem = string.Format(formdataTemplate, key, nvc[key]);
|
||||
byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(formitem);
|
||||
rs.Write(formitembytes, 0, formitembytes.Length);
|
||||
}
|
||||
rs.Write(boundarybytes, 0, boundarybytes.Length);
|
||||
|
||||
if (file != null) {
|
||||
string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
|
||||
string header = string.Format(headerTemplate, "fileContent", fileName, "application/octet-stream");
|
||||
byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
|
||||
rs.Write(headerbytes, 0, headerbytes.Length);
|
||||
|
||||
rs.Write(file, 0, file.Length);
|
||||
}
|
||||
|
||||
byte[] trailer = StringToAscii("\r\n--" + boundary + "--\r\n");
|
||||
rs.Write(trailer, 0, trailer.Length);
|
||||
contentLength = rs.Length;
|
||||
|
||||
rs.Position = 0;
|
||||
var tempBuffer = new byte[rs.Length];
|
||||
rs.Read(tempBuffer, 0, tempBuffer.Length);
|
||||
|
||||
return new MemoryStream(tempBuffer);
|
||||
}
|
||||
|
||||
public static byte[] StringToAscii(string s) {
|
||||
byte[] retval = new byte[s.Length];
|
||||
for (int ix = 0; ix < s.Length; ++ix) {
|
||||
char ch = s[ix];
|
||||
if (ch <= 0x7f) retval[ix] = (byte)ch;
|
||||
else retval[ix] = (byte)'?';
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
byte[] GetNextBinary(long completed, Stream dataStream) {
|
||||
if (completed + sliceSize > dataStream.Length) {
|
||||
sliceSize = dataStream.Length - completed;
|
||||
}
|
||||
|
||||
byte[] chunkBinary = new byte[sliceSize];
|
||||
dataStream.Seek(completed, SeekOrigin.Begin);
|
||||
dataStream.Read(chunkBinary, 0, (int)sliceSize);
|
||||
return chunkBinary;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
internal enum CommonSize : long {
|
||||
MB4 = 1024 * 1024 * 4,
|
||||
MB1 = 1024 * 1024,
|
||||
KB512 = 1024 * 1024 / 2,
|
||||
KB256 = 1024 * 1024 / 4
|
||||
}
|
||||
|
||||
internal class QiniuUploader : IFileUploader {
|
||||
private static readonly int BLOCKSIZE = 1024 * 1024 * 4;
|
||||
internal static string UP_HOST = "https://up.qbox.me";
|
||||
private readonly object mutex = new object();
|
||||
|
||||
public Task<FileState> Upload(FileState state, Stream dataStream, IDictionary<string, object> fileToken, IProgress<AVUploadProgressEventArgs> progress, CancellationToken cancellationToken) {
|
||||
state.frozenData = dataStream;
|
||||
state.CloudName = fileToken["key"] as string;
|
||||
MergeFromJSON(state, fileToken);
|
||||
return UploadNextChunk(state, dataStream, string.Empty, 0, progress).OnSuccess(_ => {
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
Task UploadNextChunk(FileState state, Stream dataStream, string context, long offset, IProgress<AVUploadProgressEventArgs> progress) {
|
||||
var totalSize = dataStream.Length;
|
||||
var remainingSize = totalSize - state.completed;
|
||||
|
||||
if (progress != null) {
|
||||
lock (mutex) {
|
||||
progress.Report(new AVUploadProgressEventArgs() {
|
||||
Progress = AVFileController.CalcProgress(state.completed, totalSize)
|
||||
});
|
||||
}
|
||||
}
|
||||
if (state.completed == totalSize) {
|
||||
return QiniuMakeFile(state, state.frozenData, state.token, state.CloudName, totalSize, state.block_ctxes.ToArray(), CancellationToken.None);
|
||||
}
|
||||
if (state.completed % BLOCKSIZE == 0) {
|
||||
var firstChunkBinary = GetChunkBinary(state.completed, dataStream);
|
||||
|
||||
var blockSize = remainingSize > BLOCKSIZE ? BLOCKSIZE : remainingSize;
|
||||
return MakeBlock(state, firstChunkBinary, blockSize).OnSuccess(t => {
|
||||
var dict = t.Result;
|
||||
var ctx = dict["ctx"].ToString();
|
||||
offset = long.Parse(dict["offset"].ToString());
|
||||
var host = dict["host"].ToString();
|
||||
|
||||
state.completed += firstChunkBinary.Length;
|
||||
if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize) {
|
||||
state.block_ctxes.Add(ctx);
|
||||
}
|
||||
|
||||
return UploadNextChunk(state, dataStream, ctx, offset, progress);
|
||||
}).Unwrap();
|
||||
}
|
||||
var chunkBinary = GetChunkBinary(state.completed, dataStream);
|
||||
return PutChunk(state, chunkBinary, context, offset).OnSuccess(t => {
|
||||
var dict = t.Result;
|
||||
var ctx = dict["ctx"].ToString();
|
||||
|
||||
offset = long.Parse(dict["offset"].ToString());
|
||||
var host = dict["host"].ToString();
|
||||
state.completed += chunkBinary.Length;
|
||||
if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize) {
|
||||
state.block_ctxes.Add(ctx);
|
||||
}
|
||||
|
||||
return UploadNextChunk(state, dataStream, ctx, offset, progress);
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
byte[] GetChunkBinary(long completed, Stream dataStream) {
|
||||
long chunkSize = (long)CommonSize.MB1;
|
||||
if (completed + chunkSize > dataStream.Length) {
|
||||
chunkSize = dataStream.Length - completed;
|
||||
}
|
||||
byte[] chunkBinary = new byte[chunkSize];
|
||||
dataStream.Seek(completed, SeekOrigin.Begin);
|
||||
dataStream.Read(chunkBinary, 0, (int)chunkSize);
|
||||
return chunkBinary;
|
||||
}
|
||||
|
||||
IList<KeyValuePair<string, string>> GetQiniuRequestHeaders(FileState state) {
|
||||
IList<KeyValuePair<string, string>> makeBlockHeaders = new List<KeyValuePair<string, string>>();
|
||||
string authHead = "UpToken " + state.token;
|
||||
makeBlockHeaders.Add(new KeyValuePair<string, string>("Authorization", authHead));
|
||||
return makeBlockHeaders;
|
||||
}
|
||||
|
||||
async Task<Dictionary<string, object>> MakeBlock(FileState state, byte[] firstChunkBinary, long blcokSize = 4194304) {
|
||||
MemoryStream firstChunkData = new MemoryStream(firstChunkBinary, 0, firstChunkBinary.Length);
|
||||
|
||||
var client = new HttpClient();
|
||||
var request = new HttpRequestMessage {
|
||||
RequestUri = new Uri($"{UP_HOST}/mkblk/{blcokSize}"),
|
||||
Method = HttpMethod.Post,
|
||||
Content = new StreamContent(firstChunkData)
|
||||
};
|
||||
var headers = GetQiniuRequestHeaders(state);
|
||||
foreach (var header in headers) {
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
|
||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||
var response = await client.SendAsync(request);
|
||||
client.Dispose();
|
||||
request.Dispose();
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
return await JsonUtils.DeserializeObjectAsync<Dictionary<string, object>>(content, new LeanCloudJsonConverter());
|
||||
}
|
||||
|
||||
async Task<Dictionary<string, object>> PutChunk(FileState state, byte[] chunkBinary, string LastChunkctx, long currentChunkOffsetInBlock) {
|
||||
MemoryStream chunkData = new MemoryStream(chunkBinary, 0, chunkBinary.Length);
|
||||
var client = new HttpClient();
|
||||
var request = new HttpRequestMessage {
|
||||
RequestUri = new Uri($"{UP_HOST}/bput/{LastChunkctx}/{currentChunkOffsetInBlock}"),
|
||||
Method = HttpMethod.Post,
|
||||
Content = new StreamContent(chunkData)
|
||||
};
|
||||
var headers = GetQiniuRequestHeaders(state);
|
||||
foreach (var header in headers) {
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
var response = await client.SendAsync(request);
|
||||
client.Dispose();
|
||||
request.Dispose();
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
return await JsonUtils.DeserializeObjectAsync<Dictionary<string, object>>(content);
|
||||
}
|
||||
|
||||
internal async Task<Tuple<HttpStatusCode, string>> QiniuMakeFile(FileState state, Stream dataStream, string upToken, string key, long fsize, string[] ctxes, CancellationToken cancellationToken) {
|
||||
StringBuilder urlBuilder = new StringBuilder();
|
||||
urlBuilder.AppendFormat("{0}/mkfile/{1}", UP_HOST, fsize);
|
||||
if (key != null) {
|
||||
urlBuilder.AppendFormat("/key/{0}", ToBase64URLSafe(key));
|
||||
}
|
||||
var metaData = GetMetaData(state, dataStream);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (string _key in metaData.Keys) {
|
||||
sb.AppendFormat("/{0}/{1}", _key, ToBase64URLSafe(metaData[_key].ToString()));
|
||||
}
|
||||
urlBuilder.Append(sb.ToString());
|
||||
|
||||
int proCount = ctxes.Length;
|
||||
Stream body = new MemoryStream();
|
||||
|
||||
for (int i = 0; i < proCount; i++) {
|
||||
byte[] bctx = StringToAscii(ctxes[i]);
|
||||
body.Write(bctx, 0, bctx.Length);
|
||||
if (i != proCount - 1) {
|
||||
body.WriteByte((byte)',');
|
||||
}
|
||||
}
|
||||
body.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var client = new HttpClient();
|
||||
var request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(urlBuilder.ToString()),
|
||||
Method = HttpMethod.Post,
|
||||
Content = new StreamContent(body)
|
||||
};
|
||||
request.Headers.Add("Authorization", $"UpToken {upToken}");
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("text/plain");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
client.Dispose();
|
||||
request.Dispose();
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
return await JsonUtils.DeserializeObjectAsync<Tuple<HttpStatusCode, string>>(content);
|
||||
}
|
||||
|
||||
internal void MergeFromJSON(FileState state, IDictionary<string, object> jsonData) {
|
||||
lock (this.mutex) {
|
||||
string url = jsonData["url"] as string;
|
||||
state.Url = new Uri(url, UriKind.Absolute);
|
||||
state.bucketId = FetchBucketId(url);
|
||||
state.token = jsonData["token"] as string;
|
||||
state.bucket = jsonData["bucket"] as string;
|
||||
state.ObjectId = jsonData["objectId"] as string;
|
||||
}
|
||||
}
|
||||
|
||||
string FetchBucketId(string url) {
|
||||
var elements = url.Split('/');
|
||||
|
||||
return elements[elements.Length - 1];
|
||||
}
|
||||
|
||||
public static byte[] StringToAscii(string s) {
|
||||
byte[] retval = new byte[s.Length];
|
||||
for (int ix = 0; ix < s.Length; ++ix) {
|
||||
char ch = s[ix];
|
||||
if (ch <= 0x7f)
|
||||
retval[ix] = (byte)ch;
|
||||
else
|
||||
retval[ix] = (byte)'?';
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static string ToBase64URLSafe(string str) {
|
||||
return Encode(str);
|
||||
}
|
||||
|
||||
public static string Encode(byte[] bs) {
|
||||
if (bs == null || bs.Length == 0)
|
||||
return "";
|
||||
string encodedStr = Convert.ToBase64String(bs);
|
||||
encodedStr = encodedStr.Replace('+', '-').Replace('/', '_');
|
||||
return encodedStr;
|
||||
}
|
||||
|
||||
public static string Encode(string text) {
|
||||
if (string.IsNullOrEmpty(text))
|
||||
return "";
|
||||
byte[] bs = Encoding.UTF8.GetBytes(text);
|
||||
string encodedStr = Convert.ToBase64String(bs);
|
||||
encodedStr = encodedStr.Replace('+', '-').Replace('/', '_');
|
||||
return encodedStr;
|
||||
}
|
||||
|
||||
internal static string GetMD5Code(Stream data) {
|
||||
MD5 md5 = new MD5CryptoServiceProvider();
|
||||
byte[] retVal = md5.ComputeHash(data);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < retVal.Length; i++) {
|
||||
sb.Append(retVal[i].ToString("x2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
|
||||
}
|
||||
|
||||
internal IDictionary<string, object> GetMetaData(FileState state, Stream data) {
|
||||
IDictionary<string, object> rtn = new Dictionary<string, object>();
|
||||
|
||||
if (state.MetaData != null) {
|
||||
foreach (var meta in state.MetaData) {
|
||||
rtn.Add(meta.Key, meta.Value);
|
||||
}
|
||||
}
|
||||
MergeDic(rtn, "mime_type", AVFile.GetMIMEType(state.Name));
|
||||
MergeDic(rtn, "size", data.Length);
|
||||
MergeDic(rtn, "_checksum", GetMD5Code(data));
|
||||
if (AVUser.CurrentUser != null)
|
||||
if (AVUser.CurrentUser.ObjectId != null)
|
||||
MergeDic(rtn, "owner", AVUser.CurrentUser.ObjectId);
|
||||
|
||||
return rtn;
|
||||
}
|
||||
|
||||
internal void MergeDic(IDictionary<string, object> dic, string key, object value) {
|
||||
if (dic.ContainsKey(key)) {
|
||||
dic[key] = value;
|
||||
} else {
|
||||
dic.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,566 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// 弥补Windows Phone 8 API没有自带MD5加密的拓展方法。
|
||||
/// </summary>
|
||||
internal class MD5CryptoServiceProvider : MD5
|
||||
{
|
||||
public MD5CryptoServiceProvider()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Summary description for MD5.
|
||||
/// </summary>
|
||||
internal class MD5 : IDisposable
|
||||
{
|
||||
static public MD5 Create(string hashName)
|
||||
{
|
||||
if (hashName == "MD5")
|
||||
return new MD5();
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
static public string GetMd5String(String source)
|
||||
{
|
||||
MD5 md = MD5CryptoServiceProvider.Create();
|
||||
byte[] hash;
|
||||
|
||||
//Create a new instance of ASCIIEncoding to
|
||||
//convert the string into an array of Unicode bytes.
|
||||
UTF8Encoding enc = new UTF8Encoding();
|
||||
// ASCIIEncoding enc = new ASCIIEncoding();
|
||||
|
||||
//Convert the string into an array of bytes.
|
||||
byte[] buffer = enc.GetBytes(source);
|
||||
|
||||
//Create the hash value from the array of bytes.
|
||||
hash = md.ComputeHash(buffer);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (byte b in hash)
|
||||
sb.Append(b.ToString("x2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
static public MD5 Create()
|
||||
{
|
||||
return new MD5();
|
||||
}
|
||||
|
||||
#region base implementation of the MD5
|
||||
#region constants
|
||||
private const byte S11 = 7;
|
||||
private const byte S12 = 12;
|
||||
private const byte S13 = 17;
|
||||
private const byte S14 = 22;
|
||||
private const byte S21 = 5;
|
||||
private const byte S22 = 9;
|
||||
private const byte S23 = 14;
|
||||
private const byte S24 = 20;
|
||||
private const byte S31 = 4;
|
||||
private const byte S32 = 11;
|
||||
private const byte S33 = 16;
|
||||
private const byte S34 = 23;
|
||||
private const byte S41 = 6;
|
||||
private const byte S42 = 10;
|
||||
private const byte S43 = 15;
|
||||
private const byte S44 = 21;
|
||||
private static readonly byte[] PADDING = {
|
||||
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
#endregion
|
||||
|
||||
#region F, G, H and I are basic MD5 functions.
|
||||
static private uint F(uint x, uint y, uint z)
|
||||
{
|
||||
return (((x) & (y)) | ((~x) & (z)));
|
||||
}
|
||||
static private uint G(uint x, uint y, uint z)
|
||||
{
|
||||
return (((x) & (z)) | ((y) & (~z)));
|
||||
}
|
||||
static private uint H(uint x, uint y, uint z)
|
||||
{
|
||||
return ((x) ^ (y) ^ (z));
|
||||
}
|
||||
static private uint I(uint x, uint y, uint z)
|
||||
{
|
||||
return ((y) ^ ((x) | (~z)));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region rotates x left n bits.
|
||||
/// <summary>
|
||||
/// rotates x left n bits.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
static private uint ROTATE_LEFT(uint x, byte n)
|
||||
{
|
||||
return (((x) << (n)) | ((x) >> (32 - (n))));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region FF, GG, HH, and II transformations
|
||||
/// FF, GG, HH, and II transformations
|
||||
/// for rounds 1, 2, 3, and 4.
|
||||
/// Rotation is separate from addition to prevent recomputation.
|
||||
static private void FF(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac)
|
||||
{
|
||||
(a) += F((b), (c), (d)) + (x) + (uint)(ac);
|
||||
(a) = ROTATE_LEFT((a), (s));
|
||||
(a) += (b);
|
||||
}
|
||||
static private void GG(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac)
|
||||
{
|
||||
(a) += G((b), (c), (d)) + (x) + (uint)(ac);
|
||||
(a) = ROTATE_LEFT((a), (s));
|
||||
(a) += (b);
|
||||
}
|
||||
static private void HH(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac)
|
||||
{
|
||||
(a) += H((b), (c), (d)) + (x) + (uint)(ac);
|
||||
(a) = ROTATE_LEFT((a), (s));
|
||||
(a) += (b);
|
||||
}
|
||||
static private void II(ref uint a, uint b, uint c, uint d, uint x, byte s, uint ac)
|
||||
{
|
||||
(a) += I((b), (c), (d)) + (x) + (uint)(ac);
|
||||
(a) = ROTATE_LEFT((a), (s));
|
||||
(a) += (b);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region context info
|
||||
/// <summary>
|
||||
/// state (ABCD)
|
||||
/// </summary>
|
||||
uint[] state = new uint[4];
|
||||
|
||||
/// <summary>
|
||||
/// number of bits, modulo 2^64 (lsb first)
|
||||
/// </summary>
|
||||
uint[] count = new uint[2];
|
||||
|
||||
/// <summary>
|
||||
/// input buffer
|
||||
/// </summary>
|
||||
byte[] buffer = new byte[64];
|
||||
#endregion
|
||||
|
||||
internal MD5()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MD5 initialization. Begins an MD5 operation, writing a new context.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The RFC named it "MD5Init"
|
||||
/// </remarks>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
count[0] = count[1] = 0;
|
||||
|
||||
// Load magic initialization constants.
|
||||
state[0] = 0x67452301;
|
||||
state[1] = 0xefcdab89;
|
||||
state[2] = 0x98badcfe;
|
||||
state[3] = 0x10325476;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MD5 block update operation. Continues an MD5 message-digest
|
||||
/// operation, processing another message block, and updating the
|
||||
/// context.
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <param name="count"></param>
|
||||
/// <remarks>The RFC Named it MD5Update</remarks>
|
||||
protected virtual void HashCore(byte[] input, int offset, int count)
|
||||
{
|
||||
int i;
|
||||
int index;
|
||||
int partLen;
|
||||
|
||||
// Compute number of bytes mod 64
|
||||
index = (int)((this.count[0] >> 3) & 0x3F);
|
||||
|
||||
// Update number of bits
|
||||
if ((this.count[0] += (uint)((uint)count << 3)) < ((uint)count << 3))
|
||||
this.count[1]++;
|
||||
this.count[1] += ((uint)count >> 29);
|
||||
|
||||
partLen = 64 - index;
|
||||
|
||||
// Transform as many times as possible.
|
||||
if (count >= partLen)
|
||||
{
|
||||
Buffer.BlockCopy(input, offset, this.buffer, index, partLen);
|
||||
Transform(this.buffer, 0);
|
||||
|
||||
for (i = partLen; i + 63 < count; i += 64)
|
||||
Transform(input, offset + i);
|
||||
|
||||
index = 0;
|
||||
}
|
||||
else
|
||||
i = 0;
|
||||
|
||||
// Buffer remaining input
|
||||
Buffer.BlockCopy(input, offset + i, this.buffer, index, count - i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MD5 finalization. Ends an MD5 message-digest operation, writing the
|
||||
/// the message digest and zeroizing the context.
|
||||
/// </summary>
|
||||
/// <returns>message digest</returns>
|
||||
/// <remarks>The RFC named it MD5Final</remarks>
|
||||
protected virtual byte[] HashFinal()
|
||||
{
|
||||
byte[] digest = new byte[16];
|
||||
byte[] bits = new byte[8];
|
||||
int index, padLen;
|
||||
|
||||
// Save number of bits
|
||||
Encode(bits, 0, this.count, 0, 8);
|
||||
|
||||
// Pad out to 56 mod 64.
|
||||
index = (int)((uint)(this.count[0] >> 3) & 0x3f);
|
||||
padLen = (index < 56) ? (56 - index) : (120 - index);
|
||||
HashCore(PADDING, 0, padLen);
|
||||
|
||||
// Append length (before padding)
|
||||
HashCore(bits, 0, 8);
|
||||
|
||||
// Store state in digest
|
||||
Encode(digest, 0, state, 0, 16);
|
||||
|
||||
// Zeroize sensitive information.
|
||||
count[0] = count[1] = 0;
|
||||
state[0] = 0;
|
||||
state[1] = 0;
|
||||
state[2] = 0;
|
||||
state[3] = 0;
|
||||
|
||||
// initialize again, to be ready to use
|
||||
Initialize();
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MD5 basic transformation. Transforms state based on 64 bytes block.
|
||||
/// </summary>
|
||||
/// <param name="block"></param>
|
||||
/// <param name="offset"></param>
|
||||
private void Transform(byte[] block, int offset)
|
||||
{
|
||||
uint a = state[0], b = state[1], c = state[2], d = state[3];
|
||||
uint[] x = new uint[16];
|
||||
Decode(x, 0, block, offset, 64);
|
||||
|
||||
// Round 1
|
||||
FF(ref a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */
|
||||
FF(ref d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */
|
||||
FF(ref c, d, a, b, x[2], S13, 0x242070db); /* 3 */
|
||||
FF(ref b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */
|
||||
FF(ref a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */
|
||||
FF(ref d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */
|
||||
FF(ref c, d, a, b, x[6], S13, 0xa8304613); /* 7 */
|
||||
FF(ref b, c, d, a, x[7], S14, 0xfd469501); /* 8 */
|
||||
FF(ref a, b, c, d, x[8], S11, 0x698098d8); /* 9 */
|
||||
FF(ref d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */
|
||||
FF(ref c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
|
||||
FF(ref b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
|
||||
FF(ref a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
|
||||
FF(ref d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
|
||||
FF(ref c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
|
||||
FF(ref b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
|
||||
|
||||
// Round 2
|
||||
GG(ref a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */
|
||||
GG(ref d, a, b, c, x[6], S22, 0xc040b340); /* 18 */
|
||||
GG(ref c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
|
||||
GG(ref b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */
|
||||
GG(ref a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */
|
||||
GG(ref d, a, b, c, x[10], S22, 0x2441453); /* 22 */
|
||||
GG(ref c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
|
||||
GG(ref b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */
|
||||
GG(ref a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */
|
||||
GG(ref d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
|
||||
GG(ref c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */
|
||||
GG(ref b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */
|
||||
GG(ref a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
|
||||
GG(ref d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */
|
||||
GG(ref c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */
|
||||
GG(ref b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
|
||||
|
||||
// Round 3
|
||||
HH(ref a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */
|
||||
HH(ref d, a, b, c, x[8], S32, 0x8771f681); /* 34 */
|
||||
HH(ref c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
|
||||
HH(ref b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
|
||||
HH(ref a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */
|
||||
HH(ref d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */
|
||||
HH(ref c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */
|
||||
HH(ref b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
|
||||
HH(ref a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
|
||||
HH(ref d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */
|
||||
HH(ref c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */
|
||||
HH(ref b, c, d, a, x[6], S34, 0x4881d05); /* 44 */
|
||||
HH(ref a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */
|
||||
HH(ref d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
|
||||
HH(ref c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
|
||||
HH(ref b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */
|
||||
|
||||
// Round 4
|
||||
II(ref a, b, c, d, x[0], S41, 0xf4292244); /* 49 */
|
||||
II(ref d, a, b, c, x[7], S42, 0x432aff97); /* 50 */
|
||||
II(ref c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
|
||||
II(ref b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */
|
||||
II(ref a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
|
||||
II(ref d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */
|
||||
II(ref c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
|
||||
II(ref b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */
|
||||
II(ref a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */
|
||||
II(ref d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
|
||||
II(ref c, d, a, b, x[6], S43, 0xa3014314); /* 59 */
|
||||
II(ref b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
|
||||
II(ref a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */
|
||||
II(ref d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
|
||||
II(ref c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */
|
||||
II(ref b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */
|
||||
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
|
||||
// Zeroize sensitive information.
|
||||
for (int i = 0; i < x.Length; i++)
|
||||
x[i] = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes input (uint) into output (byte). Assumes len is
|
||||
/// multiple of 4.
|
||||
/// </summary>
|
||||
/// <param name="output"></param>
|
||||
/// <param name="outputOffset"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="inputOffset"></param>
|
||||
/// <param name="count"></param>
|
||||
private static void Encode(byte[] output, int outputOffset, uint[] input, int inputOffset, int count)
|
||||
{
|
||||
int i, j;
|
||||
int end = outputOffset + count;
|
||||
for (i = inputOffset, j = outputOffset; j < end; i++, j += 4)
|
||||
{
|
||||
output[j] = (byte)(input[i] & 0xff);
|
||||
output[j + 1] = (byte)((input[i] >> 8) & 0xff);
|
||||
output[j + 2] = (byte)((input[i] >> 16) & 0xff);
|
||||
output[j + 3] = (byte)((input[i] >> 24) & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes input (byte) into output (uint). Assumes len is
|
||||
/// a multiple of 4.
|
||||
/// </summary>
|
||||
/// <param name="output"></param>
|
||||
/// <param name="outputOffset"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="inputOffset"></param>
|
||||
/// <param name="count"></param>
|
||||
static private void Decode(uint[] output, int outputOffset, byte[] input, int inputOffset, int count)
|
||||
{
|
||||
int i, j;
|
||||
int end = inputOffset + count;
|
||||
for (i = outputOffset, j = inputOffset; j < end; i++, j += 4)
|
||||
output[i] = ((uint)input[j]) | (((uint)input[j + 1]) << 8) | (((uint)input[j + 2]) << 16) | (((uint)input[j + 3]) << 24);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region expose the same interface as the regular MD5 object
|
||||
|
||||
protected byte[] HashValue;
|
||||
protected int State;
|
||||
public virtual bool CanReuseTransform
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool CanTransformMultipleBlocks
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public virtual byte[] Hash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.State != 0)
|
||||
throw new InvalidOperationException();
|
||||
return (byte[])HashValue.Clone();
|
||||
}
|
||||
}
|
||||
public virtual int HashSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return HashSizeValue;
|
||||
}
|
||||
}
|
||||
protected int HashSizeValue = 128;
|
||||
|
||||
public virtual int InputBlockSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
public virtual int OutputBlockSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
public byte[] ComputeHash(byte[] buffer)
|
||||
{
|
||||
return ComputeHash(buffer, 0, buffer.Length);
|
||||
}
|
||||
public byte[] ComputeHash(byte[] buffer, int offset, int count)
|
||||
{
|
||||
Initialize();
|
||||
HashCore(buffer, offset, count);
|
||||
HashValue = HashFinal();
|
||||
return (byte[])HashValue.Clone();
|
||||
}
|
||||
|
||||
public byte[] ComputeHash(Stream inputStream)
|
||||
{
|
||||
Initialize();
|
||||
int count;
|
||||
byte[] buffer = new byte[4096];
|
||||
while (0 < (count = inputStream.Read(buffer, 0, 4096)))
|
||||
{
|
||||
HashCore(buffer, 0, count);
|
||||
}
|
||||
HashValue = HashFinal();
|
||||
return (byte[])HashValue.Clone();
|
||||
}
|
||||
|
||||
public int TransformBlock(
|
||||
byte[] inputBuffer,
|
||||
int inputOffset,
|
||||
int inputCount,
|
||||
byte[] outputBuffer,
|
||||
int outputOffset
|
||||
)
|
||||
{
|
||||
if (inputBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException("inputBuffer");
|
||||
}
|
||||
if (inputOffset < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("inputOffset");
|
||||
}
|
||||
if ((inputCount < 0) || (inputCount > inputBuffer.Length))
|
||||
{
|
||||
throw new ArgumentException("inputCount");
|
||||
}
|
||||
if ((inputBuffer.Length - inputCount) < inputOffset)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("inputOffset");
|
||||
}
|
||||
if (this.State == 0)
|
||||
{
|
||||
Initialize();
|
||||
this.State = 1;
|
||||
}
|
||||
|
||||
HashCore(inputBuffer, inputOffset, inputCount);
|
||||
if ((inputBuffer != outputBuffer) || (inputOffset != outputOffset))
|
||||
{
|
||||
Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount);
|
||||
}
|
||||
return inputCount;
|
||||
}
|
||||
public byte[] TransformFinalBlock(
|
||||
byte[] inputBuffer,
|
||||
int inputOffset,
|
||||
int inputCount
|
||||
)
|
||||
{
|
||||
if (inputBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException("inputBuffer");
|
||||
}
|
||||
if (inputOffset < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("inputOffset");
|
||||
}
|
||||
if ((inputCount < 0) || (inputCount > inputBuffer.Length))
|
||||
{
|
||||
throw new ArgumentException("inputCount");
|
||||
}
|
||||
if ((inputBuffer.Length - inputCount) < inputOffset)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("inputOffset");
|
||||
}
|
||||
if (this.State == 0)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
HashCore(inputBuffer, inputOffset, inputCount);
|
||||
HashValue = HashFinal();
|
||||
byte[] buffer = new byte[inputCount];
|
||||
Buffer.BlockCopy(inputBuffer, inputOffset, buffer, 0, inputCount);
|
||||
this.State = 0;
|
||||
return buffer;
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
Initialize();
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,495 @@
|
|||
//
|
||||
// System.Security.Cryptography.SHA1CryptoServiceProvider.cs
|
||||
//
|
||||
// Authors:
|
||||
// Matthew S. Ford (Matthew.S.Ford@Rose-Hulman.Edu)
|
||||
// Sebastien Pouliot (sebastien@ximian.com)
|
||||
//
|
||||
// Copyright 2001 by Matthew S. Ford.
|
||||
// Copyright (C) 2004, 2005, 2008 Novell, Inc (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
// Note:
|
||||
// The MS Framework includes two (almost) identical class for SHA1.
|
||||
// SHA1Managed is a 100% managed implementation.
|
||||
// SHA1CryptoServiceProvider (this file) is a wrapper on CryptoAPI.
|
||||
// Mono must provide those two class for binary compatibility.
|
||||
// In our case both class are wrappers around a managed internal class SHA1Internal.
|
||||
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
|
||||
internal class SHA1Internal
|
||||
{
|
||||
|
||||
private const int BLOCK_SIZE_BYTES = 64;
|
||||
private uint[] _H; // these are my chaining variables
|
||||
private ulong count;
|
||||
private byte[] _ProcessingBuffer; // Used to start data when passed less than a block worth.
|
||||
private int _ProcessingBufferCount; // Counts how much data we have stored that still needs processed.
|
||||
private uint[] buff;
|
||||
|
||||
public SHA1Internal()
|
||||
{
|
||||
_H = new uint[5];
|
||||
_ProcessingBuffer = new byte[BLOCK_SIZE_BYTES];
|
||||
buff = new uint[80];
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void HashCore(byte[] rgb, int ibStart, int cbSize)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (_ProcessingBufferCount != 0)
|
||||
{
|
||||
if (cbSize < (BLOCK_SIZE_BYTES - _ProcessingBufferCount))
|
||||
{
|
||||
System.Buffer.BlockCopy(rgb, ibStart, _ProcessingBuffer, _ProcessingBufferCount, cbSize);
|
||||
_ProcessingBufferCount += cbSize;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
i = (BLOCK_SIZE_BYTES - _ProcessingBufferCount);
|
||||
System.Buffer.BlockCopy(rgb, ibStart, _ProcessingBuffer, _ProcessingBufferCount, i);
|
||||
ProcessBlock(_ProcessingBuffer, 0);
|
||||
_ProcessingBufferCount = 0;
|
||||
ibStart += i;
|
||||
cbSize -= i;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < cbSize - cbSize % BLOCK_SIZE_BYTES; i += BLOCK_SIZE_BYTES)
|
||||
{
|
||||
ProcessBlock(rgb, (uint)(ibStart + i));
|
||||
}
|
||||
|
||||
if (cbSize % BLOCK_SIZE_BYTES != 0)
|
||||
{
|
||||
System.Buffer.BlockCopy(rgb, cbSize - cbSize % BLOCK_SIZE_BYTES + ibStart, _ProcessingBuffer, 0, cbSize % BLOCK_SIZE_BYTES);
|
||||
_ProcessingBufferCount = cbSize % BLOCK_SIZE_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] HashFinal()
|
||||
{
|
||||
byte[] hash = new byte[20];
|
||||
|
||||
ProcessFinalBlock(_ProcessingBuffer, 0, _ProcessingBufferCount);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
hash[i * 4 + j] = (byte)(_H[i] >> (8 * (3 - j)));
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
count = 0;
|
||||
_ProcessingBufferCount = 0;
|
||||
|
||||
_H[0] = 0x67452301;
|
||||
_H[1] = 0xefcdab89;
|
||||
_H[2] = 0x98badcfe;
|
||||
_H[3] = 0x10325476;
|
||||
_H[4] = 0xC3D2E1F0;
|
||||
}
|
||||
|
||||
private void ProcessBlock(byte[] inputBuffer, uint inputOffset)
|
||||
{
|
||||
uint a, b, c, d, e;
|
||||
|
||||
count += BLOCK_SIZE_BYTES;
|
||||
|
||||
// abc removal would not work on the fields
|
||||
uint[] _H = this._H;
|
||||
uint[] buff = this.buff;
|
||||
InitialiseBuff(buff, inputBuffer, inputOffset);
|
||||
FillBuff(buff);
|
||||
|
||||
a = _H[0];
|
||||
b = _H[1];
|
||||
c = _H[2];
|
||||
d = _H[3];
|
||||
e = _H[4];
|
||||
|
||||
// This function was unrolled because it seems to be doubling our performance with current compiler/VM.
|
||||
// Possibly roll up if this changes.
|
||||
|
||||
// ---- Round 1 --------
|
||||
int i = 0;
|
||||
while (i < 20)
|
||||
{
|
||||
e += ((a << 5) | (a >> 27)) + (((c ^ d) & b) ^ d) + 0x5A827999 + buff[i];
|
||||
b = (b << 30) | (b >> 2);
|
||||
|
||||
d += ((e << 5) | (e >> 27)) + (((b ^ c) & a) ^ c) + 0x5A827999 + buff[i + 1];
|
||||
a = (a << 30) | (a >> 2);
|
||||
|
||||
c += ((d << 5) | (d >> 27)) + (((a ^ b) & e) ^ b) + 0x5A827999 + buff[i + 2];
|
||||
e = (e << 30) | (e >> 2);
|
||||
|
||||
b += ((c << 5) | (c >> 27)) + (((e ^ a) & d) ^ a) + 0x5A827999 + buff[i + 3];
|
||||
d = (d << 30) | (d >> 2);
|
||||
|
||||
a += ((b << 5) | (b >> 27)) + (((d ^ e) & c) ^ e) + 0x5A827999 + buff[i + 4];
|
||||
c = (c << 30) | (c >> 2);
|
||||
i += 5;
|
||||
}
|
||||
|
||||
// ---- Round 2 --------
|
||||
while (i < 40)
|
||||
{
|
||||
e += ((a << 5) | (a >> 27)) + (b ^ c ^ d) + 0x6ED9EBA1 + buff[i];
|
||||
b = (b << 30) | (b >> 2);
|
||||
|
||||
d += ((e << 5) | (e >> 27)) + (a ^ b ^ c) + 0x6ED9EBA1 + buff[i + 1];
|
||||
a = (a << 30) | (a >> 2);
|
||||
|
||||
c += ((d << 5) | (d >> 27)) + (e ^ a ^ b) + 0x6ED9EBA1 + buff[i + 2];
|
||||
e = (e << 30) | (e >> 2);
|
||||
|
||||
b += ((c << 5) | (c >> 27)) + (d ^ e ^ a) + 0x6ED9EBA1 + buff[i + 3];
|
||||
d = (d << 30) | (d >> 2);
|
||||
|
||||
a += ((b << 5) | (b >> 27)) + (c ^ d ^ e) + 0x6ED9EBA1 + buff[i + 4];
|
||||
c = (c << 30) | (c >> 2);
|
||||
i += 5;
|
||||
}
|
||||
|
||||
// ---- Round 3 --------
|
||||
while (i < 60)
|
||||
{
|
||||
e += ((a << 5) | (a >> 27)) + ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC + buff[i];
|
||||
b = (b << 30) | (b >> 2);
|
||||
|
||||
d += ((e << 5) | (e >> 27)) + ((a & b) | (a & c) | (b & c)) + 0x8F1BBCDC + buff[i + 1];
|
||||
a = (a << 30) | (a >> 2);
|
||||
|
||||
c += ((d << 5) | (d >> 27)) + ((e & a) | (e & b) | (a & b)) + 0x8F1BBCDC + buff[i + 2];
|
||||
e = (e << 30) | (e >> 2);
|
||||
|
||||
b += ((c << 5) | (c >> 27)) + ((d & e) | (d & a) | (e & a)) + 0x8F1BBCDC + buff[i + 3];
|
||||
d = (d << 30) | (d >> 2);
|
||||
|
||||
a += ((b << 5) | (b >> 27)) + ((c & d) | (c & e) | (d & e)) + 0x8F1BBCDC + buff[i + 4];
|
||||
c = (c << 30) | (c >> 2);
|
||||
i += 5;
|
||||
}
|
||||
|
||||
// ---- Round 4 --------
|
||||
while (i < 80)
|
||||
{
|
||||
e += ((a << 5) | (a >> 27)) + (b ^ c ^ d) + 0xCA62C1D6 + buff[i];
|
||||
b = (b << 30) | (b >> 2);
|
||||
|
||||
d += ((e << 5) | (e >> 27)) + (a ^ b ^ c) + 0xCA62C1D6 + buff[i + 1];
|
||||
a = (a << 30) | (a >> 2);
|
||||
|
||||
c += ((d << 5) | (d >> 27)) + (e ^ a ^ b) + 0xCA62C1D6 + buff[i + 2];
|
||||
e = (e << 30) | (e >> 2);
|
||||
|
||||
b += ((c << 5) | (c >> 27)) + (d ^ e ^ a) + 0xCA62C1D6 + buff[i + 3];
|
||||
d = (d << 30) | (d >> 2);
|
||||
|
||||
a += ((b << 5) | (b >> 27)) + (c ^ d ^ e) + 0xCA62C1D6 + buff[i + 4];
|
||||
c = (c << 30) | (c >> 2);
|
||||
i += 5;
|
||||
}
|
||||
|
||||
_H[0] += a;
|
||||
_H[1] += b;
|
||||
_H[2] += c;
|
||||
_H[3] += d;
|
||||
_H[4] += e;
|
||||
}
|
||||
|
||||
private static void InitialiseBuff(uint[] buff, byte[] input, uint inputOffset)
|
||||
{
|
||||
buff[0] = (uint)((input[inputOffset + 0] << 24) | (input[inputOffset + 1] << 16) | (input[inputOffset + 2] << 8) | (input[inputOffset + 3]));
|
||||
buff[1] = (uint)((input[inputOffset + 4] << 24) | (input[inputOffset + 5] << 16) | (input[inputOffset + 6] << 8) | (input[inputOffset + 7]));
|
||||
buff[2] = (uint)((input[inputOffset + 8] << 24) | (input[inputOffset + 9] << 16) | (input[inputOffset + 10] << 8) | (input[inputOffset + 11]));
|
||||
buff[3] = (uint)((input[inputOffset + 12] << 24) | (input[inputOffset + 13] << 16) | (input[inputOffset + 14] << 8) | (input[inputOffset + 15]));
|
||||
buff[4] = (uint)((input[inputOffset + 16] << 24) | (input[inputOffset + 17] << 16) | (input[inputOffset + 18] << 8) | (input[inputOffset + 19]));
|
||||
buff[5] = (uint)((input[inputOffset + 20] << 24) | (input[inputOffset + 21] << 16) | (input[inputOffset + 22] << 8) | (input[inputOffset + 23]));
|
||||
buff[6] = (uint)((input[inputOffset + 24] << 24) | (input[inputOffset + 25] << 16) | (input[inputOffset + 26] << 8) | (input[inputOffset + 27]));
|
||||
buff[7] = (uint)((input[inputOffset + 28] << 24) | (input[inputOffset + 29] << 16) | (input[inputOffset + 30] << 8) | (input[inputOffset + 31]));
|
||||
buff[8] = (uint)((input[inputOffset + 32] << 24) | (input[inputOffset + 33] << 16) | (input[inputOffset + 34] << 8) | (input[inputOffset + 35]));
|
||||
buff[9] = (uint)((input[inputOffset + 36] << 24) | (input[inputOffset + 37] << 16) | (input[inputOffset + 38] << 8) | (input[inputOffset + 39]));
|
||||
buff[10] = (uint)((input[inputOffset + 40] << 24) | (input[inputOffset + 41] << 16) | (input[inputOffset + 42] << 8) | (input[inputOffset + 43]));
|
||||
buff[11] = (uint)((input[inputOffset + 44] << 24) | (input[inputOffset + 45] << 16) | (input[inputOffset + 46] << 8) | (input[inputOffset + 47]));
|
||||
buff[12] = (uint)((input[inputOffset + 48] << 24) | (input[inputOffset + 49] << 16) | (input[inputOffset + 50] << 8) | (input[inputOffset + 51]));
|
||||
buff[13] = (uint)((input[inputOffset + 52] << 24) | (input[inputOffset + 53] << 16) | (input[inputOffset + 54] << 8) | (input[inputOffset + 55]));
|
||||
buff[14] = (uint)((input[inputOffset + 56] << 24) | (input[inputOffset + 57] << 16) | (input[inputOffset + 58] << 8) | (input[inputOffset + 59]));
|
||||
buff[15] = (uint)((input[inputOffset + 60] << 24) | (input[inputOffset + 61] << 16) | (input[inputOffset + 62] << 8) | (input[inputOffset + 63]));
|
||||
}
|
||||
|
||||
private static void FillBuff(uint[] buff)
|
||||
{
|
||||
uint val;
|
||||
for (int i = 16; i < 80; i += 8)
|
||||
{
|
||||
val = buff[i - 3] ^ buff[i - 8] ^ buff[i - 14] ^ buff[i - 16];
|
||||
buff[i] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i - 2] ^ buff[i - 7] ^ buff[i - 13] ^ buff[i - 15];
|
||||
buff[i + 1] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i - 1] ^ buff[i - 6] ^ buff[i - 12] ^ buff[i - 14];
|
||||
buff[i + 2] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i + 0] ^ buff[i - 5] ^ buff[i - 11] ^ buff[i - 13];
|
||||
buff[i + 3] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i + 1] ^ buff[i - 4] ^ buff[i - 10] ^ buff[i - 12];
|
||||
buff[i + 4] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i + 2] ^ buff[i - 3] ^ buff[i - 9] ^ buff[i - 11];
|
||||
buff[i + 5] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i + 3] ^ buff[i - 2] ^ buff[i - 8] ^ buff[i - 10];
|
||||
buff[i + 6] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i + 4] ^ buff[i - 1] ^ buff[i - 7] ^ buff[i - 9];
|
||||
buff[i + 7] = (val << 1) | (val >> 31);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
|
||||
{
|
||||
ulong total = count + (ulong)inputCount;
|
||||
int paddingSize = (56 - (int)(total % BLOCK_SIZE_BYTES));
|
||||
|
||||
if (paddingSize < 1)
|
||||
paddingSize += BLOCK_SIZE_BYTES;
|
||||
|
||||
int length = inputCount + paddingSize + 8;
|
||||
byte[] fooBuffer = (length == 64) ? _ProcessingBuffer : new byte[length];
|
||||
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
fooBuffer[i] = inputBuffer[i + inputOffset];
|
||||
}
|
||||
|
||||
fooBuffer[inputCount] = 0x80;
|
||||
for (int i = inputCount + 1; i < inputCount + paddingSize; i++)
|
||||
{
|
||||
fooBuffer[i] = 0x00;
|
||||
}
|
||||
|
||||
// I deal in bytes. The algorithm deals in bits.
|
||||
ulong size = total << 3;
|
||||
AddLength(size, fooBuffer, inputCount + paddingSize);
|
||||
ProcessBlock(fooBuffer, 0);
|
||||
|
||||
if (length == 128)
|
||||
ProcessBlock(fooBuffer, 64);
|
||||
}
|
||||
|
||||
internal void AddLength(ulong length, byte[] buffer, int position)
|
||||
{
|
||||
buffer[position++] = (byte)(length >> 56);
|
||||
buffer[position++] = (byte)(length >> 48);
|
||||
buffer[position++] = (byte)(length >> 40);
|
||||
buffer[position++] = (byte)(length >> 32);
|
||||
buffer[position++] = (byte)(length >> 24);
|
||||
buffer[position++] = (byte)(length >> 16);
|
||||
buffer[position++] = (byte)(length >> 8);
|
||||
buffer[position] = (byte)(length);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SHA1CryptoServiceProvider : SHA1
|
||||
{
|
||||
|
||||
private SHA1Internal sha;
|
||||
|
||||
public SHA1CryptoServiceProvider()
|
||||
{
|
||||
sha = new SHA1Internal();
|
||||
}
|
||||
|
||||
~SHA1CryptoServiceProvider()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
// nothing new to do (managed implementation)
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void HashCore(byte[] rgb, int ibStart, int cbSize)
|
||||
{
|
||||
State = 1;
|
||||
sha.HashCore(rgb, ibStart, cbSize);
|
||||
}
|
||||
|
||||
protected override byte[] HashFinal()
|
||||
{
|
||||
State = 0;
|
||||
return sha.HashFinal();
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
sha.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SHA1 : HashAlgorithm
|
||||
{
|
||||
protected SHA1()
|
||||
{
|
||||
HashSizeValue = 160;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class HashAlgorithm : IDisposable
|
||||
{
|
||||
protected int HashSizeValue;
|
||||
protected internal byte[] HashValue;
|
||||
protected int State = 0;
|
||||
|
||||
private bool m_bDisposed = false;
|
||||
|
||||
protected HashAlgorithm() { }
|
||||
|
||||
//
|
||||
// public properties
|
||||
//
|
||||
|
||||
public virtual int HashSize
|
||||
{
|
||||
get { return HashSizeValue; }
|
||||
}
|
||||
|
||||
//
|
||||
// public methods
|
||||
//
|
||||
|
||||
public byte[] ComputeHash(Stream inputStream)
|
||||
{
|
||||
if (m_bDisposed)
|
||||
throw new ObjectDisposedException(null);
|
||||
|
||||
// Default the buffer size to 4K.
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
do
|
||||
{
|
||||
bytesRead = inputStream.Read(buffer, 0, 4096);
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
HashCore(buffer, 0, bytesRead);
|
||||
}
|
||||
} while (bytesRead > 0);
|
||||
|
||||
HashValue = HashFinal();
|
||||
byte[] Tmp = (byte[])HashValue.Clone();
|
||||
Initialize();
|
||||
return (Tmp);
|
||||
}
|
||||
|
||||
public byte[] ComputeHash(byte[] buffer)
|
||||
{
|
||||
if (m_bDisposed)
|
||||
throw new ObjectDisposedException(null);
|
||||
|
||||
// Do some validation
|
||||
if (buffer == null) throw new ArgumentNullException("buffer");
|
||||
|
||||
HashCore(buffer, 0, buffer.Length);
|
||||
HashValue = HashFinal();
|
||||
byte[] Tmp = (byte[])HashValue.Clone();
|
||||
Initialize();
|
||||
return (Tmp);
|
||||
}
|
||||
|
||||
// ICryptoTransform methods
|
||||
|
||||
// we assume any HashAlgorithm can take input a byte at a time
|
||||
public virtual int InputBlockSize
|
||||
{
|
||||
get { return (1); }
|
||||
}
|
||||
|
||||
public virtual int OutputBlockSize
|
||||
{
|
||||
get { return (1); }
|
||||
}
|
||||
|
||||
public virtual bool CanTransformMultipleBlocks
|
||||
{
|
||||
get { return (true); }
|
||||
}
|
||||
|
||||
public virtual bool CanReuseTransform
|
||||
{
|
||||
get { return (true); }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
(this as IDisposable).Dispose();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (HashValue != null)
|
||||
Array.Clear(HashValue, 0, HashValue.Length);
|
||||
HashValue = null;
|
||||
m_bDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// abstract public methods
|
||||
//
|
||||
|
||||
public abstract void Initialize();
|
||||
|
||||
protected abstract void HashCore(byte[] array, int ibStart, int cbSize);
|
||||
|
||||
protected abstract byte[] HashFinal();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class FileState {
|
||||
public string ObjectId { get; internal set; }
|
||||
public string Name { get; internal set; }
|
||||
public string CloudName { get; set; }
|
||||
public string MimeType { get; internal set; }
|
||||
public Uri Url { get; internal set; }
|
||||
public IDictionary<string, object> MetaData { get; internal set; }
|
||||
public long Size { get; internal set; }
|
||||
public long FixedChunkSize { get; internal set; }
|
||||
|
||||
public int counter;
|
||||
public Stream frozenData;
|
||||
public string bucketId;
|
||||
public string bucket;
|
||||
public string token;
|
||||
public long completed;
|
||||
public List<string> block_ctxes = new List<string>();
|
||||
|
||||
public static FileState NewFromDict(IDictionary<string, object> dict) {
|
||||
FileState state = new FileState {
|
||||
ObjectId = dict["objectId"] as string,
|
||||
Url = new Uri(dict["url"] as string, UriKind.Absolute)
|
||||
};
|
||||
if (dict.TryGetValue("name", out object name)) {
|
||||
state.Name = name as string;
|
||||
}
|
||||
if (dict.TryGetValue("metaData", out object metaData)) {
|
||||
state.MetaData = metaData as Dictionary<string, object>;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// 临时方案,后面将有 Android 和 iOS 提供 device token
|
||||
/// </summary>
|
||||
public class InstallationIdController {
|
||||
private string installationId;
|
||||
private readonly object mutex = new object();
|
||||
|
||||
public string Get() {
|
||||
if (installationId == null) {
|
||||
lock (mutex) {
|
||||
if (installationId == null) {
|
||||
string installationPath = "installation.conf";
|
||||
// 文件读取或从 Native 平台读取
|
||||
if (File.Exists(installationPath)) {
|
||||
using (StreamReader reader = new StreamReader(installationPath)) {
|
||||
installationId = reader.ReadToEnd();
|
||||
if (installationId != null) {
|
||||
return installationId;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 生成新的 device token
|
||||
Guid newInstallationId = Guid.NewGuid();
|
||||
installationId = newInstallationId.ToString();
|
||||
// 写回文件
|
||||
using (StreamWriter writer = new StreamWriter(installationPath)) {
|
||||
writer.Write(installationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return installationId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Utilities;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVObjectController {
|
||||
public 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: cancellationToken).OnSuccess(t => {
|
||||
return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance);
|
||||
});
|
||||
}
|
||||
|
||||
public Task<IObjectState> SaveAsync(IObjectState state,
|
||||
IDictionary<string, IAVFieldOperation> operations,
|
||||
bool fetchWhenSave,
|
||||
AVQuery<AVObject> query,
|
||||
CancellationToken cancellationToken) {
|
||||
var objectJSON = AVObject.ToJSONObjectForSaving(operations);
|
||||
|
||||
var command = new AVCommand {
|
||||
Path = state.ObjectId == null ? $"classes/{Uri.EscapeDataString(state.ClassName)}" : $"classes/{Uri.EscapeDataString(state.ClassName)}/{state.ObjectId}",
|
||||
Method = state.ObjectId == null ? HttpMethod.Post : HttpMethod.Put,
|
||||
Content = objectJSON
|
||||
};
|
||||
Dictionary<string, object> args = new Dictionary<string, object>();
|
||||
if (fetchWhenSave) {
|
||||
args.Add("fetchWhenSave", fetchWhenSave);
|
||||
}
|
||||
// 查询条件
|
||||
if (query != null && query.where != null) {
|
||||
args.Add("where", PointerOrLocalIdEncoder.Instance.Encode(query.where));
|
||||
}
|
||||
if (args.Count > 0) {
|
||||
string encode = AVClient.BuildQueryString(args);
|
||||
command.Path = $"{command.Path}?{encode}";
|
||||
}
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(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<Task<IObjectState>> SaveAllAsync(IList<IObjectState> states,
|
||||
IList<IDictionary<string, IAVFieldOperation>> 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)}",
|
||||
Method = item.ObjectId == null ? HttpMethod.Post : HttpMethod.Put,
|
||||
Content = AVObject.ToJSONObjectForSaving(ops)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var batchTasks = ExecuteBatchRequests(requests, cancellationToken);
|
||||
var stateTasks = new List<Task<IObjectState>>();
|
||||
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,
|
||||
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: cancellationToken);
|
||||
}
|
||||
|
||||
public IList<Task> DeleteAllAsync(IList<IObjectState> states,
|
||||
CancellationToken cancellationToken) {
|
||||
var requests = states
|
||||
.Where(item => item.ObjectId != null)
|
||||
.Select(item => new AVCommand {
|
||||
Path = $"classes/{Uri.EscapeDataString(item.ClassName)}/{Uri.EscapeDataString(item.ObjectId)}",
|
||||
Method = HttpMethod.Delete
|
||||
})
|
||||
.ToList();
|
||||
return ExecuteBatchRequests(requests, cancellationToken).Cast<Task>().ToList();
|
||||
}
|
||||
|
||||
// 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>>>();
|
||||
int batchSize = requests.Count;
|
||||
|
||||
IEnumerable<AVCommand> 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 IList<Task<IDictionary<string, object>>> ExecuteBatchRequest(IList<AVCommand> requests,
|
||||
CancellationToken cancellationToken) {
|
||||
var tasks = new List<Task<IDictionary<string, object>>>();
|
||||
int batchSize = requests.Count;
|
||||
var tcss = new List<TaskCompletionSource<IDictionary<string, object>>>();
|
||||
for (int i = 0; i < batchSize; ++i) {
|
||||
var tcs = new TaskCompletionSource<IDictionary<string, object>>();
|
||||
tcss.Add(tcs);
|
||||
tasks.Add(tcs.Task);
|
||||
}
|
||||
|
||||
var encodedRequests = requests.Select(r => {
|
||||
var results = new Dictionary<string, object> {
|
||||
{ "method", r.Method },
|
||||
{ "path", r.Path },
|
||||
};
|
||||
|
||||
if (r.Content != null) {
|
||||
results["body"] = r.Content;
|
||||
}
|
||||
return results;
|
||||
}).Cast<object>().ToList();
|
||||
var command = new AVCommand {
|
||||
Path = "batch",
|
||||
Method = HttpMethod.Post,
|
||||
Content = new Dictionary<string, object> {
|
||||
{ "requests", encodedRequests }
|
||||
}
|
||||
};
|
||||
AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(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<IList<object>>(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<string, object>;
|
||||
var tcs = tcss[i];
|
||||
|
||||
if (result.ContainsKey("success")) {
|
||||
tcs.TrySetResult(result["success"] as IDictionary<string, object>);
|
||||
} else if (result.ContainsKey("error")) {
|
||||
var error = result["error"] as IDictionary<string, object>;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IObjectState : IEnumerable<KeyValuePair<string, object>>
|
||||
{
|
||||
bool IsNew { get; }
|
||||
string ClassName { get; }
|
||||
string ObjectId { get; }
|
||||
DateTime? UpdatedAt { get; }
|
||||
DateTime? CreatedAt { get; }
|
||||
object this[string key] { get; }
|
||||
|
||||
bool ContainsKey(string key);
|
||||
|
||||
IObjectState MutatedClone(Action<MutableObjectState> func);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class MutableObjectState : IObjectState
|
||||
{
|
||||
public bool IsNew { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
public string ObjectId { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
|
||||
// Initialize serverData to avoid further null checking.
|
||||
private IDictionary<string, object> serverData = new Dictionary<string, object>();
|
||||
public IDictionary<string, object> ServerData
|
||||
{
|
||||
get
|
||||
{
|
||||
return serverData;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
serverData = value;
|
||||
}
|
||||
}
|
||||
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServerData[key];
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return ServerData.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void Apply(IDictionary<string, IAVFieldOperation> operationSet)
|
||||
{
|
||||
// Apply 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)
|
||||
{
|
||||
ServerData[pair.Key] = newValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerData.Remove(pair.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Apply(IObjectState other)
|
||||
{
|
||||
IsNew = other.IsNew;
|
||||
if (other.ObjectId != null)
|
||||
{
|
||||
ObjectId = other.ObjectId;
|
||||
}
|
||||
if (other.UpdatedAt != null)
|
||||
{
|
||||
UpdatedAt = other.UpdatedAt;
|
||||
}
|
||||
if (other.CreatedAt != null)
|
||||
{
|
||||
CreatedAt = other.CreatedAt;
|
||||
}
|
||||
|
||||
foreach (var pair in other)
|
||||
{
|
||||
ServerData[pair.Key] = pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public IObjectState MutatedClone(Action<MutableObjectState> func)
|
||||
{
|
||||
var clone = MutableClone();
|
||||
func(clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
protected virtual MutableObjectState MutableClone()
|
||||
{
|
||||
return new MutableObjectState
|
||||
{
|
||||
IsNew = IsNew,
|
||||
ClassName = ClassName,
|
||||
ObjectId = ObjectId,
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt,
|
||||
ServerData = this.ToDictionary(t => t.Key, t => t.Value)
|
||||
};
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
|
||||
{
|
||||
return ServerData.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<KeyValuePair<string, object>>)this).GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
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)
|
||||
{
|
||||
TypeInfo = type.GetTypeInfo();
|
||||
ClassName = GetClassName(TypeInfo);
|
||||
Constructor = constructor;
|
||||
PropertyMappings = ReflectionHelpers.GetProperties(type)
|
||||
.Select(prop => Tuple.Create(prop, prop.GetCustomAttribute<AVFieldNameAttribute>(true)))
|
||||
.Where(t => t.Item2 != null)
|
||||
.Select(t => Tuple.Create(t.Item1, t.Item2.FieldName))
|
||||
.ToDictionary(t => t.Item1.Name, t => t.Item2);
|
||||
}
|
||||
|
||||
public TypeInfo TypeInfo { get; private set; }
|
||||
public string ClassName { get; private set; }
|
||||
public IDictionary<string, string> PropertyMappings { get; private set; }
|
||||
private ConstructorInfo Constructor { get; set; }
|
||||
|
||||
public AVObject Instantiate()
|
||||
{
|
||||
return (AVObject)Constructor.Invoke(null);
|
||||
}
|
||||
|
||||
internal static string GetClassName(TypeInfo type)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<AVClassNameAttribute>();
|
||||
return attribute != null ? attribute.ClassName : null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class ObjectSubclassingController {
|
||||
// Class names starting with _ are documented to be reserved. Use this one
|
||||
// here to allow us to 'inherit' certain properties.
|
||||
private static readonly string avObjectClassName = "_AVObject";
|
||||
|
||||
private readonly ReaderWriterLockSlim mutex;
|
||||
private readonly IDictionary<string, ObjectSubclassInfo> registeredSubclasses;
|
||||
private Dictionary<string, Action> registerActions;
|
||||
|
||||
public ObjectSubclassingController() {
|
||||
mutex = new ReaderWriterLockSlim();
|
||||
registeredSubclasses = new Dictionary<string, ObjectSubclassInfo>();
|
||||
registerActions = new Dictionary<string, Action>();
|
||||
|
||||
// Register the AVObject subclass, so we get access to the ACL,
|
||||
// objectId, and other AVFieldName properties.
|
||||
RegisterSubclass(typeof(AVObject));
|
||||
}
|
||||
|
||||
public string GetClassName(Type type) {
|
||||
return type == typeof(AVObject)
|
||||
? avObjectClassName
|
||||
: ObjectSubclassInfo.GetClassName(type.GetTypeInfo());
|
||||
}
|
||||
|
||||
public Type GetType(string className) {
|
||||
mutex.EnterReadLock();
|
||||
registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return info?.TypeInfo.AsType();
|
||||
}
|
||||
|
||||
public bool IsTypeValid(string className, Type type) {
|
||||
ObjectSubclassInfo subclassInfo = null;
|
||||
|
||||
mutex.EnterReadLock();
|
||||
registeredSubclasses.TryGetValue(className, out subclassInfo);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return subclassInfo == null
|
||||
? type == typeof(AVObject)
|
||||
: subclassInfo.TypeInfo == type.GetTypeInfo();
|
||||
}
|
||||
|
||||
public void RegisterSubclass(Type type) {
|
||||
TypeInfo typeInfo = type.GetTypeInfo();
|
||||
if (!typeof(AVObject).GetTypeInfo().IsAssignableFrom(typeInfo)) {
|
||||
throw new ArgumentException("Cannot register a type that is not a subclass of AVObject");
|
||||
}
|
||||
|
||||
string className = GetClassName(type);
|
||||
|
||||
try {
|
||||
// Perform this as a single independent transaction, so we can never get into an
|
||||
// intermediate state where we *theoretically* register the wrong class due to a
|
||||
// TOCTTOU bug.
|
||||
mutex.EnterWriteLock();
|
||||
|
||||
ObjectSubclassInfo previousInfo = null;
|
||||
if (registeredSubclasses.TryGetValue(className, out 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)) {
|
||||
// Previous subclass is parent of new child, fallthrough and actually register
|
||||
// this class.
|
||||
/* Do nothing */
|
||||
} else {
|
||||
throw new ArgumentException(
|
||||
"Tried to register both " + previousInfo.TypeInfo.FullName + " and " + typeInfo.FullName +
|
||||
" as the AVObject subclass of " + className + ". Cannot determine the right class " +
|
||||
"to use because neither inherits from the other."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConstructorInfo constructor = type.FindConstructor();
|
||||
if (constructor == null) {
|
||||
throw new ArgumentException("Cannot register a type that does not implement the default constructor!");
|
||||
}
|
||||
|
||||
registeredSubclasses[className] = new ObjectSubclassInfo(type, constructor);
|
||||
} finally {
|
||||
mutex.ExitWriteLock();
|
||||
}
|
||||
|
||||
Action toPerform;
|
||||
|
||||
mutex.EnterReadLock();
|
||||
registerActions.TryGetValue(className, out toPerform);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
toPerform?.Invoke();
|
||||
}
|
||||
|
||||
public void UnregisterSubclass(Type type) {
|
||||
mutex.EnterWriteLock();
|
||||
registeredSubclasses.Remove(GetClassName(type));
|
||||
mutex.ExitWriteLock();
|
||||
}
|
||||
|
||||
public void AddRegisterHook(Type t, Action action) {
|
||||
mutex.EnterWriteLock();
|
||||
registerActions.Add(GetClassName(t), action);
|
||||
mutex.ExitWriteLock();
|
||||
}
|
||||
|
||||
public AVObject Instantiate(string className) {
|
||||
ObjectSubclassInfo info = null;
|
||||
|
||||
mutex.EnterReadLock();
|
||||
registeredSubclasses.TryGetValue(className, out info);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return info != null
|
||||
? info.Instantiate()
|
||||
: new AVObject(className);
|
||||
}
|
||||
|
||||
public IDictionary<string, string> GetPropertyMappings(string className) {
|
||||
mutex.EnterReadLock();
|
||||
if (!registeredSubclasses.TryGetValue(className, out ObjectSubclassInfo info)) {
|
||||
_ = registeredSubclasses.TryGetValue(avObjectClassName, out info);
|
||||
}
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return info.PropertyMappings;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVAddOperation : IAVFieldOperation {
|
||||
private ReadOnlyCollection<object> objects;
|
||||
public AVAddOperation(IEnumerable<object> objects) {
|
||||
this.objects = new ReadOnlyCollection<object>(objects.ToList());
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{"__op", "Add"},
|
||||
{"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)}
|
||||
};
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) {
|
||||
if (previous == null) {
|
||||
return this;
|
||||
}
|
||||
if (previous is AVDeleteOperation) {
|
||||
return new AVSetOperation(objects.ToList());
|
||||
}
|
||||
if (previous is AVSetOperation) {
|
||||
var setOp = (AVSetOperation)previous;
|
||||
var oldList = Conversion.To<IList<object>>(setOp.Value);
|
||||
return new AVSetOperation(oldList.Concat(objects).ToList());
|
||||
}
|
||||
if (previous is AVAddOperation) {
|
||||
return new AVAddOperation(((AVAddOperation)previous).Objects.Concat(objects));
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
if (oldValue == null) {
|
||||
return objects.ToList();
|
||||
}
|
||||
var oldList = Conversion.To<IList<object>>(oldValue);
|
||||
return oldList.Concat(objects).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<object> Objects {
|
||||
get {
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVAddUniqueOperation : IAVFieldOperation {
|
||||
private ReadOnlyCollection<object> objects;
|
||||
public AVAddUniqueOperation(IEnumerable<object> objects) {
|
||||
this.objects = new ReadOnlyCollection<object>(objects.Distinct().ToList());
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{"__op", "AddUnique"},
|
||||
{"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)}
|
||||
};
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) {
|
||||
if (previous == null) {
|
||||
return this;
|
||||
}
|
||||
if (previous is AVDeleteOperation) {
|
||||
return new AVSetOperation(objects.ToList());
|
||||
}
|
||||
if (previous is AVSetOperation) {
|
||||
var setOp = (AVSetOperation)previous;
|
||||
var oldList = Conversion.To<IList<object>>(setOp.Value);
|
||||
var result = this.Apply(oldList, null);
|
||||
return new AVSetOperation(result);
|
||||
}
|
||||
if (previous is AVAddUniqueOperation) {
|
||||
var oldList = ((AVAddUniqueOperation)previous).Objects;
|
||||
return new AVAddUniqueOperation((IList<object>)this.Apply(oldList, null));
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
if (oldValue == null) {
|
||||
return objects.ToList();
|
||||
}
|
||||
var newList = Conversion.To<IList<object>>(oldValue).ToList();
|
||||
var comparer = AVFieldOperations.AVObjectComparer;
|
||||
foreach (var objToAdd in objects) {
|
||||
if (objToAdd is AVObject) {
|
||||
var matchedObj = newList.FirstOrDefault(listObj => comparer.Equals(objToAdd, listObj));
|
||||
if (matchedObj == null) {
|
||||
newList.Add(objToAdd);
|
||||
} else {
|
||||
var index = newList.IndexOf(matchedObj);
|
||||
newList[index] = objToAdd;
|
||||
}
|
||||
} else if (!newList.Contains<object>(objToAdd, comparer)) {
|
||||
newList.Add(objToAdd);
|
||||
}
|
||||
}
|
||||
return newList;
|
||||
}
|
||||
|
||||
public IEnumerable<object> Objects {
|
||||
get {
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// An operation where a field is deleted from the object.
|
||||
/// </summary>
|
||||
public class AVDeleteOperation : IAVFieldOperation
|
||||
{
|
||||
internal static readonly object DeleteToken = new object();
|
||||
private static AVDeleteOperation _Instance = new AVDeleteOperation();
|
||||
public static AVDeleteOperation Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Instance;
|
||||
}
|
||||
}
|
||||
|
||||
private AVDeleteOperation() { }
|
||||
public object Encode()
|
||||
{
|
||||
return new Dictionary<string, object> {
|
||||
{"__op", "Delete"}
|
||||
};
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key)
|
||||
{
|
||||
return DeleteToken;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVObjectIdComparer : IEqualityComparer<object> {
|
||||
bool IEqualityComparer<object>.Equals(object p1, object p2) {
|
||||
var avObj1 = p1 as AVObject;
|
||||
var avObj2 = p2 as AVObject;
|
||||
if (avObj1 != null && avObj2 != null) {
|
||||
return object.Equals(avObj1.ObjectId, avObj2.ObjectId);
|
||||
}
|
||||
return object.Equals(p1, p2);
|
||||
}
|
||||
|
||||
public int GetHashCode(object p) {
|
||||
var avObject = p as AVObject;
|
||||
if (avObject != null) {
|
||||
return avObject.ObjectId.GetHashCode();
|
||||
}
|
||||
return p.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
static class AVFieldOperations {
|
||||
private static AVObjectIdComparer comparer;
|
||||
|
||||
public static IAVFieldOperation Decode(IDictionary<string, object> json) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static IEqualityComparer<object> AVObjectComparer {
|
||||
get {
|
||||
if (comparer == null) {
|
||||
comparer = new AVObjectIdComparer();
|
||||
}
|
||||
return comparer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVIncrementOperation : IAVFieldOperation
|
||||
{
|
||||
private static readonly IDictionary<Tuple<Type, Type>, Func<object, object, object>> adders;
|
||||
|
||||
static AVIncrementOperation()
|
||||
{
|
||||
// Defines adders for all of the implicit conversions: http://msdn.microsoft.com/en-US/library/y5b434w4(v=vs.80).aspx
|
||||
adders = new Dictionary<Tuple<Type, Type>, Func<object, object, object>> {
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(sbyte)), (left, right) => (sbyte)left + (sbyte)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(short)), (left, right) => (sbyte)left + (short)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(int)), (left, right) => (sbyte)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(long)), (left, right) => (sbyte)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(float)), (left, right) => (sbyte)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(double)), (left, right) => (sbyte)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(decimal)), (left, right) => (sbyte)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(byte)), (left, right) => (byte)left + (byte)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(short)), (left, right) => (byte)left + (short)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(ushort)), (left, right) => (byte)left + (ushort)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(int)), (left, right) => (byte)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(uint)), (left, right) => (byte)left + (uint)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(long)), (left, right) => (byte)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(ulong)), (left, right) => (byte)left + (ulong)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(float)), (left, right) => (byte)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(double)), (left, right) => (byte)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(decimal)), (left, right) => (byte)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(short)), (left, right) => (short)left + (short)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(int)), (left, right) => (short)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(long)), (left, right) => (short)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(float)), (left, right) => (short)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(double)), (left, right) => (short)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(decimal)), (left, right) => (short)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(ushort)), (left, right) => (ushort)left + (ushort)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(int)), (left, right) => (ushort)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(uint)), (left, right) => (ushort)left + (uint)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(long)), (left, right) => (ushort)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(ulong)), (left, right) => (ushort)left + (ulong)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(float)), (left, right) => (ushort)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(double)), (left, right) => (ushort)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(decimal)), (left, right) => (ushort)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(int), typeof(int)), (left, right) => (int)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(int), typeof(long)), (left, right) => (int)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(int), typeof(float)), (left, right) => (int)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(int), typeof(double)), (left, right) => (int)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(int), typeof(decimal)), (left, right) => (int)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(uint)), (left, right) => (uint)left + (uint)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(long)), (left, right) => (uint)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(ulong)), (left, right) => (uint)left + (ulong)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(float)), (left, right) => (uint)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(double)), (left, right) => (uint)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(decimal)), (left, right) => (uint)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(long), typeof(long)), (left, right) => (long)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(long), typeof(float)), (left, right) => (long)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(long), typeof(double)), (left, right) => (long)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(long), typeof(decimal)), (left, right) => (long)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(char)), (left, right) => (char)left + (char)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(ushort)), (left, right) => (char)left + (ushort)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(int)), (left, right) => (char)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(uint)), (left, right) => (char)left + (uint)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(long)), (left, right) => (char)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(ulong)), (left, right) => (char)left + (ulong)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(float)), (left, right) => (char)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(double)), (left, right) => (char)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(decimal)), (left, right) => (char)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(float), typeof(float)), (left, right) => (float)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(float), typeof(double)), (left, right) => (float)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(ulong), typeof(ulong)), (left, right) => (ulong)left + (ulong)right},
|
||||
{new Tuple<Type, Type>(typeof(ulong), typeof(float)), (left, right) => (ulong)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(ulong), typeof(double)), (left, right) => (ulong)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(ulong), typeof(decimal)), (left, right) => (ulong)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(double), typeof(double)), (left, right) => (double)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(decimal), typeof(decimal)), (left, right) => (decimal)left + (decimal)right}
|
||||
};
|
||||
// Generate the adders in the other direction
|
||||
foreach (var pair in adders.Keys.ToList())
|
||||
{
|
||||
if (pair.Item1.Equals(pair.Item2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var reversePair = new Tuple<Type, Type>(pair.Item2, pair.Item1);
|
||||
var func = adders[pair];
|
||||
adders[reversePair] = (left, right) => func(right, left);
|
||||
}
|
||||
}
|
||||
|
||||
private object amount;
|
||||
|
||||
public AVIncrementOperation(object amount)
|
||||
{
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public object Encode()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{"__op", "Increment"},
|
||||
{"amount", amount}
|
||||
};
|
||||
}
|
||||
|
||||
private static object Add(object obj1, object obj2)
|
||||
{
|
||||
Func<object, object, object> adder;
|
||||
if (adders.TryGetValue(new Tuple<Type, Type>(obj1.GetType(), obj2.GetType()), out adder))
|
||||
{
|
||||
return adder(obj1, obj2);
|
||||
}
|
||||
throw new InvalidCastException("Cannot add " + obj1.GetType() + " to " + obj2.GetType());
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous)
|
||||
{
|
||||
if (previous == null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
if (previous is AVDeleteOperation)
|
||||
{
|
||||
return new AVSetOperation(amount);
|
||||
}
|
||||
if (previous is AVSetOperation)
|
||||
{
|
||||
var otherAmount = ((AVSetOperation)previous).Value;
|
||||
if (otherAmount is string)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot increment a non-number type.");
|
||||
}
|
||||
var myAmount = amount;
|
||||
return new AVSetOperation(Add(otherAmount, myAmount));
|
||||
}
|
||||
if (previous is AVIncrementOperation)
|
||||
{
|
||||
object otherAmount = ((AVIncrementOperation)previous).Amount;
|
||||
object myAmount = amount;
|
||||
return new AVIncrementOperation(Add(otherAmount, myAmount));
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key)
|
||||
{
|
||||
if (oldValue is string)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot increment a non-number type.");
|
||||
}
|
||||
object otherAmount = oldValue ?? 0;
|
||||
object myAmount = amount;
|
||||
return Add(otherAmount, myAmount);
|
||||
}
|
||||
|
||||
public object Amount
|
||||
{
|
||||
get
|
||||
{
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVRelationOperation : IAVFieldOperation {
|
||||
private readonly IList<string> adds;
|
||||
private readonly IList<string> removes;
|
||||
private readonly string targetClassName;
|
||||
|
||||
private AVRelationOperation(IEnumerable<string> adds,
|
||||
IEnumerable<string> removes,
|
||||
string targetClassName) {
|
||||
this.targetClassName = targetClassName;
|
||||
this.adds = new ReadOnlyCollection<string>(adds.ToList());
|
||||
this.removes = new ReadOnlyCollection<string>(removes.ToList());
|
||||
}
|
||||
|
||||
public AVRelationOperation(IEnumerable<AVObject> adds,
|
||||
IEnumerable<AVObject> removes) {
|
||||
adds = adds ?? new AVObject[0];
|
||||
removes = removes ?? new AVObject[0];
|
||||
this.targetClassName = adds.Concat(removes).Select(o => o.ClassName).FirstOrDefault();
|
||||
this.adds = new ReadOnlyCollection<string>(IdsFromObjects(adds).ToList());
|
||||
this.removes = new ReadOnlyCollection<string>(IdsFromObjects(removes).ToList());
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
var adds = this.adds
|
||||
.Select(id => PointerOrLocalIdEncoder.Instance.Encode(
|
||||
AVObject.CreateWithoutData(targetClassName, id)))
|
||||
.ToList();
|
||||
var removes = this.removes
|
||||
.Select(id => PointerOrLocalIdEncoder.Instance.Encode(
|
||||
AVObject.CreateWithoutData(targetClassName, id)))
|
||||
.ToList();
|
||||
var addDict = adds.Count == 0 ? null : new Dictionary<string, object> {
|
||||
{"__op", "AddRelation"},
|
||||
{"objects", adds}
|
||||
};
|
||||
var removeDict = removes.Count == 0 ? null : new Dictionary<string, object> {
|
||||
{"__op", "RemoveRelation"},
|
||||
{"objects", removes}
|
||||
};
|
||||
|
||||
if (addDict != null && removeDict != null) {
|
||||
return new Dictionary<string, object> {
|
||||
{"__op", "Batch"},
|
||||
{"ops", new[] {addDict, removeDict}}
|
||||
};
|
||||
}
|
||||
return addDict ?? removeDict;
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) {
|
||||
if (previous == null) {
|
||||
return this;
|
||||
}
|
||||
if (previous is AVDeleteOperation) {
|
||||
throw new InvalidOperationException("You can't modify a relation after deleting it.");
|
||||
}
|
||||
var other = previous as AVRelationOperation;
|
||||
if (other != null) {
|
||||
if (other.TargetClassName != TargetClassName) {
|
||||
throw new InvalidOperationException(
|
||||
string.Format("Related object must be of class {0}, but {1} was passed in.",
|
||||
other.TargetClassName,
|
||||
TargetClassName));
|
||||
}
|
||||
var newAdd = adds.Union(other.adds.Except(removes)).ToList();
|
||||
var newRemove = removes.Union(other.removes.Except(adds)).ToList();
|
||||
return new AVRelationOperation(newAdd, newRemove, TargetClassName);
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
if (adds.Count == 0 && removes.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
if (oldValue == null) {
|
||||
return AVRelationBase.CreateRelation(null, key, targetClassName);
|
||||
}
|
||||
if (oldValue is AVRelationBase) {
|
||||
var oldRelation = (AVRelationBase)oldValue;
|
||||
var oldClassName = oldRelation.TargetClassName;
|
||||
if (oldClassName != null && oldClassName != targetClassName) {
|
||||
throw new InvalidOperationException("Related object must be a " + oldClassName
|
||||
+ ", but a " + targetClassName + " was passed in.");
|
||||
}
|
||||
oldRelation.TargetClassName = targetClassName;
|
||||
return oldRelation;
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public string TargetClassName { get { return targetClassName; } }
|
||||
|
||||
private IEnumerable<string> IdsFromObjects(IEnumerable<AVObject> objects) {
|
||||
foreach (var obj in objects) {
|
||||
if (obj.ObjectId == null) {
|
||||
throw new ArgumentException(
|
||||
"You can't add an unsaved AVObject to a relation.");
|
||||
}
|
||||
if (obj.ClassName != targetClassName) {
|
||||
throw new ArgumentException(string.Format(
|
||||
"Tried to create a AVRelation with 2 different types: {0} and {1}",
|
||||
targetClassName,
|
||||
obj.ClassName));
|
||||
}
|
||||
}
|
||||
return objects.Select(o => o.ObjectId).Distinct();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVRemoveOperation : IAVFieldOperation
|
||||
{
|
||||
private ReadOnlyCollection<object> objects;
|
||||
public AVRemoveOperation(IEnumerable<object> objects)
|
||||
{
|
||||
this.objects = new ReadOnlyCollection<object>(objects.Distinct().ToList());
|
||||
}
|
||||
|
||||
public object Encode()
|
||||
{
|
||||
return new Dictionary<string, object> {
|
||||
{"__op", "Remove"},
|
||||
{"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)}
|
||||
};
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous)
|
||||
{
|
||||
if (previous == null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
if (previous is AVDeleteOperation)
|
||||
{
|
||||
return previous;
|
||||
}
|
||||
if (previous is AVSetOperation)
|
||||
{
|
||||
var setOp = (AVSetOperation)previous;
|
||||
var oldList = Conversion.As<IList<object>>(setOp.Value);
|
||||
return new AVSetOperation(this.Apply(oldList, null));
|
||||
}
|
||||
if (previous is AVRemoveOperation)
|
||||
{
|
||||
var oldOp = (AVRemoveOperation)previous;
|
||||
return new AVRemoveOperation(oldOp.Objects.Concat(objects));
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key)
|
||||
{
|
||||
if (oldValue == null)
|
||||
{
|
||||
return new List<object>();
|
||||
}
|
||||
var oldList = Conversion.As<IList<object>>(oldValue);
|
||||
return oldList.Except(objects, AVFieldOperations.AVObjectComparer).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<object> Objects
|
||||
{
|
||||
get
|
||||
{
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVSetOperation : IAVFieldOperation {
|
||||
public AVSetOperation(object value) {
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return PointerOrLocalIdEncoder.Instance.Encode(Value);
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
return Value;
|
||||
}
|
||||
|
||||
public object Value { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// A AVFieldOperation represents a modification to a value in a AVObject.
|
||||
/// For example, setting, deleting, or incrementing a value are all different kinds of
|
||||
/// AVFieldOperations. AVFieldOperations themselves can be considered to be
|
||||
/// immutable.
|
||||
/// </summary>
|
||||
public interface IAVFieldOperation {
|
||||
/// <summary>
|
||||
/// Converts the AVFieldOperation to a data structure that can be converted to JSON and sent to
|
||||
/// LeanCloud as part of a save operation.
|
||||
/// </summary>
|
||||
/// <returns>An object to be JSONified.</returns>
|
||||
object Encode();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a field operation that is composed of a previous operation followed by
|
||||
/// this operation. This will not mutate either operation. However, it may return
|
||||
/// <code>this</code> if the current operation is not affected by previous changes.
|
||||
/// For example:
|
||||
/// {increment by 2}.MergeWithPrevious({set to 5}) -> {set to 7}
|
||||
/// {set to 5}.MergeWithPrevious({increment by 2}) -> {set to 5}
|
||||
/// {add "foo"}.MergeWithPrevious({delete}) -> {set to ["foo"]}
|
||||
/// {delete}.MergeWithPrevious({add "foo"}) -> {delete} /// </summary>
|
||||
/// <param name="previous">The most recent operation on the field, or null if none.</param>
|
||||
/// <returns>A new AVFieldOperation or this.</returns>
|
||||
IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new estimated value based on a previous value and this operation. This
|
||||
/// value is not intended to be sent to LeanCloud, but it is used locally on the client to
|
||||
/// inspect the most likely current value for a field.
|
||||
///
|
||||
/// The key and object are used solely for AVRelation to be able to construct objects
|
||||
/// that refer back to their parents.
|
||||
/// </summary>
|
||||
/// <param name="oldValue">The previous value for the field.</param>
|
||||
/// <param name="key">The key that this value is for.</param>
|
||||
/// <returns>The new value for the field.</returns>
|
||||
object Apply(object oldValue, string key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVQueryController {
|
||||
public async Task<IEnumerable<IObjectState>> FindAsync<T>(AVQuery<T> query, AVUser user,
|
||||
CancellationToken cancellationToken) where T : AVObject {
|
||||
IList<object> items = await FindAsync<IList<object>>(query.Path, query.BuildParameters(), "results", cancellationToken);
|
||||
return from item in items
|
||||
select AVObjectCoder.Instance.Decode(item as IDictionary<string, object>, AVDecoder.Instance);
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync<T>(AVQuery<T> query,
|
||||
AVUser user,
|
||||
CancellationToken cancellationToken) where T : AVObject {
|
||||
var parameters = query.BuildParameters();
|
||||
parameters["limit"] = 0;
|
||||
parameters["count"] = 1;
|
||||
long ret = await FindAsync<long>(query.Path, parameters, "count", cancellationToken);
|
||||
return Convert.ToInt32(ret);
|
||||
}
|
||||
|
||||
public async Task<IObjectState> FirstAsync<T>(AVQuery<T> query,
|
||||
AVUser user,
|
||||
CancellationToken cancellationToken) where T : AVObject {
|
||||
var parameters = query.BuildParameters();
|
||||
parameters["limit"] = 1;
|
||||
IList<object> items = await FindAsync<IList<object>>(query.Path, query.BuildParameters(), "results", cancellationToken);
|
||||
// Not found. Return empty state.
|
||||
if (!(items.FirstOrDefault() is IDictionary<string, object> item)) {
|
||||
return (IObjectState)null;
|
||||
}
|
||||
return AVObjectCoder.Instance.Decode(item, AVDecoder.Instance);
|
||||
}
|
||||
|
||||
private async Task<T> FindAsync<T>(string path,
|
||||
IDictionary<string, object> parameters,
|
||||
string key,
|
||||
CancellationToken cancellationToken) {
|
||||
var command = new AVCommand {
|
||||
Path = $"{path}?{AVClient.BuildQueryString(parameters)}",
|
||||
Method = HttpMethod.Get
|
||||
};
|
||||
var result = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken: cancellationToken);
|
||||
return (T)result.Item2[key];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVUserController {
|
||||
public async Task<IObjectState> SignUpAsync(IObjectState state, IDictionary<string, IAVFieldOperation> operations) {
|
||||
var objectJSON = AVObject.ToJSONObjectForSaving(operations);
|
||||
var command = new AVCommand {
|
||||
Path = "classes/_User",
|
||||
Method = HttpMethod.Post,
|
||||
Content = objectJSON
|
||||
};
|
||||
var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
var serverState = AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance);
|
||||
serverState = serverState.MutatedClone(mutableClone => {
|
||||
mutableClone.IsNew = true;
|
||||
});
|
||||
return serverState;
|
||||
}
|
||||
|
||||
public async Task<IObjectState> LogInAsync(string username, string email, string password) {
|
||||
var data = new Dictionary<string, object>{
|
||||
{ "password", password}
|
||||
};
|
||||
if (username != null) {
|
||||
data.Add("username", username);
|
||||
}
|
||||
if (email != null) {
|
||||
data.Add("email", email);
|
||||
}
|
||||
var command = new AVCommand {
|
||||
Path = "login",
|
||||
Method = HttpMethod.Post,
|
||||
Content = data
|
||||
};
|
||||
var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
var serverState = AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance);
|
||||
serverState = serverState.MutatedClone(mutableClone => {
|
||||
mutableClone.IsNew = ret.Item1 == System.Net.HttpStatusCode.Created;
|
||||
});
|
||||
return serverState;
|
||||
}
|
||||
|
||||
public async Task<IObjectState> LogInAsync(string authType, IDictionary<string, object> data, bool failOnNotExist) {
|
||||
var authData = new Dictionary<string, object> {
|
||||
[authType] = data
|
||||
};
|
||||
var path = failOnNotExist ? "users?failOnNotExist=true" : "users";
|
||||
var command = new AVCommand {
|
||||
Path = path,
|
||||
Method = HttpMethod.Post,
|
||||
Content = new Dictionary<string, object> {
|
||||
{ "authData", authData}
|
||||
}
|
||||
};
|
||||
var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
var serverState = AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance);
|
||||
serverState = serverState.MutatedClone(mutableClone => {
|
||||
mutableClone.IsNew = ret.Item1 == System.Net.HttpStatusCode.Created;
|
||||
});
|
||||
return serverState;
|
||||
}
|
||||
|
||||
public async Task<IObjectState> GetUserAsync(string sessionToken) {
|
||||
var command = new AVCommand {
|
||||
Path = "users/me",
|
||||
Method = HttpMethod.Get,
|
||||
Headers = new Dictionary<string, string> {
|
||||
{ "X-LC-Session", sessionToken }
|
||||
}
|
||||
};
|
||||
var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
return AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance);
|
||||
}
|
||||
|
||||
public async Task RequestPasswordResetAsync(string email) {
|
||||
var command = new AVCommand {
|
||||
Path = "requestPasswordReset",
|
||||
Method = HttpMethod.Post,
|
||||
Content = new Dictionary<string, object> {
|
||||
{ "email", email}
|
||||
}
|
||||
};
|
||||
await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
}
|
||||
|
||||
public async Task<IObjectState> LogInWithParametersAsync(string relativeUrl, IDictionary<string, object> data) {
|
||||
var command = new AVCommand {
|
||||
Path = relativeUrl,
|
||||
Method = HttpMethod.Post,
|
||||
Content = data
|
||||
};
|
||||
var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
var serverState = AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance);
|
||||
serverState = serverState.MutatedClone(mutableClone => {
|
||||
mutableClone.IsNew = ret.Item1 == System.Net.HttpStatusCode.Created;
|
||||
});
|
||||
return serverState;
|
||||
}
|
||||
|
||||
public async Task<IObjectState> UpdatePasswordAsync(string userId, string oldPassword, string newPassword) {
|
||||
var command = new AVCommand {
|
||||
Path = $"users/{userId}/updatePassword",
|
||||
Method = HttpMethod.Put,
|
||||
Content = new Dictionary<string, object> {
|
||||
{ "old_password", oldPassword },
|
||||
{ "new_password", newPassword },
|
||||
}
|
||||
};
|
||||
var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
return AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance);
|
||||
}
|
||||
|
||||
public async Task<IObjectState> RefreshSessionTokenAsync(string userId) {
|
||||
var command = new AVCommand {
|
||||
Path = $"users/{userId}/refreshSessionToken",
|
||||
Method = HttpMethod.Put
|
||||
};
|
||||
var ret = await AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
var serverState = AVObjectCoder.Instance.Decode(ret.Item2, AVDecoder.Instance);
|
||||
return serverState;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static class AVFileExtensions {
|
||||
public static AVFile Create(string name, Uri uri, string mimeType = null) {
|
||||
return new AVFile(name, uri, mimeType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using System.ComponentModel;
|
||||
using System.Collections;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static class AVObjectExtensions
|
||||
{
|
||||
public static T FromState<T>(IObjectState state, string defaultClassName) where T : AVObject
|
||||
{
|
||||
return AVObject.FromState<T>(state, defaultClassName);
|
||||
}
|
||||
|
||||
public static IObjectState GetState(this AVObject obj)
|
||||
{
|
||||
return obj.State;
|
||||
}
|
||||
|
||||
public static void HandleFetchResult(this AVObject obj, IObjectState serverState)
|
||||
{
|
||||
obj.HandleFetchResult(serverState);
|
||||
}
|
||||
|
||||
public static IDictionary<string, IAVFieldOperation> GetCurrentOperations(this AVObject obj)
|
||||
{
|
||||
return obj.CurrentOperations;
|
||||
}
|
||||
|
||||
public static IDictionary<string, object> Encode(this AVObject obj)
|
||||
{
|
||||
return PointerOrLocalIdEncoder.Instance.EncodeAVObject(obj, false);
|
||||
}
|
||||
|
||||
public static IEnumerable<object> DeepTraversal(object root, bool traverseAVObjects = false, bool yieldRoot = false)
|
||||
{
|
||||
return AVObject.DeepTraversal(root, traverseAVObjects, yieldRoot);
|
||||
}
|
||||
|
||||
public static void SetIfDifferent<T>(this AVObject obj, string key, T value)
|
||||
{
|
||||
obj.SetIfDifferent<T>(key, value);
|
||||
}
|
||||
|
||||
public static IDictionary<string, object> ServerDataToJSONObjectForSerialization(this AVObject obj)
|
||||
{
|
||||
return obj.ServerDataToJSONObjectForSerialization();
|
||||
}
|
||||
|
||||
public static void Set(this AVObject obj, string key, object value)
|
||||
{
|
||||
obj.Set(key, value);
|
||||
}
|
||||
|
||||
public static void DisableHooks(this AVObject obj, IEnumerable<string> hookKeys)
|
||||
{
|
||||
obj.Set("__ignore_hooks", hookKeys);
|
||||
}
|
||||
public static void DisableHook(this AVObject obj, string hookKey)
|
||||
{
|
||||
var newList = new List<string>();
|
||||
if (obj.ContainsKey("__ignore_hooks"))
|
||||
{
|
||||
var hookKeys = obj.Get<IEnumerable<string>>("__ignore_hooks");
|
||||
newList = hookKeys.ToList();
|
||||
}
|
||||
newList.Add(hookKey);
|
||||
obj.DisableHooks(newList);
|
||||
}
|
||||
|
||||
public static void DisableAfterHook(this AVObject obj)
|
||||
{
|
||||
obj.DisableAfterSave();
|
||||
obj.DisableAfterUpdate();
|
||||
obj.DisableAfterDelete();
|
||||
}
|
||||
|
||||
public static void DisableBeforeHook(this AVObject obj)
|
||||
{
|
||||
obj.DisableBeforeSave();
|
||||
obj.DisableBeforeDelete();
|
||||
obj.DisableBeforeUpdate();
|
||||
}
|
||||
|
||||
public static void DisableBeforeSave(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("beforeSave");
|
||||
}
|
||||
public static void DisableAfterSave(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("afterSave");
|
||||
}
|
||||
public static void DisableBeforeUpdate(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("beforeUpdate");
|
||||
}
|
||||
public static void DisableAfterUpdate(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("afterUpdate");
|
||||
}
|
||||
public static void DisableBeforeDelete(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("beforeDelete");
|
||||
}
|
||||
public static void DisableAfterDelete(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("afterDelete");
|
||||
}
|
||||
|
||||
#region on property updated or changed or collection updated
|
||||
|
||||
/// <summary>
|
||||
/// On the property changed.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnPropertyChanged(this AVObject avObj, string propertyName, PropertyChangedEventHandler handler)
|
||||
{
|
||||
avObj.PropertyChanged += (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == propertyName)
|
||||
{
|
||||
handler(sender, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On the property updated.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnPropertyUpdated(this AVObject avObj, string propertyName, PropertyUpdatedEventHandler handler)
|
||||
{
|
||||
avObj.PropertyUpdated += (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == propertyName)
|
||||
{
|
||||
handler(sender, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On the property updated.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnPropertyUpdated(this AVObject avObj, string propertyName, Action<object, object> handler)
|
||||
{
|
||||
avObj.OnPropertyUpdated(propertyName,(object sender, PropertyUpdatedEventArgs e) =>
|
||||
{
|
||||
handler(e.OldValue, e.NewValue);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On the collection property updated.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnCollectionPropertyUpdated(this AVObject avObj, string propertyName, CollectionPropertyUpdatedEventHandler handler)
|
||||
{
|
||||
avObj.CollectionPropertyUpdated += (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == propertyName)
|
||||
{
|
||||
handler(sender, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On the collection property added.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnCollectionPropertyAdded(this AVObject avObj, string propertyName, Action<IEnumerable> handler)
|
||||
{
|
||||
avObj.OnCollectionPropertyUpdated(propertyName, (sender, e) =>
|
||||
{
|
||||
if (e.CollectionAction == NotifyCollectionUpdatedAction.Add)
|
||||
{
|
||||
handler(e.NewValues);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On the collection property removed.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnCollectionPropertyRemoved(this AVObject avObj, string propertyName, Action<IEnumerable> handler)
|
||||
{
|
||||
avObj.OnCollectionPropertyUpdated(propertyName, (sender, e) =>
|
||||
{
|
||||
if (e.CollectionAction == NotifyCollectionUpdatedAction.Remove)
|
||||
{
|
||||
handler(e.NewValues);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static class AVQueryExtensions {
|
||||
public static string GetClassName<T>(this AVQuery<T> query) where T: AVObject {
|
||||
return query.ClassName;
|
||||
}
|
||||
|
||||
public static IDictionary<string, object> BuildParameters<T>(this AVQuery<T> query) where T: AVObject {
|
||||
return query.BuildParameters(false);
|
||||
}
|
||||
|
||||
public static object GetConstraint<T>(this AVQuery<T> query, string key) where T : AVObject {
|
||||
return query.GetConstraint(key);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static class AVRelationExtensions {
|
||||
public static AVRelation<T> Create<T>(AVObject parent, string childKey) where T : AVObject {
|
||||
return new AVRelation<T>(parent, childKey);
|
||||
}
|
||||
|
||||
public static AVRelation<T> Create<T>(AVObject parent, string childKey, string targetClassName) where T: AVObject {
|
||||
return new AVRelation<T>(parent, childKey, targetClassName);
|
||||
}
|
||||
|
||||
public static string GetTargetClassName<T>(this AVRelation<T> relation) where T : AVObject {
|
||||
return relation.TargetClassName;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Provides a Dictionary implementation that can delegate to any other
|
||||
/// dictionary, regardless of its value type. Used for coercion of
|
||||
/// dictionaries when returning them to users.
|
||||
/// </summary>
|
||||
/// <typeparam name="TOut">The resulting type of value in the dictionary.</typeparam>
|
||||
/// <typeparam name="TIn">The original type of value in the dictionary.</typeparam>
|
||||
[Preserve(AllMembers = true, Conditional = false)]
|
||||
public class FlexibleDictionaryWrapper<TOut, TIn> : IDictionary<string, TOut> {
|
||||
private readonly IDictionary<string, TIn> toWrap;
|
||||
public FlexibleDictionaryWrapper(IDictionary<string, TIn> toWrap) {
|
||||
this.toWrap = toWrap;
|
||||
}
|
||||
|
||||
public void Add(string key, TOut value) {
|
||||
toWrap.Add(key, (TIn)Conversion.ConvertTo<TIn>(value));
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key) {
|
||||
return toWrap.ContainsKey(key);
|
||||
}
|
||||
|
||||
public ICollection<string> Keys {
|
||||
get { return toWrap.Keys; }
|
||||
}
|
||||
|
||||
public bool Remove(string key) {
|
||||
return toWrap.Remove(key);
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out TOut value) {
|
||||
TIn outValue;
|
||||
bool result = toWrap.TryGetValue(key, out outValue);
|
||||
value = (TOut)Conversion.ConvertTo<TOut>(outValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
public ICollection<TOut> Values {
|
||||
get {
|
||||
return toWrap.Values
|
||||
.Select(item => (TOut)Conversion.ConvertTo<TOut>(item)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public TOut this[string key] {
|
||||
get {
|
||||
return (TOut)Conversion.ConvertTo<TOut>(toWrap[key]);
|
||||
}
|
||||
set {
|
||||
toWrap[key] = (TIn)Conversion.ConvertTo<TIn>(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, TOut> item) {
|
||||
toWrap.Add(new KeyValuePair<string, TIn>(item.Key,
|
||||
(TIn)Conversion.ConvertTo<TIn>(item.Value)));
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
toWrap.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, TOut> item) {
|
||||
return toWrap.Contains(new KeyValuePair<string, TIn>(item.Key,
|
||||
(TIn)Conversion.ConvertTo<TIn>(item.Value)));
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, TOut>[] array, int arrayIndex) {
|
||||
var converted = from pair in toWrap
|
||||
select new KeyValuePair<string, TOut>(pair.Key,
|
||||
(TOut)Conversion.ConvertTo<TOut>(pair.Value));
|
||||
converted.ToList().CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count {
|
||||
get { return toWrap.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly {
|
||||
get { return toWrap.IsReadOnly; }
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, TOut> item) {
|
||||
return toWrap.Remove(new KeyValuePair<string, TIn>(item.Key,
|
||||
(TIn)Conversion.ConvertTo<TIn>(item.Value)));
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, TOut>> GetEnumerator() {
|
||||
foreach (var pair in toWrap) {
|
||||
yield return new KeyValuePair<string, TOut>(pair.Key,
|
||||
(TOut)Conversion.ConvertTo<TOut>(pair.Value));
|
||||
}
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Provides a List implementation that can delegate to any other
|
||||
/// list, regardless of its value type. Used for coercion of
|
||||
/// lists when returning them to users.
|
||||
/// </summary>
|
||||
/// <typeparam name="TOut">The resulting type of value in the list.</typeparam>
|
||||
/// <typeparam name="TIn">The original type of value in the list.</typeparam>
|
||||
[Preserve(AllMembers = true, Conditional = false)]
|
||||
public class FlexibleListWrapper<TOut, TIn> : IList<TOut> {
|
||||
private IList<TIn> toWrap;
|
||||
public FlexibleListWrapper(IList<TIn> toWrap) {
|
||||
this.toWrap = toWrap;
|
||||
}
|
||||
|
||||
public int IndexOf(TOut item) {
|
||||
return toWrap.IndexOf((TIn)Conversion.ConvertTo<TIn>(item));
|
||||
}
|
||||
|
||||
public void Insert(int index, TOut item) {
|
||||
toWrap.Insert(index, (TIn)Conversion.ConvertTo<TIn>(item));
|
||||
}
|
||||
|
||||
public void RemoveAt(int index) {
|
||||
toWrap.RemoveAt(index);
|
||||
}
|
||||
|
||||
public TOut this[int index] {
|
||||
get {
|
||||
return (TOut)Conversion.ConvertTo<TOut>(toWrap[index]);
|
||||
}
|
||||
set {
|
||||
toWrap[index] = (TIn)Conversion.ConvertTo<TIn>(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TOut item) {
|
||||
toWrap.Add((TIn)Conversion.ConvertTo<TIn>(item));
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
toWrap.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(TOut item) {
|
||||
return toWrap.Contains((TIn)Conversion.ConvertTo<TIn>(item));
|
||||
}
|
||||
|
||||
public void CopyTo(TOut[] array, int arrayIndex) {
|
||||
toWrap.Select(item => (TOut)Conversion.ConvertTo<TOut>(item))
|
||||
.ToList().CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count {
|
||||
get { return toWrap.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly {
|
||||
get { return toWrap.IsReadOnly; }
|
||||
}
|
||||
|
||||
public bool Remove(TOut item) {
|
||||
return toWrap.Remove((TIn)Conversion.ConvertTo<TIn>(item));
|
||||
}
|
||||
|
||||
public IEnumerator<TOut> GetEnumerator() {
|
||||
foreach (var item in (IEnumerable)toWrap) {
|
||||
yield return (TOut)Conversion.ConvertTo<TOut>(item);
|
||||
}
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Represents an object that can be converted into JSON.
|
||||
/// </summary>
|
||||
public interface IJsonConvertible {
|
||||
/// <summary>
|
||||
/// Converts the object to a data structure that can be converted to JSON.
|
||||
/// </summary>
|
||||
/// <returns>An object to be JSONified.</returns>
|
||||
IDictionary<string, object> ToJSON();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// An equality comparer that uses the object identity (i.e. ReferenceEquals)
|
||||
/// rather than .Equals, allowing identity to be used for checking equality in
|
||||
/// ISets and IDictionaries.
|
||||
/// </summary>
|
||||
public class IdentityEqualityComparer<T> : IEqualityComparer<T>
|
||||
{
|
||||
public bool Equals(T x, T y)
|
||||
{
|
||||
return object.ReferenceEquals(x, y);
|
||||
}
|
||||
|
||||
public int GetHashCode(T obj)
|
||||
{
|
||||
return RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Provides helper methods that allow us to use terser code elsewhere.
|
||||
/// </summary>
|
||||
public static class InternalExtensions {
|
||||
/// <summary>
|
||||
/// Ensures a task (even null) is awaitable.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="task"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<T> Safe<T>(this Task<T> task) {
|
||||
return task ?? Task.FromResult<T>(default(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures a task (even null) is awaitable.
|
||||
/// </summary>
|
||||
/// <param name="task"></param>
|
||||
/// <returns></returns>
|
||||
public static Task Safe(this Task task) {
|
||||
return task ?? Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
public delegate void PartialAccessor<T>(ref T arg);
|
||||
|
||||
public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> self,
|
||||
TKey key,
|
||||
TValue defaultValue) {
|
||||
TValue value;
|
||||
if (self.TryGetValue(key, out value)) {
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static bool CollectionsEqual<T>(this IEnumerable<T> a, IEnumerable<T> b) {
|
||||
return Object.Equals(a, b) ||
|
||||
(a != null && b != null &&
|
||||
a.SequenceEqual(b));
|
||||
}
|
||||
|
||||
public static Task<TResult> OnSuccess<TIn, TResult>(this Task<TIn> task,
|
||||
Func<Task<TIn>, TResult> continuation) {
|
||||
return ((Task)task).OnSuccess(t => continuation((Task<TIn>)t));
|
||||
}
|
||||
|
||||
public static Task OnSuccess<TIn>(this Task<TIn> task, Action<Task<TIn>> continuation) {
|
||||
return task.OnSuccess((Func<Task<TIn>, object>)(t => {
|
||||
continuation(t);
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
public static Task<TResult> OnSuccess<TResult>(this Task task,
|
||||
Func<Task, TResult> continuation) {
|
||||
return task.ContinueWith(t => {
|
||||
if (t.IsFaulted) {
|
||||
var ex = t.Exception.Flatten();
|
||||
if (ex.InnerExceptions.Count == 1) {
|
||||
ExceptionDispatchInfo.Capture(ex.InnerExceptions[0]).Throw();
|
||||
} else {
|
||||
ExceptionDispatchInfo.Capture(ex).Throw();
|
||||
}
|
||||
// Unreachable
|
||||
return Task.FromResult(default(TResult));
|
||||
} else if (t.IsCanceled) {
|
||||
var tcs = new TaskCompletionSource<TResult>();
|
||||
tcs.SetCanceled();
|
||||
return tcs.Task;
|
||||
} else {
|
||||
return Task.FromResult(continuation(t));
|
||||
}
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
public static Task OnSuccess(this Task task, Action<Task> continuation) {
|
||||
return task.OnSuccess((Func<Task, object>)(t => {
|
||||
continuation(t);
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
public static Task WhileAsync(Func<Task<bool>> predicate, Func<Task> body) {
|
||||
Func<Task> iterate = null;
|
||||
iterate = () => {
|
||||
return predicate().OnSuccess(t => {
|
||||
if (!t.Result) {
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
return body().OnSuccess(_ => iterate()).Unwrap();
|
||||
}).Unwrap();
|
||||
};
|
||||
return iterate();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,554 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple recursive-descent JSON Parser based on the grammar defined at http://www.json.org
|
||||
/// and http://tools.ietf.org/html/rfc4627
|
||||
/// </summary>
|
||||
public class Json
|
||||
{
|
||||
/// <summary>
|
||||
/// Place at the start of a regex to force the match to begin wherever the search starts (i.e.
|
||||
/// anchored at the index of the first character of the search, even when that search starts
|
||||
/// in the middle of the string).
|
||||
/// </summary>
|
||||
private static readonly string startOfString = "\\G";
|
||||
private static readonly char startObject = '{';
|
||||
private static readonly char endObject = '}';
|
||||
private static readonly char startArray = '[';
|
||||
private static readonly char endArray = ']';
|
||||
private static readonly char valueSeparator = ',';
|
||||
private static readonly char nameSeparator = ':';
|
||||
private static readonly char[] falseValue = "false".ToCharArray();
|
||||
private static readonly char[] trueValue = "true".ToCharArray();
|
||||
private static readonly char[] nullValue = "null".ToCharArray();
|
||||
private static readonly Regex numberValue = new Regex(startOfString +
|
||||
@"-?(?:0|[1-9]\d*)(?<frac>\.\d+)?(?<exp>(?:e|E)(?:-|\+)?\d+)?");
|
||||
private static readonly Regex stringValue = new Regex(startOfString +
|
||||
"\"(?<content>(?:[^\\\\\"]|(?<escape>\\\\(?:[\\\\\"/bfnrt]|u[0-9a-fA-F]{4})))*)\"",
|
||||
RegexOptions.Multiline);
|
||||
|
||||
private static readonly Regex escapePattern = new Regex("\\\\|\"|[\u0000-\u001F]");
|
||||
|
||||
private class JsonStringParser
|
||||
{
|
||||
public string Input { get; private set; }
|
||||
|
||||
public char[] InputAsArray { get; private set; }
|
||||
|
||||
private int currentIndex;
|
||||
public int CurrentIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return currentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public void Skip(int skip)
|
||||
{
|
||||
currentIndex += skip;
|
||||
}
|
||||
|
||||
public JsonStringParser(string input)
|
||||
{
|
||||
Input = input;
|
||||
InputAsArray = input.ToCharArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses JSON object syntax (e.g. '{}')
|
||||
/// </summary>
|
||||
internal bool AVObject(out object output)
|
||||
{
|
||||
output = null;
|
||||
int initialCurrentIndex = CurrentIndex;
|
||||
if (!Accept(startObject))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var dict = new Dictionary<string, object>();
|
||||
while (true)
|
||||
{
|
||||
object pairValue;
|
||||
if (!ParseMember(out pairValue))
|
||||
{
|
||||
break;
|
||||
}
|
||||
var pair = pairValue as Tuple<string, object>;
|
||||
dict[pair.Item1] = pair.Item2;
|
||||
if (!Accept(valueSeparator))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!Accept(endObject))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
output = dict;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses JSON member syntax (e.g. '"keyname" : null')
|
||||
/// </summary>
|
||||
private bool ParseMember(out object output)
|
||||
{
|
||||
output = null;
|
||||
object key;
|
||||
if (!ParseString(out key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!Accept(nameSeparator))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
object value;
|
||||
if (!ParseValue(out value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
output = new Tuple<string, object>((string)key, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses JSON array syntax (e.g. '[]')
|
||||
/// </summary>
|
||||
internal bool ParseArray(out object output)
|
||||
{
|
||||
output = null;
|
||||
if (!Accept(startArray))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var list = new List<object>();
|
||||
while (true)
|
||||
{
|
||||
object value;
|
||||
if (!ParseValue(out value))
|
||||
{
|
||||
break;
|
||||
}
|
||||
list.Add(value);
|
||||
if (!Accept(valueSeparator))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!Accept(endArray))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
output = list;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a value (i.e. the right-hand side of an object member assignment or
|
||||
/// an element in an array)
|
||||
/// </summary>
|
||||
private bool ParseValue(out object output)
|
||||
{
|
||||
if (Accept(falseValue))
|
||||
{
|
||||
output = false;
|
||||
return true;
|
||||
}
|
||||
else if (Accept(nullValue))
|
||||
{
|
||||
output = null;
|
||||
return true;
|
||||
}
|
||||
else if (Accept(trueValue))
|
||||
{
|
||||
output = true;
|
||||
return true;
|
||||
}
|
||||
return AVObject(out output) ||
|
||||
ParseArray(out output) ||
|
||||
ParseNumber(out output) ||
|
||||
ParseString(out output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a JSON string (e.g. '"foo\u1234bar\n"')
|
||||
/// </summary>
|
||||
private bool ParseString(out object output)
|
||||
{
|
||||
output = null;
|
||||
Match m;
|
||||
if (!Accept(stringValue, out m))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// handle escapes:
|
||||
int offset = 0;
|
||||
var contentCapture = m.Groups["content"];
|
||||
var builder = new StringBuilder(contentCapture.Value);
|
||||
foreach (Capture escape in m.Groups["escape"].Captures)
|
||||
{
|
||||
int index = (escape.Index - contentCapture.Index) - offset;
|
||||
offset += escape.Length - 1;
|
||||
builder.Remove(index + 1, escape.Length - 1);
|
||||
switch (escape.Value[1])
|
||||
{
|
||||
case '\"':
|
||||
builder[index] = '\"';
|
||||
break;
|
||||
case '\\':
|
||||
builder[index] = '\\';
|
||||
break;
|
||||
case '/':
|
||||
builder[index] = '/';
|
||||
break;
|
||||
case 'b':
|
||||
builder[index] = '\b';
|
||||
break;
|
||||
case 'f':
|
||||
builder[index] = '\f';
|
||||
break;
|
||||
case 'n':
|
||||
builder[index] = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
builder[index] = '\r';
|
||||
break;
|
||||
case 't':
|
||||
builder[index] = '\t';
|
||||
break;
|
||||
case 'u':
|
||||
builder[index] = (char)ushort.Parse(escape.Value.Substring(2), NumberStyles.AllowHexSpecifier);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unexpected escape character in string: " + escape.Value);
|
||||
}
|
||||
}
|
||||
output = builder.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a number. Returns a long if the number is an integer or has an exponent,
|
||||
/// otherwise returns a double.
|
||||
/// </summary>
|
||||
private bool ParseNumber(out object output)
|
||||
{
|
||||
output = null;
|
||||
Match m;
|
||||
if (!Accept(numberValue, out m))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (m.Groups["frac"].Length > 0 || m.Groups["exp"].Length > 0)
|
||||
{
|
||||
// It's a double.
|
||||
output = double.Parse(m.Value, CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int temp = 0;
|
||||
if (int.TryParse(m.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out temp))
|
||||
{
|
||||
output = temp;
|
||||
return true;
|
||||
}
|
||||
output = long.Parse(m.Value, CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches the string to a regex, consuming part of the string and returning the match.
|
||||
/// </summary>
|
||||
private bool Accept(Regex matcher, out Match match)
|
||||
{
|
||||
match = matcher.Match(Input, CurrentIndex);
|
||||
if (match.Success)
|
||||
{
|
||||
Skip(match.Length);
|
||||
}
|
||||
return match.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the first occurrences of a character, consuming part of the string.
|
||||
/// </summary>
|
||||
private bool Accept(char condition)
|
||||
{
|
||||
int step = 0;
|
||||
int strLen = InputAsArray.Length;
|
||||
int currentStep = currentIndex;
|
||||
char currentChar;
|
||||
|
||||
// Remove whitespace
|
||||
while (currentStep < strLen &&
|
||||
((currentChar = InputAsArray[currentStep]) == ' ' ||
|
||||
currentChar == '\r' ||
|
||||
currentChar == '\t' ||
|
||||
currentChar == '\n'))
|
||||
{
|
||||
++step;
|
||||
++currentStep;
|
||||
}
|
||||
|
||||
bool match = (currentStep < strLen) && (InputAsArray[currentStep] == condition);
|
||||
if (match)
|
||||
{
|
||||
++step;
|
||||
++currentStep;
|
||||
|
||||
// Remove whitespace
|
||||
while (currentStep < strLen &&
|
||||
((currentChar = InputAsArray[currentStep]) == ' ' ||
|
||||
currentChar == '\r' ||
|
||||
currentChar == '\t' ||
|
||||
currentChar == '\n'))
|
||||
{
|
||||
++step;
|
||||
++currentStep;
|
||||
}
|
||||
|
||||
Skip(step);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the first occurrences of a string, consuming part of the string.
|
||||
/// </summary>
|
||||
private bool Accept(char[] condition)
|
||||
{
|
||||
int step = 0;
|
||||
int strLen = InputAsArray.Length;
|
||||
int currentStep = currentIndex;
|
||||
char currentChar;
|
||||
|
||||
// Remove whitespace
|
||||
while (currentStep < strLen &&
|
||||
((currentChar = InputAsArray[currentStep]) == ' ' ||
|
||||
currentChar == '\r' ||
|
||||
currentChar == '\t' ||
|
||||
currentChar == '\n'))
|
||||
{
|
||||
++step;
|
||||
++currentStep;
|
||||
}
|
||||
|
||||
bool strMatch = true;
|
||||
for (int i = 0; currentStep < strLen && i < condition.Length; ++i, ++currentStep)
|
||||
{
|
||||
if (InputAsArray[currentStep] != condition[i])
|
||||
{
|
||||
strMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool match = (currentStep < strLen) && strMatch;
|
||||
if (match)
|
||||
{
|
||||
Skip(step + condition.Length);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a JSON-text as defined in http://tools.ietf.org/html/rfc4627, returning an
|
||||
/// IDictionary<string, object> or an IList<object> depending on whether
|
||||
/// the value was an array or dictionary. Nested objects also match these types.
|
||||
/// </summary>
|
||||
public static object Parse(string input)
|
||||
{
|
||||
object output;
|
||||
input = input.Trim();
|
||||
JsonStringParser parser = new JsonStringParser(input);
|
||||
|
||||
if ((parser.AVObject(out output) ||
|
||||
parser.ParseArray(out output)) &&
|
||||
parser.CurrentIndex == input.Length)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
throw new ArgumentException("Input JSON was invalid.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a dictionary into a JSON string. Supports values that are
|
||||
/// IDictionary<string, object>, IList<object>, strings,
|
||||
/// nulls, and any of the primitive types.
|
||||
/// </summary>
|
||||
public static string Encode(IDictionary<string, object> dict)
|
||||
{
|
||||
if (dict == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (dict.Count == 0)
|
||||
{
|
||||
return "{}";
|
||||
}
|
||||
var builder = new StringBuilder("{");
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
builder.Append(Encode(pair.Key));
|
||||
builder.Append(":");
|
||||
builder.Append(Encode(pair.Value));
|
||||
builder.Append(",");
|
||||
}
|
||||
builder[builder.Length - 1] = '}';
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a list into a JSON string. Supports values that are
|
||||
/// IDictionary<string, object>, IList<object>, strings,
|
||||
/// nulls, and any of the primitive types.
|
||||
/// </summary>
|
||||
public static string Encode(IList<object> list)
|
||||
{
|
||||
if (list == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (list.Count == 0)
|
||||
{
|
||||
return "[]";
|
||||
}
|
||||
var builder = new StringBuilder("[");
|
||||
foreach (var item in list)
|
||||
{
|
||||
builder.Append(Encode(item));
|
||||
builder.Append(",");
|
||||
}
|
||||
builder[builder.Length - 1] = ']';
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static string Encode(IList<string> strList)
|
||||
{
|
||||
if (strList == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (strList.Count == 0)
|
||||
{
|
||||
return "[]";
|
||||
}
|
||||
StringBuilder stringBuilder = new StringBuilder("[");
|
||||
foreach (object obj in strList)
|
||||
{
|
||||
stringBuilder.Append(Json.Encode(obj));
|
||||
stringBuilder.Append(",");
|
||||
}
|
||||
stringBuilder[stringBuilder.Length - 1] = ']';
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
public static string Encode(IList<IDictionary<string, object>> dicList)
|
||||
{
|
||||
if (dicList == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (dicList.Count == 0)
|
||||
{
|
||||
return "[]";
|
||||
}
|
||||
StringBuilder stringBuilder = new StringBuilder("[");
|
||||
foreach (object obj in dicList)
|
||||
{
|
||||
stringBuilder.Append(Json.Encode(obj));
|
||||
stringBuilder.Append(",");
|
||||
}
|
||||
stringBuilder[stringBuilder.Length - 1] = ']';
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes an object into a JSON string.
|
||||
/// </summary>
|
||||
public static string Encode(object obj)
|
||||
{
|
||||
var dict = obj as IDictionary<string, object>;
|
||||
if (dict != null)
|
||||
{
|
||||
return Encode(dict);
|
||||
}
|
||||
var list = obj as IList<object>;
|
||||
if (list != null)
|
||||
{
|
||||
return Encode(list);
|
||||
}
|
||||
var dicList = obj as IList<IDictionary<string, object>>;
|
||||
if (dicList != null)
|
||||
{
|
||||
return Encode(dicList);
|
||||
}
|
||||
var strLists = obj as IList<string>;
|
||||
if (strLists != null)
|
||||
{
|
||||
return Encode(strLists);
|
||||
}
|
||||
var str = obj as string;
|
||||
if (str != null)
|
||||
{
|
||||
str = escapePattern.Replace(str, m =>
|
||||
{
|
||||
switch (m.Value[0])
|
||||
{
|
||||
case '\\':
|
||||
return "\\\\";
|
||||
case '\"':
|
||||
return "\\\"";
|
||||
case '\b':
|
||||
return "\\b";
|
||||
case '\f':
|
||||
return "\\f";
|
||||
case '\n':
|
||||
return "\\n";
|
||||
case '\r':
|
||||
return "\\r";
|
||||
case '\t':
|
||||
return "\\t";
|
||||
default:
|
||||
return "\\u" + ((ushort)m.Value[0]).ToString("x4");
|
||||
}
|
||||
});
|
||||
return "\"" + str + "\"";
|
||||
}
|
||||
if (obj == null)
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
if (obj is bool)
|
||||
{
|
||||
if ((bool)obj)
|
||||
{
|
||||
return "true";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "false";
|
||||
}
|
||||
}
|
||||
if (!obj.GetType().GetTypeInfo().IsPrimitive)
|
||||
{
|
||||
throw new ArgumentException("Unable to encode objects of type " + obj.GetType());
|
||||
}
|
||||
return Convert.ToString(obj, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// 为 Json 解析提供异步接口
|
||||
/// </summary>
|
||||
public static class JsonUtils {
|
||||
public static async Task<string> SerializeObjectAsync(object obj) {
|
||||
string str = null;
|
||||
await Task.Run(() => {
|
||||
str = JsonConvert.SerializeObject(obj);
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
||||
public static Task<string> SerializeAsync(object obj) {
|
||||
return Task.Run(() => {
|
||||
return JsonConvert.SerializeObject(obj);
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<T> DeserializeObjectAsync<T>(string str, params JsonConverter[] converters) {
|
||||
T obj = default;
|
||||
await Task.Run(() => {
|
||||
obj = JsonConvert.DeserializeObject<T>(str, converters);
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static Task<T> DeserializeAsync<T>(string str, params JsonConverter[] converts) {
|
||||
return Task.Run(() => {
|
||||
return JsonConvert.DeserializeObject<T>(str, converts);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class LeanCloudJsonConverter : JsonConverter {
|
||||
public override bool CanConvert(Type objectType) {
|
||||
return objectType == typeof(object);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
|
||||
serializer.Serialize(writer, value);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
|
||||
if (reader.TokenType == JsonToken.StartObject) {
|
||||
var obj = new Dictionary<string, object>();
|
||||
serializer.Populate(reader, obj);
|
||||
return obj;
|
||||
}
|
||||
if (reader.TokenType == JsonToken.StartArray) {
|
||||
var arr = new List<object>();
|
||||
serializer.Populate(reader, arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
return serializer.Deserialize(reader);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class LockSet {
|
||||
private static readonly ConditionalWeakTable<object, IComparable> stableIds =
|
||||
new ConditionalWeakTable<object, IComparable>();
|
||||
private static long nextStableId = 0;
|
||||
|
||||
private readonly IEnumerable<object> mutexes;
|
||||
|
||||
public LockSet(IEnumerable<object> mutexes) {
|
||||
this.mutexes = (from mutex in mutexes
|
||||
orderby GetStableId(mutex)
|
||||
select mutex).ToList();
|
||||
}
|
||||
|
||||
public void Enter() {
|
||||
foreach (var mutex in mutexes) {
|
||||
Monitor.Enter(mutex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Exit() {
|
||||
foreach (var mutex in mutexes) {
|
||||
Monitor.Exit(mutex);
|
||||
}
|
||||
}
|
||||
|
||||
private static IComparable GetStableId(object mutex) {
|
||||
lock (stableIds) {
|
||||
return stableIds.GetValue(mutex, k => nextStableId++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public static class ReflectionHelpers
|
||||
{
|
||||
public static IEnumerable<PropertyInfo> GetProperties(Type type)
|
||||
{
|
||||
#if MONO || UNITY
|
||||
return type.GetProperties();
|
||||
#else
|
||||
return type.GetRuntimeProperties();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static MethodInfo GetMethod(Type type, string name, Type[] parameters)
|
||||
{
|
||||
#if MONO || UNITY
|
||||
return type.GetMethod(name, parameters);
|
||||
#else
|
||||
return type.GetRuntimeMethod(name, parameters);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool IsPrimitive(Type type)
|
||||
{
|
||||
#if MONO || UNITY
|
||||
return type.IsPrimitive;
|
||||
#else
|
||||
return type.GetTypeInfo().IsPrimitive;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetInterfaces(Type type)
|
||||
{
|
||||
#if MONO || UNITY
|
||||
return type.GetInterfaces();
|
||||
#else
|
||||
return type.GetTypeInfo().ImplementedInterfaces;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool IsConstructedGenericType(Type type)
|
||||
{
|
||||
#if UNITY
|
||||
return type.IsGenericType && !type.IsGenericTypeDefinition;
|
||||
#else
|
||||
return type.IsConstructedGenericType;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static IEnumerable<ConstructorInfo> GetConstructors(Type type)
|
||||
{
|
||||
#if UNITY
|
||||
BindingFlags searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
return type.GetConstructors(searchFlags);
|
||||
#else
|
||||
return type.GetTypeInfo().DeclaredConstructors
|
||||
.Where(c => (c.Attributes & MethodAttributes.Static) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Type[] GetGenericTypeArguments(Type type)
|
||||
{
|
||||
#if UNITY
|
||||
return type.GetGenericArguments();
|
||||
#else
|
||||
return type.GenericTypeArguments;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static PropertyInfo GetProperty(Type type, string name)
|
||||
{
|
||||
#if MONO || UNITY
|
||||
return type.GetProperty(name);
|
||||
#else
|
||||
return type.GetRuntimeProperty(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method helps simplify the process of getting a constructor for a type.
|
||||
/// A method like this exists in .NET but is not allowed in a Portable Class Library,
|
||||
/// so we've built our own.
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
/// <param name="parameterTypes"></param>
|
||||
/// <returns></returns>
|
||||
public static ConstructorInfo FindConstructor(this Type self, params Type[] parameterTypes)
|
||||
{
|
||||
var constructors =
|
||||
from constructor in GetConstructors(self)
|
||||
let parameters = constructor.GetParameters()
|
||||
let types = from p in parameters select p.ParameterType
|
||||
where types.SequenceEqual(parameterTypes)
|
||||
select constructor;
|
||||
return constructors.SingleOrDefault();
|
||||
}
|
||||
|
||||
public static bool IsNullable(Type t)
|
||||
{
|
||||
bool isGeneric;
|
||||
#if UNITY
|
||||
isGeneric = t.IsGenericType && !t.IsGenericTypeDefinition;
|
||||
#else
|
||||
isGeneric = t.IsConstructedGenericType;
|
||||
#endif
|
||||
return isGeneric && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
|
||||
}
|
||||
|
||||
public static IEnumerable<T> GetCustomAttributes<T>(this Assembly assembly) where T : Attribute
|
||||
{
|
||||
#if UNITY
|
||||
return assembly.GetCustomAttributes(typeof(T), false).Select(attr => attr as T);
|
||||
#else
|
||||
return CustomAttributeExtensions.GetCustomAttributes<T>(assembly);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Represents an event handler that calls back from the synchronization context
|
||||
/// that subscribed.
|
||||
/// <typeparam name="T">Should look like an EventArgs, but may not inherit EventArgs if T is implemented by the Windows team.</typeparam>
|
||||
/// </summary>
|
||||
public class SynchronizedEventHandler<T> {
|
||||
private LinkedList<Tuple<Delegate, TaskFactory>> delegates =
|
||||
new LinkedList<Tuple<Delegate, TaskFactory>>();
|
||||
public void Add(Delegate del) {
|
||||
lock (delegates) {
|
||||
TaskFactory factory;
|
||||
if (SynchronizationContext.Current != null) {
|
||||
factory =
|
||||
new TaskFactory(CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.FromCurrentSynchronizationContext());
|
||||
} else {
|
||||
factory = Task.Factory;
|
||||
}
|
||||
foreach (var d in del.GetInvocationList()) {
|
||||
delegates.AddLast(new Tuple<Delegate, TaskFactory>(d, factory));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(Delegate del) {
|
||||
lock (delegates) {
|
||||
if (delegates.Count == 0) {
|
||||
return;
|
||||
}
|
||||
foreach (var d in del.GetInvocationList()) {
|
||||
var node = delegates.First;
|
||||
while (node != null) {
|
||||
if (node.Value.Item1 == d) {
|
||||
delegates.Remove(node);
|
||||
break;
|
||||
}
|
||||
node = node.Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task Invoke(object sender, T args) {
|
||||
IEnumerable<Tuple<Delegate, TaskFactory>> toInvoke;
|
||||
var toContinue = new[] { Task.FromResult(0) };
|
||||
lock (delegates) {
|
||||
toInvoke = delegates.ToList();
|
||||
}
|
||||
var invocations = toInvoke
|
||||
.Select(p => p.Item2.ContinueWhenAll(toContinue,
|
||||
_ => p.Item1.DynamicInvoke(sender, args)))
|
||||
.ToList();
|
||||
return Task.WhenAll(invocations);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// A helper class for enqueuing tasks
|
||||
/// </summary>
|
||||
public class TaskQueue {
|
||||
/// <summary>
|
||||
/// We only need to keep the tail of the queue. Cancelled tasks will
|
||||
/// just complete normally/immediately when their turn arrives.
|
||||
/// </summary>
|
||||
private Task tail;
|
||||
private readonly object mutex = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cancellable task that can be safely awaited and is dependent
|
||||
/// on the current tail of the queue. This essentially gives us a proxy
|
||||
/// for the tail end of the queue whose awaiting can be cancelled.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that cancels
|
||||
/// the task even if the task is still in the queue. This allows the
|
||||
/// running task to return immediately without breaking the dependency
|
||||
/// chain. It also ensures that errors do not propagate.</param>
|
||||
/// <returns>A new task that should be awaited by enqueued tasks.</returns>
|
||||
private Task GetTaskToAwait(CancellationToken cancellationToken) {
|
||||
lock (mutex) {
|
||||
Task toAwait = tail ?? Task.FromResult(true);
|
||||
return toAwait.ContinueWith(task => { }, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a task created by <paramref name="taskStart"/>. If the task is
|
||||
/// cancellable (or should be able to be cancelled while it is waiting in the
|
||||
/// queue), pass a cancellationToken.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of task.</typeparam>
|
||||
/// <param name="taskStart">A function given a task to await once state is
|
||||
/// snapshotted (e.g. after capturing session tokens at the time of the save call).
|
||||
/// Awaiting this task will wait for the created task's turn in the queue.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to
|
||||
/// cancel waiting in the queue.</param>
|
||||
/// <returns>The task created by the taskStart function.</returns>
|
||||
public T Enqueue<T>(Func<Task, T> taskStart, CancellationToken cancellationToken)
|
||||
where T : Task {
|
||||
Task oldTail;
|
||||
T task;
|
||||
lock (mutex) {
|
||||
oldTail = this.tail ?? Task.FromResult(true);
|
||||
// The task created by taskStart is responsible for waiting the
|
||||
// task passed to it before doing its work (this gives it an opportunity
|
||||
// to do startup work or save state before waiting for its turn in the queue
|
||||
task = taskStart(GetTaskToAwait(cancellationToken));
|
||||
|
||||
// The tail task should be dependent on the old tail as well as the newly-created
|
||||
// task. This prevents cancellation of the new task from causing the queue to run
|
||||
// out of order.
|
||||
this.tail = Task.WhenAll(oldTail, task);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
public object Mutex { get { return mutex; } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,426 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// A reimplementation of Xamarin's PreserveAttribute.
|
||||
/// This allows us to support AOT and linking for Xamarin platforms.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
internal class PreserveAttribute : Attribute {
|
||||
public bool AllMembers;
|
||||
public bool Conditional;
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
internal class LinkerSafeAttribute : Attribute {
|
||||
public LinkerSafeAttribute() { }
|
||||
}
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
internal class PreserveWrapperTypes {
|
||||
/// <summary>
|
||||
/// Exists to ensure that generic types are AOT-compiled for the conversions we support.
|
||||
/// Any new value types that we add support for will need to be registered here.
|
||||
/// The method itself is never called, but by virtue of the Preserve attribute being set
|
||||
/// on the class, these types will be AOT-compiled.
|
||||
///
|
||||
/// This also applies to Unity.
|
||||
/// </summary>
|
||||
private static List<object> CreateWrapperTypes() {
|
||||
return new List<object> {
|
||||
typeof(FlexibleListWrapper<object, object>),
|
||||
typeof(FlexibleListWrapper<object, bool>),
|
||||
typeof(FlexibleListWrapper<object, byte>),
|
||||
typeof(FlexibleListWrapper<object, sbyte>),
|
||||
typeof(FlexibleListWrapper<object, short>),
|
||||
typeof(FlexibleListWrapper<object, ushort>),
|
||||
typeof(FlexibleListWrapper<object, int>),
|
||||
typeof(FlexibleListWrapper<object, uint>),
|
||||
typeof(FlexibleListWrapper<object, long>),
|
||||
typeof(FlexibleListWrapper<object, ulong>),
|
||||
typeof(FlexibleListWrapper<object, char>),
|
||||
typeof(FlexibleListWrapper<object, double>),
|
||||
typeof(FlexibleListWrapper<object, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<bool, object>),
|
||||
typeof(FlexibleListWrapper<bool, bool>),
|
||||
typeof(FlexibleListWrapper<bool, byte>),
|
||||
typeof(FlexibleListWrapper<bool, sbyte>),
|
||||
typeof(FlexibleListWrapper<bool, short>),
|
||||
typeof(FlexibleListWrapper<bool, ushort>),
|
||||
typeof(FlexibleListWrapper<bool, int>),
|
||||
typeof(FlexibleListWrapper<bool, uint>),
|
||||
typeof(FlexibleListWrapper<bool, long>),
|
||||
typeof(FlexibleListWrapper<bool, ulong>),
|
||||
typeof(FlexibleListWrapper<bool, char>),
|
||||
typeof(FlexibleListWrapper<bool, double>),
|
||||
typeof(FlexibleListWrapper<bool, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<byte, object>),
|
||||
typeof(FlexibleListWrapper<byte, bool>),
|
||||
typeof(FlexibleListWrapper<byte, byte>),
|
||||
typeof(FlexibleListWrapper<byte, sbyte>),
|
||||
typeof(FlexibleListWrapper<byte, short>),
|
||||
typeof(FlexibleListWrapper<byte, ushort>),
|
||||
typeof(FlexibleListWrapper<byte, int>),
|
||||
typeof(FlexibleListWrapper<byte, uint>),
|
||||
typeof(FlexibleListWrapper<byte, long>),
|
||||
typeof(FlexibleListWrapper<byte, ulong>),
|
||||
typeof(FlexibleListWrapper<byte, char>),
|
||||
typeof(FlexibleListWrapper<byte, double>),
|
||||
typeof(FlexibleListWrapper<byte, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<sbyte, object>),
|
||||
typeof(FlexibleListWrapper<sbyte, bool>),
|
||||
typeof(FlexibleListWrapper<sbyte, byte>),
|
||||
typeof(FlexibleListWrapper<sbyte, sbyte>),
|
||||
typeof(FlexibleListWrapper<sbyte, short>),
|
||||
typeof(FlexibleListWrapper<sbyte, ushort>),
|
||||
typeof(FlexibleListWrapper<sbyte, int>),
|
||||
typeof(FlexibleListWrapper<sbyte, uint>),
|
||||
typeof(FlexibleListWrapper<sbyte, long>),
|
||||
typeof(FlexibleListWrapper<sbyte, ulong>),
|
||||
typeof(FlexibleListWrapper<sbyte, char>),
|
||||
typeof(FlexibleListWrapper<sbyte, double>),
|
||||
typeof(FlexibleListWrapper<sbyte, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<short, object>),
|
||||
typeof(FlexibleListWrapper<short, bool>),
|
||||
typeof(FlexibleListWrapper<short, byte>),
|
||||
typeof(FlexibleListWrapper<short, sbyte>),
|
||||
typeof(FlexibleListWrapper<short, short>),
|
||||
typeof(FlexibleListWrapper<short, ushort>),
|
||||
typeof(FlexibleListWrapper<short, int>),
|
||||
typeof(FlexibleListWrapper<short, uint>),
|
||||
typeof(FlexibleListWrapper<short, long>),
|
||||
typeof(FlexibleListWrapper<short, ulong>),
|
||||
typeof(FlexibleListWrapper<short, char>),
|
||||
typeof(FlexibleListWrapper<short, double>),
|
||||
typeof(FlexibleListWrapper<short, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<ushort, object>),
|
||||
typeof(FlexibleListWrapper<ushort, bool>),
|
||||
typeof(FlexibleListWrapper<ushort, byte>),
|
||||
typeof(FlexibleListWrapper<ushort, sbyte>),
|
||||
typeof(FlexibleListWrapper<ushort, short>),
|
||||
typeof(FlexibleListWrapper<ushort, ushort>),
|
||||
typeof(FlexibleListWrapper<ushort, int>),
|
||||
typeof(FlexibleListWrapper<ushort, uint>),
|
||||
typeof(FlexibleListWrapper<ushort, long>),
|
||||
typeof(FlexibleListWrapper<ushort, ulong>),
|
||||
typeof(FlexibleListWrapper<ushort, char>),
|
||||
typeof(FlexibleListWrapper<ushort, double>),
|
||||
typeof(FlexibleListWrapper<ushort, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<int, object>),
|
||||
typeof(FlexibleListWrapper<int, bool>),
|
||||
typeof(FlexibleListWrapper<int, byte>),
|
||||
typeof(FlexibleListWrapper<int, sbyte>),
|
||||
typeof(FlexibleListWrapper<int, short>),
|
||||
typeof(FlexibleListWrapper<int, ushort>),
|
||||
typeof(FlexibleListWrapper<int, int>),
|
||||
typeof(FlexibleListWrapper<int, uint>),
|
||||
typeof(FlexibleListWrapper<int, long>),
|
||||
typeof(FlexibleListWrapper<int, ulong>),
|
||||
typeof(FlexibleListWrapper<int, char>),
|
||||
typeof(FlexibleListWrapper<int, double>),
|
||||
typeof(FlexibleListWrapper<int, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<uint, object>),
|
||||
typeof(FlexibleListWrapper<uint, bool>),
|
||||
typeof(FlexibleListWrapper<uint, byte>),
|
||||
typeof(FlexibleListWrapper<uint, sbyte>),
|
||||
typeof(FlexibleListWrapper<uint, short>),
|
||||
typeof(FlexibleListWrapper<uint, ushort>),
|
||||
typeof(FlexibleListWrapper<uint, int>),
|
||||
typeof(FlexibleListWrapper<uint, uint>),
|
||||
typeof(FlexibleListWrapper<uint, long>),
|
||||
typeof(FlexibleListWrapper<uint, ulong>),
|
||||
typeof(FlexibleListWrapper<uint, char>),
|
||||
typeof(FlexibleListWrapper<uint, double>),
|
||||
typeof(FlexibleListWrapper<uint, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<long, object>),
|
||||
typeof(FlexibleListWrapper<long, bool>),
|
||||
typeof(FlexibleListWrapper<long, byte>),
|
||||
typeof(FlexibleListWrapper<long, sbyte>),
|
||||
typeof(FlexibleListWrapper<long, short>),
|
||||
typeof(FlexibleListWrapper<long, ushort>),
|
||||
typeof(FlexibleListWrapper<long, int>),
|
||||
typeof(FlexibleListWrapper<long, uint>),
|
||||
typeof(FlexibleListWrapper<long, long>),
|
||||
typeof(FlexibleListWrapper<long, ulong>),
|
||||
typeof(FlexibleListWrapper<long, char>),
|
||||
typeof(FlexibleListWrapper<long, double>),
|
||||
typeof(FlexibleListWrapper<long, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<ulong, object>),
|
||||
typeof(FlexibleListWrapper<ulong, bool>),
|
||||
typeof(FlexibleListWrapper<ulong, byte>),
|
||||
typeof(FlexibleListWrapper<ulong, sbyte>),
|
||||
typeof(FlexibleListWrapper<ulong, short>),
|
||||
typeof(FlexibleListWrapper<ulong, ushort>),
|
||||
typeof(FlexibleListWrapper<ulong, int>),
|
||||
typeof(FlexibleListWrapper<ulong, uint>),
|
||||
typeof(FlexibleListWrapper<ulong, long>),
|
||||
typeof(FlexibleListWrapper<ulong, ulong>),
|
||||
typeof(FlexibleListWrapper<ulong, char>),
|
||||
typeof(FlexibleListWrapper<ulong, double>),
|
||||
typeof(FlexibleListWrapper<ulong, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<char, object>),
|
||||
typeof(FlexibleListWrapper<char, bool>),
|
||||
typeof(FlexibleListWrapper<char, byte>),
|
||||
typeof(FlexibleListWrapper<char, sbyte>),
|
||||
typeof(FlexibleListWrapper<char, short>),
|
||||
typeof(FlexibleListWrapper<char, ushort>),
|
||||
typeof(FlexibleListWrapper<char, int>),
|
||||
typeof(FlexibleListWrapper<char, uint>),
|
||||
typeof(FlexibleListWrapper<char, long>),
|
||||
typeof(FlexibleListWrapper<char, ulong>),
|
||||
typeof(FlexibleListWrapper<char, char>),
|
||||
typeof(FlexibleListWrapper<char, double>),
|
||||
typeof(FlexibleListWrapper<char, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<double, object>),
|
||||
typeof(FlexibleListWrapper<double, bool>),
|
||||
typeof(FlexibleListWrapper<double, byte>),
|
||||
typeof(FlexibleListWrapper<double, sbyte>),
|
||||
typeof(FlexibleListWrapper<double, short>),
|
||||
typeof(FlexibleListWrapper<double, ushort>),
|
||||
typeof(FlexibleListWrapper<double, int>),
|
||||
typeof(FlexibleListWrapper<double, uint>),
|
||||
typeof(FlexibleListWrapper<double, long>),
|
||||
typeof(FlexibleListWrapper<double, ulong>),
|
||||
typeof(FlexibleListWrapper<double, char>),
|
||||
typeof(FlexibleListWrapper<double, double>),
|
||||
typeof(FlexibleListWrapper<double, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<float, object>),
|
||||
typeof(FlexibleListWrapper<float, bool>),
|
||||
typeof(FlexibleListWrapper<float, byte>),
|
||||
typeof(FlexibleListWrapper<float, sbyte>),
|
||||
typeof(FlexibleListWrapper<float, short>),
|
||||
typeof(FlexibleListWrapper<float, ushort>),
|
||||
typeof(FlexibleListWrapper<float, int>),
|
||||
typeof(FlexibleListWrapper<float, uint>),
|
||||
typeof(FlexibleListWrapper<float, long>),
|
||||
typeof(FlexibleListWrapper<float, ulong>),
|
||||
typeof(FlexibleListWrapper<float, char>),
|
||||
typeof(FlexibleListWrapper<float, double>),
|
||||
typeof(FlexibleListWrapper<float, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<object, DateTime>),
|
||||
typeof(FlexibleListWrapper<DateTime, object>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<object, object>),
|
||||
typeof(FlexibleDictionaryWrapper<object, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<object, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<object, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<object, short>),
|
||||
typeof(FlexibleDictionaryWrapper<object, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<object, int>),
|
||||
typeof(FlexibleDictionaryWrapper<object, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<object, long>),
|
||||
typeof(FlexibleDictionaryWrapper<object, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<object, char>),
|
||||
typeof(FlexibleDictionaryWrapper<object, double>),
|
||||
typeof(FlexibleDictionaryWrapper<object, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<bool, object>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, short>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, int>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, long>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, char>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, double>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<byte, object>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, short>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, int>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, long>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, char>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, double>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, object>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, short>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, int>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, long>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, char>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, double>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<short, object>),
|
||||
typeof(FlexibleDictionaryWrapper<short, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<short, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<short, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<short, short>),
|
||||
typeof(FlexibleDictionaryWrapper<short, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<short, int>),
|
||||
typeof(FlexibleDictionaryWrapper<short, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<short, long>),
|
||||
typeof(FlexibleDictionaryWrapper<short, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<short, char>),
|
||||
typeof(FlexibleDictionaryWrapper<short, double>),
|
||||
typeof(FlexibleDictionaryWrapper<short, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<ushort, object>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, short>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, int>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, long>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, char>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, double>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<int, object>),
|
||||
typeof(FlexibleDictionaryWrapper<int, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<int, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<int, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<int, short>),
|
||||
typeof(FlexibleDictionaryWrapper<int, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<int, int>),
|
||||
typeof(FlexibleDictionaryWrapper<int, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<int, long>),
|
||||
typeof(FlexibleDictionaryWrapper<int, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<int, char>),
|
||||
typeof(FlexibleDictionaryWrapper<int, double>),
|
||||
typeof(FlexibleDictionaryWrapper<int, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<uint, object>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, short>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, int>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, long>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, char>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, double>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<long, object>),
|
||||
typeof(FlexibleDictionaryWrapper<long, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<long, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<long, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<long, short>),
|
||||
typeof(FlexibleDictionaryWrapper<long, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<long, int>),
|
||||
typeof(FlexibleDictionaryWrapper<long, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<long, long>),
|
||||
typeof(FlexibleDictionaryWrapper<long, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<long, char>),
|
||||
typeof(FlexibleDictionaryWrapper<long, double>),
|
||||
typeof(FlexibleDictionaryWrapper<long, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<ulong, object>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, short>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, int>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, long>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, char>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, double>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<char, object>),
|
||||
typeof(FlexibleDictionaryWrapper<char, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<char, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<char, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<char, short>),
|
||||
typeof(FlexibleDictionaryWrapper<char, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<char, int>),
|
||||
typeof(FlexibleDictionaryWrapper<char, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<char, long>),
|
||||
typeof(FlexibleDictionaryWrapper<char, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<char, char>),
|
||||
typeof(FlexibleDictionaryWrapper<char, double>),
|
||||
typeof(FlexibleDictionaryWrapper<char, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<double, object>),
|
||||
typeof(FlexibleDictionaryWrapper<double, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<double, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<double, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<double, short>),
|
||||
typeof(FlexibleDictionaryWrapper<double, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<double, int>),
|
||||
typeof(FlexibleDictionaryWrapper<double, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<double, long>),
|
||||
typeof(FlexibleDictionaryWrapper<double, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<double, char>),
|
||||
typeof(FlexibleDictionaryWrapper<double, double>),
|
||||
typeof(FlexibleDictionaryWrapper<double, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<float, object>),
|
||||
typeof(FlexibleDictionaryWrapper<float, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<float, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<float, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<float, short>),
|
||||
typeof(FlexibleDictionaryWrapper<float, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<float, int>),
|
||||
typeof(FlexibleDictionaryWrapper<float, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<float, long>),
|
||||
typeof(FlexibleDictionaryWrapper<float, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<float, char>),
|
||||
typeof(FlexibleDictionaryWrapper<float, double>),
|
||||
typeof(FlexibleDictionaryWrapper<float, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<object, DateTime>),
|
||||
typeof(FlexibleDictionaryWrapper<DateTime, object>),
|
||||
|
||||
(Action)(() => AVCloud.CallFunctionAsync<object>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<bool>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<byte>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<sbyte>(null, null ,CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<short>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<ushort>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<int>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<uint>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<long>(null, null,CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<ulong>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<char>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<double>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<float>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<IDictionary<string, object>>(null, null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<IList<object>>(null, null, CancellationToken.None)),
|
||||
|
||||
typeof(FlexibleListWrapper<object, AVGeoPoint>),
|
||||
typeof(FlexibleListWrapper<AVGeoPoint, object>),
|
||||
typeof(FlexibleDictionaryWrapper<object, AVGeoPoint>),
|
||||
typeof(FlexibleDictionaryWrapper<AVGeoPoint, object>),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,284 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// A AVACL is used to control which users and roles can access or modify a particular object. Each
|
||||
/// <see cref="AVObject"/> can have its own AVACL. You can grant read and write permissions
|
||||
/// separately to specific users, to groups of users that belong to roles, or you can grant permissions
|
||||
/// to "the public" so that, for example, any user could read a particular object but only a particular
|
||||
/// set of users could write to that object.
|
||||
/// </summary>
|
||||
public class AVACL : IJsonConvertible {
|
||||
private enum AccessKind {
|
||||
Read,
|
||||
Write
|
||||
}
|
||||
private const string publicName = "*";
|
||||
private readonly ICollection<string> readers = new HashSet<string>();
|
||||
private readonly ICollection<string> writers = new HashSet<string>();
|
||||
|
||||
internal AVACL(IDictionary<string, object> jsonObject) {
|
||||
readers = new HashSet<string>(from pair in jsonObject
|
||||
where ((IDictionary<string, object>)pair.Value).ContainsKey("read")
|
||||
select pair.Key);
|
||||
writers = new HashSet<string>(from pair in jsonObject
|
||||
where ((IDictionary<string, object>)pair.Value).ContainsKey("write")
|
||||
select pair.Key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an ACL with no permissions granted.
|
||||
/// </summary>
|
||||
public AVACL() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an ACL where only the provided user has access.
|
||||
/// </summary>
|
||||
/// <param name="owner">The only user that can read or write objects governed by this ACL.</param>
|
||||
public AVACL(AVUser owner) {
|
||||
SetReadAccess(owner, true);
|
||||
SetWriteAccess(owner, true);
|
||||
}
|
||||
|
||||
IDictionary<string, object> IJsonConvertible.ToJSON() {
|
||||
var result = new Dictionary<string, object>();
|
||||
foreach (var user in readers.Union(writers)) {
|
||||
var userPermissions = new Dictionary<string, object>();
|
||||
if (readers.Contains(user)) {
|
||||
userPermissions["read"] = true;
|
||||
}
|
||||
if (writers.Contains(user)) {
|
||||
userPermissions["write"] = true;
|
||||
}
|
||||
result[user] = userPermissions;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetAccess(AccessKind kind, string userId, bool allowed) {
|
||||
if (userId == null) {
|
||||
throw new ArgumentException("Cannot set access for an unsaved user or role.");
|
||||
}
|
||||
ICollection<string> target = null;
|
||||
switch (kind) {
|
||||
case AccessKind.Read:
|
||||
target = readers;
|
||||
break;
|
||||
case AccessKind.Write:
|
||||
target = writers;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("Unknown AccessKind");
|
||||
}
|
||||
if (allowed) {
|
||||
target.Add(userId);
|
||||
} else {
|
||||
target.Remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetAccess(AccessKind kind, string userId) {
|
||||
if (userId == null) {
|
||||
throw new ArgumentException("Cannot get access for an unsaved user or role.");
|
||||
}
|
||||
switch (kind) {
|
||||
case AccessKind.Read:
|
||||
return readers.Contains(userId);
|
||||
case AccessKind.Write:
|
||||
return writers.Contains(userId);
|
||||
default:
|
||||
throw new NotImplementedException("Unknown AccessKind");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the public is allowed to read this object.
|
||||
/// </summary>
|
||||
public bool PublicReadAccess {
|
||||
get {
|
||||
return GetAccess(AccessKind.Read, publicName);
|
||||
}
|
||||
set {
|
||||
SetAccess(AccessKind.Read, publicName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the public is allowed to write this object.
|
||||
/// </summary>
|
||||
public bool PublicWriteAccess {
|
||||
get {
|
||||
return GetAccess(AccessKind.Write, publicName);
|
||||
}
|
||||
set {
|
||||
SetAccess(AccessKind.Write, publicName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the given user id is allowed to read this object.
|
||||
/// </summary>
|
||||
/// <param name="userId">The objectId of the user.</param>
|
||||
/// <param name="allowed">Whether the user has permission.</param>
|
||||
public void SetReadAccess(string userId, bool allowed) {
|
||||
SetAccess(AccessKind.Read, userId, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the given user is allowed to read this object.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="allowed">Whether the user has permission.</param>
|
||||
public void SetReadAccess(AVUser user, bool allowed) {
|
||||
SetReadAccess(user.ObjectId, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the given user id is allowed to write this object.
|
||||
/// </summary>
|
||||
/// <param name="userId">The objectId of the user.</param>
|
||||
/// <param name="allowed">Whether the user has permission.</param>
|
||||
public void SetWriteAccess(string userId, bool allowed) {
|
||||
SetAccess(AccessKind.Write, userId, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the given user is allowed to write this object.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="allowed">Whether the user has permission.</param>
|
||||
public void SetWriteAccess(AVUser user, bool allowed) {
|
||||
SetWriteAccess(user.ObjectId, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the given user id is *explicitly* allowed to read this object.
|
||||
/// Even if this returns false, the user may still be able to read it if
|
||||
/// PublicReadAccess is true or a role that the user belongs to has read access.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user objectId to check.</param>
|
||||
/// <returns>Whether the user has access.</returns>
|
||||
public bool GetReadAccess(string userId) {
|
||||
return GetAccess(AccessKind.Read, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the given user is *explicitly* allowed to read this object.
|
||||
/// Even if this returns false, the user may still be able to read it if
|
||||
/// PublicReadAccess is true or a role that the user belongs to has read access.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to check.</param>
|
||||
/// <returns>Whether the user has access.</returns>
|
||||
public bool GetReadAccess(AVUser user) {
|
||||
return GetReadAccess(user.ObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the given user id is *explicitly* allowed to write this object.
|
||||
/// Even if this returns false, the user may still be able to write it if
|
||||
/// PublicReadAccess is true or a role that the user belongs to has write access.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user objectId to check.</param>
|
||||
/// <returns>Whether the user has access.</returns>
|
||||
public bool GetWriteAccess(string userId) {
|
||||
return GetAccess(AccessKind.Write, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the given user is *explicitly* allowed to write this object.
|
||||
/// Even if this returns false, the user may still be able to write it if
|
||||
/// PublicReadAccess is true or a role that the user belongs to has write access.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to check.</param>
|
||||
/// <returns>Whether the user has access.</returns>
|
||||
public bool GetWriteAccess(AVUser user) {
|
||||
return GetWriteAccess(user.ObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether users belonging to the role with the given <paramref name="roleName"/>
|
||||
/// are allowed to read this object.
|
||||
/// </summary>
|
||||
/// <param name="roleName">The name of the role.</param>
|
||||
/// <param name="allowed">Whether the role has access.</param>
|
||||
public void SetRoleReadAccess(string roleName, bool allowed) {
|
||||
SetAccess(AccessKind.Read, "role:" + roleName, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether users belonging to the given role are allowed to read this object.
|
||||
/// </summary>
|
||||
/// <param name="role">The role.</param>
|
||||
/// <param name="allowed">Whether the role has access.</param>
|
||||
public void SetRoleReadAccess(AVRole role, bool allowed) {
|
||||
SetRoleReadAccess(role.Name, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether users belonging to the role with the given <paramref name="roleName"/>
|
||||
/// are allowed to read this object. Even if this returns false, the role may still be
|
||||
/// able to read it if a parent role has read access.
|
||||
/// </summary>
|
||||
/// <param name="roleName">The name of the role.</param>
|
||||
/// <returns>Whether the role has access.</returns>
|
||||
public bool GetRoleReadAccess(string roleName) {
|
||||
return GetAccess(AccessKind.Read, "role:" + roleName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether users belonging to the role are allowed to read this object.
|
||||
/// Even if this returns false, the role may still be able to read it if a
|
||||
/// parent role has read access.
|
||||
/// </summary>
|
||||
/// <param name="role">The name of the role.</param>
|
||||
/// <returns>Whether the role has access.</returns>
|
||||
public bool GetRoleReadAccess(AVRole role) {
|
||||
return GetRoleReadAccess(role.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether users belonging to the role with the given <paramref name="roleName"/>
|
||||
/// are allowed to write this object.
|
||||
/// </summary>
|
||||
/// <param name="roleName">The name of the role.</param>
|
||||
/// <param name="allowed">Whether the role has access.</param>
|
||||
public void SetRoleWriteAccess(string roleName, bool allowed) {
|
||||
SetAccess(AccessKind.Write, "role:" + roleName, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether users belonging to the given role are allowed to write this object.
|
||||
/// </summary>
|
||||
/// <param name="role">The role.</param>
|
||||
/// <param name="allowed">Whether the role has access.</param>
|
||||
public void SetRoleWriteAccess(AVRole role, bool allowed) {
|
||||
SetRoleWriteAccess(role.Name, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether users belonging to the role with the given <paramref name="roleName"/>
|
||||
/// are allowed to write this object. Even if this returns false, the role may still be
|
||||
/// able to write it if a parent role has write access.
|
||||
/// </summary>
|
||||
/// <param name="roleName">The name of the role.</param>
|
||||
/// <returns>Whether the role has access.</returns>
|
||||
public bool GetRoleWriteAccess(string roleName) {
|
||||
return GetAccess(AccessKind.Write, "role:" + roleName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether users belonging to the role are allowed to write this object.
|
||||
/// Even if this returns false, the role may still be able to write it if a
|
||||
/// parent role has write access.
|
||||
/// </summary>
|
||||
/// <param name="role">The name of the role.</param>
|
||||
/// <returns>Whether the role has access.</returns>
|
||||
public bool GetRoleWriteAccess(AVRole role) {
|
||||
return GetRoleWriteAccess(role.Name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the class name for a subclass of AVObject.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class AVClassNameAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new AVClassName attribute.
|
||||
/// </summary>
|
||||
/// <param name="className">The class name to associate with the AVObject subclass.</param>
|
||||
public AVClassNameAttribute(string className)
|
||||
{
|
||||
this.ClassName = className;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the class name to associate with the AVObject subclass.
|
||||
/// </summary>
|
||||
public string ClassName { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// LeanCloud SDK 客户端类
|
||||
/// </summary>
|
||||
public static class AVClient {
|
||||
public static readonly string[] DateFormatStrings = {
|
||||
// Official ISO format
|
||||
"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'",
|
||||
// It's possible that the string converter server-side may trim trailing zeroes,
|
||||
// so these two formats cover ourselves from that.
|
||||
"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ff'Z'",
|
||||
"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'f'Z'",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// LeanCloud SDK 配置
|
||||
/// </summary>
|
||||
public struct Configuration {
|
||||
/// <summary>
|
||||
/// App Id
|
||||
/// </summary>
|
||||
public string ApplicationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// App Key
|
||||
/// </summary>
|
||||
public string ApplicationKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Master Key
|
||||
/// </summary>
|
||||
public string MasterKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional HTTP headers to be sent with network requests from the SDK.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> AdditionalHTTPHeaders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 存储服务器地址
|
||||
/// </summary>
|
||||
/// <value>The API server.</value>
|
||||
public string ApiServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 云引擎服务器地址
|
||||
/// </summary>
|
||||
/// <value>The engine server uri.</value>
|
||||
public string EngineServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 即时通信服务器地址
|
||||
/// </summary>
|
||||
/// <value>The RTMR outer.</value>
|
||||
public string RTMServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 直连即时通信服务器地址
|
||||
/// </summary>
|
||||
/// <value>The realtime server.</value>
|
||||
public string RealtimeServer { get; set; }
|
||||
|
||||
public Uri PushServer { get; set; }
|
||||
|
||||
public Uri StatsServer { get; set; }
|
||||
}
|
||||
|
||||
private static readonly object mutex = new object();
|
||||
|
||||
static AVClient() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// LeanCloud SDK 当前配置
|
||||
/// </summary>
|
||||
public static Configuration CurrentConfiguration { get; internal set; }
|
||||
|
||||
internal static string APIVersion {
|
||||
get {
|
||||
return "1.1";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Name {
|
||||
get {
|
||||
return "LeanCloud-CSharp-SDK";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Version {
|
||||
get {
|
||||
return "0.1.0";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 LeanCloud SDK
|
||||
/// </summary>
|
||||
/// <param name="applicationId">App Id</param>
|
||||
/// <param name="applicationKey">App Key</param>
|
||||
public static void Initialize(string applicationId, string applicationKey) {
|
||||
Initialize(new Configuration {
|
||||
ApplicationId = applicationId,
|
||||
ApplicationKey = applicationKey
|
||||
});
|
||||
}
|
||||
|
||||
internal static Action<string> LogTracker { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动日志打印
|
||||
/// </summary>
|
||||
/// <param name="trace"></param>
|
||||
public static void HttpLog(Action<string> trace) {
|
||||
LogTracker = trace;
|
||||
}
|
||||
/// <summary>
|
||||
/// 打印 HTTP 访问日志
|
||||
/// </summary>
|
||||
/// <param name="log"></param>
|
||||
public static void PrintLog(string log) {
|
||||
LogTracker?.Invoke(log);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用生产环境
|
||||
/// </summary>
|
||||
public static bool UseProduction {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用 MasterKey
|
||||
/// </summary>
|
||||
public static bool UseMasterKey {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 LeanCloud
|
||||
/// </summary>
|
||||
/// <param name="configuration">初始化配置</param>
|
||||
public static void Initialize(Configuration configuration) {
|
||||
CurrentConfiguration = configuration;
|
||||
|
||||
AVObject.RegisterSubclass<AVUser>();
|
||||
AVObject.RegisterSubclass<AVRole>();
|
||||
}
|
||||
|
||||
internal static void Clear() {
|
||||
AVPlugins.Instance.AppRouterController.Clear();
|
||||
AVPlugins.Instance.Reset();
|
||||
}
|
||||
|
||||
public static string BuildQueryString(IDictionary<string, object> parameters) {
|
||||
return string.Join("&", (from pair in parameters
|
||||
let valueString = pair.Value as string
|
||||
select string.Format("{0}={1}",
|
||||
Uri.EscapeDataString(pair.Key),
|
||||
Uri.EscapeDataString(string.IsNullOrEmpty(valueString) ?
|
||||
JsonConvert.SerializeObject(pair.Value) : valueString)))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
internal static IDictionary<string, string> DecodeQueryString(string queryString) {
|
||||
var dict = new Dictionary<string, string>();
|
||||
foreach (var pair in queryString.Split('&')) {
|
||||
var parts = pair.Split(new char[] { '=' }, 2);
|
||||
dict[parts[0]] = parts.Length == 2 ? Uri.UnescapeDataString(parts[1].Replace("+", " ")) : null;
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// AVCloud,提供与 LeanCloud 云函数交互的接口
|
||||
/// </summary>
|
||||
public static class AVCloud {
|
||||
internal static AVCloudCodeController CloudCodeController {
|
||||
get {
|
||||
return AVPlugins.Instance.CloudCodeController;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls a cloud function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data you will receive from the cloud function. This
|
||||
/// can be an IDictionary, string, IList, AVObject, or any other type supported by
|
||||
/// AVObject.</typeparam>
|
||||
/// <param name="name">The cloud function to call.</param>
|
||||
/// <param name="parameters">The parameters to send to the cloud function. This
|
||||
/// dictionary can contain anything that could be passed into a AVObject except for
|
||||
/// AVObjects themselves.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The result of the cloud call.</returns>
|
||||
public static Task<T> CallFunctionAsync<T>(string name, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default) {
|
||||
return CloudCodeController.CallFunctionAsync<T>(name, parameters, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 远程调用云函数,返回结果会反序列化为 <see cref="AVObject"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<T> RPCFunctionAsync<T>(string name, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default) {
|
||||
return CloudCodeController.RPCFunction<T>(name, parameters, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求发送验证码。
|
||||
/// </summary>
|
||||
/// <returns>是否发送成功。</returns>
|
||||
/// <param name="mobilePhoneNumber">手机号。</param>
|
||||
/// <param name="name">应用名称。</param>
|
||||
/// <param name="op">进行的操作名称。</param>
|
||||
/// <param name="ttl">验证码失效时间。</param>
|
||||
/// <param name="cancellationToken">Cancellation token。</param>
|
||||
public static Task RequestSMSCodeAsync(string mobilePhoneNumber, string name, string op, int ttl = 10, CancellationToken cancellationToken = default) {
|
||||
if (string.IsNullOrEmpty(mobilePhoneNumber)) {
|
||||
throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null);
|
||||
}
|
||||
|
||||
Dictionary<string, object> strs = new Dictionary<string, object>()
|
||||
{
|
||||
{ "mobilePhoneNumber", mobilePhoneNumber },
|
||||
};
|
||||
if (!string.IsNullOrEmpty(name)) {
|
||||
strs.Add("name", name);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(op)) {
|
||||
strs.Add("op", op);
|
||||
}
|
||||
if (ttl > 0) {
|
||||
strs.Add("TTL", ttl);
|
||||
}
|
||||
var command = new EngineCommand {
|
||||
Path = "requestSmsCode",
|
||||
Method = HttpMethod.Post,
|
||||
Content = strs
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求发送验证码。
|
||||
/// </summary>
|
||||
/// <returns>是否发送成功。</returns>
|
||||
/// <param name="mobilePhoneNumber">手机号。</param>
|
||||
public static Task RequestSMSCodeAsync(string mobilePhoneNumber) {
|
||||
return RequestSMSCodeAsync(mobilePhoneNumber, CancellationToken.None);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 请求发送验证码。
|
||||
/// </summary>
|
||||
/// <returns>是否发送成功。</returns>
|
||||
/// <param name="mobilePhoneNumber">手机号。</param>
|
||||
/// <param name="cancellationToken">Cancellation Token.</param>
|
||||
public static Task RequestSMSCodeAsync(string mobilePhoneNumber, CancellationToken cancellationToken) {
|
||||
return RequestSMSCodeAsync(mobilePhoneNumber, null, null, 0, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送手机短信,并指定模板以及传入模板所需的参数。
|
||||
/// </summary>
|
||||
/// <param name="mobilePhoneNumber"></param>
|
||||
/// <param name="template"></param>
|
||||
/// <param name="env"></param>
|
||||
/// <param name="sign"></param>
|
||||
/// <param name="validateToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task RequestSMSCodeAsync(
|
||||
string mobilePhoneNumber,
|
||||
string template,
|
||||
IDictionary<string, object> env,
|
||||
string sign = "",
|
||||
string validateToken = "") {
|
||||
|
||||
if (string.IsNullOrEmpty(mobilePhoneNumber)) {
|
||||
throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null);
|
||||
}
|
||||
Dictionary<string, object> strs = new Dictionary<string, object>()
|
||||
{
|
||||
{ "mobilePhoneNumber", mobilePhoneNumber },
|
||||
};
|
||||
strs.Add("template", template);
|
||||
if (string.IsNullOrEmpty(sign)) {
|
||||
strs.Add("sign", sign);
|
||||
}
|
||||
if (string.IsNullOrEmpty(validateToken)) {
|
||||
strs.Add("validate_token", validateToken);
|
||||
}
|
||||
foreach (var key in env.Keys) {
|
||||
strs.Add(key, env[key]);
|
||||
}
|
||||
var command = new EngineCommand {
|
||||
Path = "requestSmsCode",
|
||||
Method = HttpMethod.Post,
|
||||
Content = strs
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="mobilePhoneNumber"></param>
|
||||
/// <returns></returns>
|
||||
public static Task RequestVoiceCodeAsync(string mobilePhoneNumber) {
|
||||
if (string.IsNullOrEmpty(mobilePhoneNumber)) {
|
||||
throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null);
|
||||
}
|
||||
Dictionary<string, object> body = new Dictionary<string, object>()
|
||||
{
|
||||
{ "mobilePhoneNumber", mobilePhoneNumber },
|
||||
{ "smsType", "voice" },
|
||||
{ "IDD", "+86" }
|
||||
};
|
||||
|
||||
var command = new EngineCommand {
|
||||
Path = "requestSmsCode",
|
||||
Method = HttpMethod.Post,
|
||||
Content = body
|
||||
};
|
||||
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证是否是有效短信验证码。
|
||||
/// </summary>
|
||||
/// <returns>是否验证通过。</returns>
|
||||
/// <param name="mobilePhoneNumber">手机号</param>
|
||||
/// <param name="code">验证码。</param>
|
||||
public static Task VerifySmsCodeAsync(string code, string mobilePhoneNumber) {
|
||||
return VerifySmsCodeAsync(code, mobilePhoneNumber, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证是否是有效短信验证码。
|
||||
/// </summary>
|
||||
/// <returns>是否验证通过。</returns>
|
||||
/// <param name="code">验证码。</param>
|
||||
/// <param name="mobilePhoneNumber">手机号</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static Task VerifySmsCodeAsync(string code, string mobilePhoneNumber, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand {
|
||||
Path = $"verifySmsCode/{code.Trim()}?mobilePhoneNumber={mobilePhoneNumber.Trim()}",
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stands for a captcha result.
|
||||
/// </summary>
|
||||
public class Captcha {
|
||||
/// <summary>
|
||||
/// Used for captcha verify.
|
||||
/// </summary>
|
||||
public string Token { internal set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// Captcha image URL.
|
||||
/// </summary>
|
||||
public string Url { internal set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// Verify the user's input of catpcha.
|
||||
/// </summary>
|
||||
/// <param name="code">User's input of this captcha.</param>
|
||||
/// <returns></returns>
|
||||
public Task VerifyAsync(string code) {
|
||||
return AVCloud.VerifyCaptchaAsync(code, Token);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a captcha image.
|
||||
/// </summary>
|
||||
/// <param name="width">captcha image width.</param>
|
||||
/// <param name="height">captcha image height.</param>
|
||||
/// <param name="cancellationToken">CancellationToken.</param>
|
||||
/// <returns>an instance of Captcha.</returns>
|
||||
public static Task<Captcha> RequestCaptchaAsync(int width = 85, int height = 30, CancellationToken cancellationToken = default) {
|
||||
var path = string.Format("requestCaptcha?width={0}&height={1}", width, height);
|
||||
var command = new AVCommand {
|
||||
Path = $"requestCaptcha?width={width}&height={height}",
|
||||
Method = HttpMethod.Get
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken).OnSuccess(t => {
|
||||
var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary<string, object>;
|
||||
return new Captcha() {
|
||||
Token = decoded["captcha_token"] as string,
|
||||
Url = decoded["captcha_url"] as string,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify the user's input of catpcha.
|
||||
/// </summary>
|
||||
/// <param name="token">The captcha's token, from server.</param>
|
||||
/// <param name="code">User's input of this captcha.</param>
|
||||
/// <param name="cancellationToken">CancellationToken.</param>
|
||||
/// <returns></returns>
|
||||
public static Task<string> VerifyCaptchaAsync(string code, string token, CancellationToken cancellationToken = default) {
|
||||
var data = new Dictionary<string, object> {
|
||||
{ "captcha_token", token },
|
||||
{ "captcha_code", code },
|
||||
};
|
||||
var command = new AVCommand {
|
||||
Path = "verifyCaptcha",
|
||||
Method = HttpMethod.Post,
|
||||
Content = data
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken).ContinueWith(t => {
|
||||
if (!t.Result.Item2.ContainsKey("validate_token"))
|
||||
throw new KeyNotFoundException("validate_token");
|
||||
return t.Result.Item2["validate_token"] as string;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the custom cloud parameters, you can set them at console https://leancloud.cn/dashboard/devcomponent.html?appid={your_app_Id}#/component/custom_param
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<IDictionary<string, object>> GetCustomParametersAsync(CancellationToken cancellationToken = default) {
|
||||
var command = new AVCommand {
|
||||
Path = $"statistics/apps/{AVClient.CurrentConfiguration.ApplicationId}/sendPolicy",
|
||||
Method = HttpMethod.Get
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken).OnSuccess(t => {
|
||||
var settings = t.Result.Item2;
|
||||
var CloudParameters = settings["parameters"] as IDictionary<string, object>;
|
||||
return CloudParameters;
|
||||
});
|
||||
}
|
||||
|
||||
public class RealtimeSignature {
|
||||
public string Nonce { internal set; get; }
|
||||
public long Timestamp { internal set; get; }
|
||||
public string ClientId { internal set; get; }
|
||||
public string Signature { internal set; get; }
|
||||
}
|
||||
|
||||
public static Task<RealtimeSignature> RequestRealtimeSignatureAsync(CancellationToken cancellationToken = default) {
|
||||
var command = new AVCommand {
|
||||
Path = "rtm/sign",
|
||||
Method = HttpMethod.Post,
|
||||
Content = new Dictionary<string, string> {
|
||||
{ "session_token", AVUser.CurrentUser?.SessionToken }
|
||||
}
|
||||
};
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync<IDictionary<string, object>>(command, cancellationToken).OnSuccess(t => {
|
||||
var body = t.Result.Item2;
|
||||
return new RealtimeSignature() {
|
||||
Nonce = body["nonce"] as string,
|
||||
Timestamp = (long)body["timestamp"],
|
||||
ClientId = body["client_id"] as string,
|
||||
Signature = body["signature"] as string,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LeanEngine hosting URL for current app async.
|
||||
/// </summary>
|
||||
/// <returns>The lean engine hosting URL async.</returns>
|
||||
public static Task<string> GetLeanEngineHostingUrlAsync() {
|
||||
return CallFunctionAsync<string>("_internal_extensions_get_domain");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Exceptions that may occur when sending requests to LeanCloud.
|
||||
/// </summary>
|
||||
public class AVException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Error codes that may be delivered in response to requests to LeanCloud.
|
||||
/// </summary>
|
||||
public enum ErrorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Error code indicating that an unknown error or an error unrelated to LeanCloud
|
||||
/// occurred.
|
||||
/// </summary>
|
||||
OtherCause = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that something has gone wrong with the server.
|
||||
/// If you get this error code, it is LeanCloud's fault.
|
||||
/// </summary>
|
||||
InternalServerError = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating the connection to the LeanCloud servers failed.
|
||||
/// </summary>
|
||||
ConnectionFailed = 100,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating the specified object doesn't exist.
|
||||
/// </summary>
|
||||
ObjectNotFound = 101,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating you tried to query with a datatype that doesn't
|
||||
/// support it, like exact matching an array or object.
|
||||
/// </summary>
|
||||
InvalidQuery = 102,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating a missing or invalid classname. Classnames are
|
||||
/// case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the
|
||||
/// only valid characters.
|
||||
/// </summary>
|
||||
InvalidClassName = 103,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating an unspecified object id.
|
||||
/// </summary>
|
||||
MissingObjectId = 104,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating an invalid key name. Keys are case-sensitive. They
|
||||
/// must start with a letter, and a-zA-Z0-9_ are the only valid characters.
|
||||
/// </summary>
|
||||
InvalidKeyName = 105,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating a malformed pointer. You should not see this unless
|
||||
/// you have been mucking about changing internal LeanCloud code.
|
||||
/// </summary>
|
||||
InvalidPointer = 106,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that badly formed JSON was received upstream. This
|
||||
/// either indicates you have done something unusual with modifying how
|
||||
/// things encode to JSON, or the network is failing badly.
|
||||
/// </summary>
|
||||
InvalidJSON = 107,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the feature you tried to access is only
|
||||
/// available internally for testing purposes.
|
||||
/// </summary>
|
||||
CommandUnavailable = 108,
|
||||
|
||||
/// <summary>
|
||||
/// You must call LeanCloud.initialize before using the LeanCloud library.
|
||||
/// </summary>
|
||||
NotInitialized = 109,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a field was set to an inconsistent type.
|
||||
/// </summary>
|
||||
IncorrectType = 111,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating an invalid channel name. A channel name is either
|
||||
/// an empty string (the broadcast channel) or contains only a-zA-Z0-9_
|
||||
/// characters and starts with a letter.
|
||||
/// </summary>
|
||||
InvalidChannelName = 112,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that push is misconfigured.
|
||||
/// </summary>
|
||||
PushMisconfigured = 115,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the object is too large.
|
||||
/// </summary>
|
||||
ObjectTooLarge = 116,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the operation isn't allowed for clients.
|
||||
/// </summary>
|
||||
OperationForbidden = 119,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating the result was not found in the cache.
|
||||
/// </summary>
|
||||
CacheMiss = 120,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that an invalid key was used in a nested
|
||||
/// JSONObject.
|
||||
/// </summary>
|
||||
InvalidNestedKey = 121,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that an invalid filename was used for AVFile.
|
||||
/// A valid file name contains only a-zA-Z0-9_. characters and is between 1
|
||||
/// and 128 characters.
|
||||
/// </summary>
|
||||
InvalidFileName = 122,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating an invalid ACL was provided.
|
||||
/// </summary>
|
||||
InvalidACL = 123,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the request timed out on the server. Typically
|
||||
/// this indicates that the request is too expensive to run.
|
||||
/// </summary>
|
||||
Timeout = 124,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the email address was invalid.
|
||||
/// </summary>
|
||||
InvalidEmailAddress = 125,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a unique field was given a value that is
|
||||
/// already taken.
|
||||
/// </summary>
|
||||
DuplicateValue = 137,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a role's name is invalid.
|
||||
/// </summary>
|
||||
InvalidRoleName = 139,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that an application quota was exceeded. Upgrade to
|
||||
/// resolve.
|
||||
/// </summary>
|
||||
ExceededQuota = 140,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a Cloud Code script failed.
|
||||
/// </summary>
|
||||
ScriptFailed = 141,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a Cloud Code validation failed.
|
||||
/// </summary>
|
||||
ValidationFailed = 142,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that deleting a file failed.
|
||||
/// </summary>
|
||||
FileDeleteFailed = 153,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the application has exceeded its request limit.
|
||||
/// </summary>
|
||||
RequestLimitExceeded = 155,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the provided event name is invalid.
|
||||
/// </summary>
|
||||
InvalidEventName = 160,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the username is missing or empty.
|
||||
/// </summary>
|
||||
UsernameMissing = 200,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the password is missing or empty.
|
||||
/// </summary>
|
||||
PasswordMissing = 201,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the username has already been taken.
|
||||
/// </summary>
|
||||
UsernameTaken = 202,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the email has already been taken.
|
||||
/// </summary>
|
||||
EmailTaken = 203,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the email is missing, but must be specified.
|
||||
/// </summary>
|
||||
EmailMissing = 204,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a user with the specified email was not found.
|
||||
/// </summary>
|
||||
EmailNotFound = 205,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a user object without a valid session could
|
||||
/// not be altered.
|
||||
/// </summary>
|
||||
SessionMissing = 206,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a user can only be created through signup.
|
||||
/// </summary>
|
||||
MustCreateUserThroughSignup = 207,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that an an account being linked is already linked
|
||||
/// to another user.
|
||||
/// </summary>
|
||||
AccountAlreadyLinked = 208,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the current session token is invalid.
|
||||
/// </summary>
|
||||
InvalidSessionToken = 209,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a user cannot be linked to an account because
|
||||
/// that account's id could not be found.
|
||||
/// </summary>
|
||||
LinkedIdMissing = 250,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a user with a linked (e.g. Facebook) account
|
||||
/// has an invalid session.
|
||||
/// </summary>
|
||||
InvalidLinkedSession = 251,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a service being linked (e.g. Facebook or
|
||||
/// Twitter) is unsupported.
|
||||
/// </summary>
|
||||
UnsupportedService = 252,
|
||||
|
||||
/// <summary>
|
||||
/// 手机号不合法
|
||||
/// </summary>
|
||||
MobilePhoneInvalid = 253
|
||||
}
|
||||
|
||||
internal AVException(ErrorCode code, string message, Exception cause = null)
|
||||
: base(message, cause)
|
||||
{
|
||||
this.Code = code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The LeanCloud error code associated with the exception.
|
||||
/// </summary>
|
||||
public ErrorCode Code { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// Provides convenience extension methods for working with collections
|
||||
/// of AVObjects so that you can easily save and fetch them in batches.
|
||||
/// </summary>
|
||||
public static class AVExtensions {
|
||||
/// <summary>
|
||||
/// Saves all of the AVObjects in the enumeration. Equivalent to
|
||||
/// calling <see cref="AVObject.SaveAllAsync{T}(IEnumerable{T})"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to save.</param>
|
||||
public static Task SaveAllAsync<T>(this IEnumerable<T> objects) where T : AVObject {
|
||||
return AVObject.SaveAllAsync(objects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves all of the AVObjects in the enumeration. Equivalent to
|
||||
/// calling
|
||||
/// <see cref="AVObject.SaveAllAsync{T}(IEnumerable{T}, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to save.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public static Task SaveAllAsync<T>(
|
||||
this IEnumerable<T> objects, CancellationToken cancellationToken) where T : AVObject {
|
||||
return AVObject.SaveAllAsync(objects, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all of the objects in the enumeration. Equivalent to
|
||||
/// calling <see cref="AVObject.FetchAllAsync{T}(IEnumerable{T})"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to save.</param>
|
||||
public static Task<IEnumerable<T>> FetchAllAsync<T>(this IEnumerable<T> objects)
|
||||
where T : AVObject {
|
||||
return AVObject.FetchAllAsync(objects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all of the objects in the enumeration. Equivalent to
|
||||
/// calling
|
||||
/// <see cref="AVObject.FetchAllAsync{T}(IEnumerable{T}, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to fetch.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public static Task<IEnumerable<T>> FetchAllAsync<T>(
|
||||
this IEnumerable<T> objects, CancellationToken cancellationToken)
|
||||
where T : AVObject {
|
||||
return AVObject.FetchAllAsync(objects, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all of the objects in the enumeration that don't already have
|
||||
/// data. Equivalent to calling
|
||||
/// <see cref="AVObject.FetchAllIfNeededAsync{T}(IEnumerable{T})"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to fetch.</param>
|
||||
public static Task<IEnumerable<T>> FetchAllIfNeededAsync<T>(
|
||||
this IEnumerable<T> objects)
|
||||
where T : AVObject {
|
||||
return AVObject.FetchAllIfNeededAsync(objects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all of the objects in the enumeration that don't already have
|
||||
/// data. Equivalent to calling
|
||||
/// <see cref="AVObject.FetchAllIfNeededAsync{T}(IEnumerable{T}, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to fetch.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public static Task<IEnumerable<T>> FetchAllIfNeededAsync<T>(
|
||||
this IEnumerable<T> objects, CancellationToken cancellationToken)
|
||||
where T : AVObject {
|
||||
return AVObject.FetchAllIfNeededAsync(objects, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a query that is the or of the given queries.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of AVObject being queried.</typeparam>
|
||||
/// <param name="source">An initial query to 'or' with additional queries.</param>
|
||||
/// <param name="queries">The list of AVQueries to 'or' together.</param>
|
||||
/// <returns>A query that is the or of the given queries.</returns>
|
||||
public static AVQuery<T> Or<T>(this AVQuery<T> source, params AVQuery<T>[] queries)
|
||||
where T : AVObject {
|
||||
return AVQuery<T>.Or(queries.Concat(new[] { source }));
|
||||
}
|
||||
|
||||
public static Task<T> FetchAsync<T>(this T obj,
|
||||
IEnumerable<string> keys = null, IEnumerable<string> includes = null, AVACL includeACL = null,
|
||||
CancellationToken cancellationToken = default) where T : AVObject {
|
||||
var queryString = new Dictionary<string, object>();
|
||||
if (keys != null) {
|
||||
var encode = string.Join(",", keys.ToArray());
|
||||
queryString.Add("keys", encode);
|
||||
}
|
||||
if (includes != null) {
|
||||
var encode = string.Join(",", includes.ToArray());
|
||||
queryString.Add("include", encode);
|
||||
}
|
||||
if (includeACL != null) {
|
||||
queryString.Add("returnACL", includeACL);
|
||||
}
|
||||
return obj.FetchAsyncInternal(queryString, cancellationToken).OnSuccess(t => (T)t.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this AVObject has not been fetched (i.e. <see cref="AVObject.IsDataAvailable"/> returns
|
||||
/// false), fetches this object with the data from the server.
|
||||
/// </summary>
|
||||
/// <param name="obj">The AVObject to fetch.</param>
|
||||
public static Task<T> FetchIfNeededAsync<T>(this T obj) where T : AVObject {
|
||||
return obj.FetchIfNeededAsyncInternal(CancellationToken.None).OnSuccess(t => (T)t.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this AVObject has not been fetched (i.e. <see cref="AVObject.IsDataAvailable"/> returns
|
||||
/// false), fetches this object with the data from the server.
|
||||
/// </summary>
|
||||
/// <param name="obj">The AVObject to fetch.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public static Task<T> FetchIfNeededAsync<T>(this T obj, CancellationToken cancellationToken)
|
||||
where T : AVObject {
|
||||
return obj.FetchIfNeededAsyncInternal(cancellationToken).OnSuccess(t => (T)t.Result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// Specifies a field name for a property on a AVObject subclass.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class AVFieldNameAttribute : Attribute {
|
||||
/// <summary>
|
||||
/// Constructs a new AVFieldName attribute.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">The name of the field on the AVObject that the
|
||||
/// property represents.</param>
|
||||
public AVFieldNameAttribute(string fieldName) {
|
||||
FieldName = fieldName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the field represented by this property.
|
||||
/// </summary>
|
||||
public string FieldName { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,556 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// AVFile 存储于 LeanCloud 的文件类
|
||||
/// </summary>
|
||||
public class AVFile : IJsonConvertible {
|
||||
private FileState state;
|
||||
private readonly Stream dataStream;
|
||||
private readonly TaskQueue taskQueue = new TaskQueue();
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// 通过文件名,数据流,文件类型,文件源信息构建一个 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">数据流</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
/// <param name="metaData">文件源信息</param>
|
||||
public AVFile(string name, Stream data, string mimeType = null, IDictionary<string, object> metaData = null) {
|
||||
mimeType = mimeType ?? GetMIMEType(name);
|
||||
state = new FileState {
|
||||
Name = name,
|
||||
MimeType = mimeType,
|
||||
MetaData = metaData
|
||||
};
|
||||
dataStream = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,文件 Byte 数组,以及文件类型构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件 Byte 数组</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
public AVFile(string name, byte[] data, string mimeType = null)
|
||||
: this(name, new MemoryStream(data), mimeType) { }
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,文件流数据,文件类型构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件流数据</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
public AVFile(string name, Stream data, string mimeType = null)
|
||||
: this(name, data, mimeType, new Dictionary<string, object>()) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 byte 数组以及文件名创建文件
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件的 byte[] 数据</param>
|
||||
public AVFile(string name, byte[] data)
|
||||
: this(name, new MemoryStream(data), new Dictionary<string, object>()) {
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,数据 byte[] 数组以及元数据创建文件
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件的 byte[] 数据</param>
|
||||
/// <param name="metaData">元数据</param>
|
||||
public AVFile(string name, byte[] data, IDictionary<string, object> metaData)
|
||||
: this(name, new MemoryStream(data), metaData) {
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,数据流以及元数据创建文件
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件的数据流</param>
|
||||
/// <param name="metaData">元数据</param>
|
||||
public AVFile(string name, Stream data, IDictionary<string, object> metaData)
|
||||
: this(name, data, GetMIMEType(name), metaData) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,数据流以及元数据创建文件
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件的数据流</param>
|
||||
public AVFile(string name, Stream data)
|
||||
: this(name, data, new Dictionary<string, object>()) {
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region created by url or uri
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,Uri,文件类型以及文件源信息
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="uri">文件Uri</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
/// <param name="metaData">文件源信息</param>
|
||||
public AVFile(string name, Uri uri, string mimeType = null, IDictionary<string, object> metaData = null) {
|
||||
mimeType = mimeType == null ? GetMIMEType(name) : mimeType;
|
||||
state = new FileState {
|
||||
Name = name,
|
||||
Url = uri,
|
||||
MetaData = metaData,
|
||||
MimeType = mimeType
|
||||
};
|
||||
IsExternal = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,文件 Url,文件类型,文件源信息构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="url">文件 Url</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
/// <param name="metaData">文件源信息</param>
|
||||
public AVFile(string name, string url, string mimeType = null, IDictionary<string, object> metaData = null)
|
||||
: this(name, new Uri(url), mimeType, metaData) {
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,文件 Url以及文件的源信息构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="url">文件 Url</param>
|
||||
/// <param name="metaData">文件源信息</param>
|
||||
public AVFile(string name, string url, IDictionary<string, object> metaData)
|
||||
: this(name, url, null, metaData) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,文件 Uri,以及文件类型构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="uri">文件 Uri</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
public AVFile(string name, Uri uri, string mimeType = null)
|
||||
: this(name, uri, mimeType, new Dictionary<string, object>()) {
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名以及文件 Uri 构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="uri">文件 Uri</param>
|
||||
public AVFile(string name, Uri uri)
|
||||
: this(name, uri, null, new Dictionary<string, object>()) {
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据文件名和 Url 创建文件
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="url">文件的 Url</param>
|
||||
public AVFile(string name, string url)
|
||||
: this(name, new Uri(url)) {
|
||||
}
|
||||
|
||||
internal AVFile(FileState filestate) {
|
||||
state = filestate;
|
||||
}
|
||||
|
||||
internal AVFile(string objectId)
|
||||
: this(new FileState {
|
||||
ObjectId = objectId
|
||||
}) {
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the file still needs to be saved.
|
||||
/// </summary>
|
||||
public bool IsDirty {
|
||||
get {
|
||||
return state.Url == null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the file. Before save is called, this is the filename given by
|
||||
/// the user. After save is called, that name gets prefixed with a unique identifier.
|
||||
/// </summary>
|
||||
[AVFieldName("name")]
|
||||
public string Name {
|
||||
get {
|
||||
return state.Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MIME type of the file. This is either passed in to the constructor or
|
||||
/// inferred from the file extension. "unknown/unknown" will be used if neither is
|
||||
/// available.
|
||||
/// </summary>
|
||||
public string MimeType {
|
||||
get {
|
||||
return state.MimeType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url of the file. It is only available after you save the file or after
|
||||
/// you get the file from a <see cref="AVObject"/>.
|
||||
/// </summary>
|
||||
[AVFieldName("url")]
|
||||
public Uri Url {
|
||||
get {
|
||||
return state.Url;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取缩略图
|
||||
/// </summary>
|
||||
/// <param name="width">宽</param>
|
||||
/// <param name="height">高</param>
|
||||
/// <param name="quality">质量 1-100</param>
|
||||
/// <param name="scaleToFit">等比缩放,是否裁剪</param>
|
||||
/// <param name="format">格式</param>
|
||||
/// <returns></returns>
|
||||
public string GetThumbnailUrl(int width, int height,
|
||||
int quality = 100, bool scaleToFit = true, string format = "png") {
|
||||
int mode = scaleToFit ? 2 : 1;
|
||||
return $"{Url}?imageView/{mode}/w/{width}/h/{height}/q/{quality}/format/{format}";
|
||||
}
|
||||
|
||||
internal static AVFileController FileController {
|
||||
get {
|
||||
return AVPlugins.Instance.FileController;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
IDictionary<string, object> IJsonConvertible.ToJSON() {
|
||||
if (IsDirty) {
|
||||
throw new InvalidOperationException(
|
||||
"AVFile must be saved before it can be serialized.");
|
||||
}
|
||||
return new Dictionary<string, object> {
|
||||
{ "__type", "File"} ,
|
||||
{ "id", ObjectId },
|
||||
{ "name", Name },
|
||||
{ "url", Url.AbsoluteUri }
|
||||
};
|
||||
}
|
||||
|
||||
#region Save
|
||||
|
||||
/// <summary>
|
||||
/// 保存文件
|
||||
/// </summary>
|
||||
/// <param name="progress">The progress callback.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public Task SaveAsync(IProgress<AVUploadProgressEventArgs> progress = null, CancellationToken cancellationToken = default) {
|
||||
return FileController.SaveAsync(state, dataStream, progress, cancellationToken)
|
||||
.OnSuccess(t => {
|
||||
state = t.Result;
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Compatible
|
||||
|
||||
/// <summary>
|
||||
/// 文件在 LeanCloud 的唯一Id 标识
|
||||
/// </summary>
|
||||
public string ObjectId {
|
||||
get {
|
||||
return state.ObjectId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件的元数据。
|
||||
/// </summary>
|
||||
public IDictionary<string, object> MetaData {
|
||||
get {
|
||||
return state.MetaData;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件是否为外链文件。
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// </value>
|
||||
public bool IsExternal {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
internal static string GetMIMEType(string fileName) {
|
||||
try {
|
||||
string str = Path.GetExtension(fileName).Remove(0, 1);
|
||||
if (!MIMETypesDictionary.ContainsKey(str)) {
|
||||
return "unknown/unknown";
|
||||
}
|
||||
return MIMETypesDictionary[str];
|
||||
} catch {
|
||||
return "unknown/unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 ObjectId 获取文件
|
||||
/// </summary>
|
||||
/// <remarks>获取之后并没有实际执行下载,只是加载了文件的元信息以及物理地址(Url)
|
||||
/// </remarks>
|
||||
public static Task<AVFile> GetFileWithObjectIdAsync(string objectId, CancellationToken cancellationToken = default) {
|
||||
return FileController.GetAsync(objectId, cancellationToken).OnSuccess(_ => {
|
||||
var filestate = _.Result;
|
||||
return new AVFile(filestate);
|
||||
});
|
||||
}
|
||||
|
||||
public static AVFile CreateWithoutData(string objectId) {
|
||||
return new AVFile(objectId);
|
||||
}
|
||||
|
||||
public static AVFile CreateWithState(FileState state) {
|
||||
return new AVFile(state);
|
||||
}
|
||||
|
||||
public static AVFile CreateWithData(string objectId, string name, string url, IDictionary<string, object> metaData) {
|
||||
var fileState = new FileState {
|
||||
Name = name,
|
||||
ObjectId = objectId,
|
||||
Url = new Uri(url),
|
||||
MetaData = metaData
|
||||
};
|
||||
return CreateWithState(fileState);
|
||||
}
|
||||
|
||||
internal void MergeFromJSON(IDictionary<string, object> data) {
|
||||
state = FileState.NewFromDict(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除文件
|
||||
/// </summary>
|
||||
/// <returns>Task</returns>
|
||||
public Task DeleteAsync(CancellationToken cancellationToken = default) {
|
||||
if (ObjectId == null) {
|
||||
return Task.FromException(new ArgumentNullException(nameof(ObjectId)));
|
||||
}
|
||||
return FileController.DeleteAsync(state, cancellationToken);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private readonly static Dictionary<string, string> MIMETypesDictionary = new Dictionary<string, string> {
|
||||
{ "ai", "application/postscript" },
|
||||
{ "aif", "audio/x-aiff" },
|
||||
{ "aifc", "audio/x-aiff" },
|
||||
{ "aiff", "audio/x-aiff" },
|
||||
{ "asc", "text/plain" },
|
||||
{ "atom", "application/atom+xml" },
|
||||
{ "au", "audio/basic" },
|
||||
{ "avi", "video/x-msvideo" },
|
||||
{ "bcpio", "application/x-bcpio" },
|
||||
{ "bin", "application/octet-stream" },
|
||||
{ "bmp", "image/bmp" },
|
||||
{ "cdf", "application/x-netcdf" },
|
||||
{ "cgm", "image/cgm" },
|
||||
{ "class", "application/octet-stream" },
|
||||
{ "cpio", "application/x-cpio" },
|
||||
{ "cpt", "application/mac-compactpro" },
|
||||
{ "csh", "application/x-csh" },
|
||||
{ "css", "text/css" },
|
||||
{ "dcr", "application/x-director" },
|
||||
{ "dif", "video/x-dv" },
|
||||
{ "dir", "application/x-director" },
|
||||
{ "djv", "image/vnd.djvu" },
|
||||
{ "djvu", "image/vnd.djvu" },
|
||||
{ "dll", "application/octet-stream" },
|
||||
{ "dmg", "application/octet-stream" },
|
||||
{ "dms", "application/octet-stream" },
|
||||
{ "doc", "application/msword" },
|
||||
{ "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
|
||||
{ "dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
|
||||
{ "docm", "application/vnd.ms-word.document.macroEnabled.12" },
|
||||
{ "dotm", "application/vnd.ms-word.template.macroEnabled.12" },
|
||||
{ "dtd", "application/xml-dtd" },
|
||||
{ "dv", "video/x-dv" },
|
||||
{ "dvi", "application/x-dvi" },
|
||||
{ "dxr", "application/x-director" },
|
||||
{ "eps", "application/postscript" },
|
||||
{ "etx", "text/x-setext" },
|
||||
{ "exe", "application/octet-stream" },
|
||||
{ "ez", "application/andrew-inset" },
|
||||
{ "gif", "image/gif" },
|
||||
{ "gram", "application/srgs" },
|
||||
{ "grxml", "application/srgs+xml" },
|
||||
{ "gtar", "application/x-gtar" },
|
||||
{ "hdf", "application/x-hdf" },
|
||||
{ "hqx", "application/mac-binhex40" },
|
||||
{ "htm", "text/html" },
|
||||
{ "html", "text/html" },
|
||||
{ "ice", "x-conference/x-cooltalk" },
|
||||
{ "ico", "image/x-icon" },
|
||||
{ "ics", "text/calendar" },
|
||||
{ "ief", "image/ief" },
|
||||
{ "ifb", "text/calendar" },
|
||||
{ "iges", "model/iges" },
|
||||
{ "igs", "model/iges" },
|
||||
{ "jnlp", "application/x-java-jnlp-file" },
|
||||
{ "jp2", "image/jp2" },
|
||||
{ "jpe", "image/jpeg" },
|
||||
{ "jpeg", "image/jpeg" },
|
||||
{ "jpg", "image/jpeg" },
|
||||
{ "js", "application/x-javascript" },
|
||||
{ "kar", "audio/midi" },
|
||||
{ "latex", "application/x-latex" },
|
||||
{ "lha", "application/octet-stream" },
|
||||
{ "lzh", "application/octet-stream" },
|
||||
{ "m3u", "audio/x-mpegurl" },
|
||||
{ "m4a", "audio/mp4a-latm" },
|
||||
{ "m4b", "audio/mp4a-latm" },
|
||||
{ "m4p", "audio/mp4a-latm" },
|
||||
{ "m4u", "video/vnd.mpegurl" },
|
||||
{ "m4v", "video/x-m4v" },
|
||||
{ "mac", "image/x-macpaint" },
|
||||
{ "man", "application/x-troff-man" },
|
||||
{ "mathml", "application/mathml+xml" },
|
||||
{ "me", "application/x-troff-me" },
|
||||
{ "mesh", "model/mesh" },
|
||||
{ "mid", "audio/midi" },
|
||||
{ "midi", "audio/midi" },
|
||||
{ "mif", "application/vnd.mif" },
|
||||
{ "mov", "video/quicktime" },
|
||||
{ "movie", "video/x-sgi-movie" },
|
||||
{ "mp2", "audio/mpeg" },
|
||||
{ "mp3", "audio/mpeg" },
|
||||
{ "mp4", "video/mp4" },
|
||||
{ "mpe", "video/mpeg" },
|
||||
{ "mpeg", "video/mpeg" },
|
||||
{ "mpg", "video/mpeg" },
|
||||
{ "mpga", "audio/mpeg" },
|
||||
{ "ms", "application/x-troff-ms" },
|
||||
{ "msh", "model/mesh" },
|
||||
{ "mxu", "video/vnd.mpegurl" },
|
||||
{ "nc", "application/x-netcdf" },
|
||||
{ "oda", "application/oda" },
|
||||
{ "ogg", "application/ogg" },
|
||||
{ "pbm", "image/x-portable-bitmap" },
|
||||
{ "pct", "image/pict" },
|
||||
{ "pdb", "chemical/x-pdb" },
|
||||
{ "pdf", "application/pdf" },
|
||||
{ "pgm", "image/x-portable-graymap" },
|
||||
{ "pgn", "application/x-chess-pgn" },
|
||||
{ "pic", "image/pict" },
|
||||
{ "pict", "image/pict" },
|
||||
{ "png", "image/png" },
|
||||
{ "pnm", "image/x-portable-anymap" },
|
||||
{ "pnt", "image/x-macpaint" },
|
||||
{ "pntg", "image/x-macpaint" },
|
||||
{ "ppm", "image/x-portable-pixmap" },
|
||||
{ "ppt", "application/vnd.ms-powerpoint" },
|
||||
{ "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
|
||||
{ "potx", "application/vnd.openxmlformats-officedocument.presentationml.template" },
|
||||
{ "ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" },
|
||||
{ "ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12" },
|
||||
{ "pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12" },
|
||||
{ "potm", "application/vnd.ms-powerpoint.template.macroEnabled.12" },
|
||||
{ "ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" },
|
||||
{ "ps", "application/postscript" },
|
||||
{ "qt", "video/quicktime" },
|
||||
{ "qti", "image/x-quicktime" },
|
||||
{ "qtif", "image/x-quicktime" },
|
||||
{ "ra", "audio/x-pn-realaudio" },
|
||||
{ "ram", "audio/x-pn-realaudio" },
|
||||
{ "ras", "image/x-cmu-raster" },
|
||||
{ "rdf", "application/rdf+xml" },
|
||||
{ "rgb", "image/x-rgb" },
|
||||
{ "rm", "application/vnd.rn-realmedia" },
|
||||
{ "roff", "application/x-troff" },
|
||||
{ "rtf", "text/rtf" },
|
||||
{ "rtx", "text/richtext" },
|
||||
{ "sgm", "text/sgml" },
|
||||
{ "sgml", "text/sgml" },
|
||||
{ "sh", "application/x-sh" },
|
||||
{ "shar", "application/x-shar" },
|
||||
{ "silo", "model/mesh" },
|
||||
{ "sit", "application/x-stuffit" },
|
||||
{ "skd", "application/x-koan" },
|
||||
{ "skm", "application/x-koan" },
|
||||
{ "skp", "application/x-koan" },
|
||||
{ "skt", "application/x-koan" },
|
||||
{ "smi", "application/smil" },
|
||||
{ "smil", "application/smil" },
|
||||
{ "snd", "audio/basic" },
|
||||
{ "so", "application/octet-stream" },
|
||||
{ "spl", "application/x-futuresplash" },
|
||||
{ "src", "application/x-wais-Source" },
|
||||
{ "sv4cpio", "application/x-sv4cpio" },
|
||||
{ "sv4crc", "application/x-sv4crc" },
|
||||
{ "svg", "image/svg+xml" },
|
||||
{ "swf", "application/x-shockwave-flash" },
|
||||
{ "t", "application/x-troff" },
|
||||
{ "tar", "application/x-tar" },
|
||||
{ "tcl", "application/x-tcl" },
|
||||
{ "tex", "application/x-tex" },
|
||||
{ "texi", "application/x-texinfo" },
|
||||
{ "texinfo", "application/x-texinfo" },
|
||||
{ "tif", "image/tiff" },
|
||||
{ "tiff", "image/tiff" },
|
||||
{ "tr", "application/x-troff" },
|
||||
{ "tsv", "text/tab-separated-values" },
|
||||
{ "txt", "text/plain" },
|
||||
{ "ustar", "application/x-ustar" },
|
||||
{ "vcd", "application/x-cdlink" },
|
||||
{ "vrml", "model/vrml" },
|
||||
{ "vxml", "application/voicexml+xml" },
|
||||
{ "wav", "audio/x-wav" },
|
||||
{ "wbmp", "image/vnd.wap.wbmp" },
|
||||
{ "wbmxl", "application/vnd.wap.wbxml" },
|
||||
{ "wml", "text/vnd.wap.wml" },
|
||||
{ "wmlc", "application/vnd.wap.wmlc" },
|
||||
{ "wmls", "text/vnd.wap.wmlscript" },
|
||||
{ "wmlsc", "application/vnd.wap.wmlscriptc" },
|
||||
{ "wrl", "model/vrml" },
|
||||
{ "xbm", "image/x-xbitmap" },
|
||||
{ "xht", "application/xhtml+xml" },
|
||||
{ "xhtml", "application/xhtml+xml" },
|
||||
{ "xls", "application/vnd.ms-excel" },
|
||||
{ "xml", "application/xml" },
|
||||
{ "xpm", "image/x-xpixmap" },
|
||||
{ "xsl", "application/xml" },
|
||||
{ "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
|
||||
{ "xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" },
|
||||
{ "xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12" },
|
||||
{ "xltm", "application/vnd.ms-excel.template.macroEnabled.12" },
|
||||
{ "xlam", "application/vnd.ms-excel.addin.macroEnabled.12" },
|
||||
{ "xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12" },
|
||||
{ "xslt", "application/xslt+xml" },
|
||||
{ "xul", "application/vnd.mozilla.xul+xml" },
|
||||
{ "xwd", "image/x-xwindowdump" },
|
||||
{ "xyz", "chemical/x-xyz" },
|
||||
{ "zip", "application/zip" }
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a distance between two AVGeoPoints.
|
||||
/// </summary>
|
||||
public struct AVGeoDistance
|
||||
{
|
||||
private const double EarthMeanRadiusKilometers = 6371.0;
|
||||
private const double EarthMeanRadiusMiles = 3958.8;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a AVGeoDistance.
|
||||
/// </summary>
|
||||
/// <param name="radians">The distance in radians.</param>
|
||||
public AVGeoDistance(double radians)
|
||||
: this()
|
||||
{
|
||||
Radians = radians;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance in radians.
|
||||
/// </summary>
|
||||
public double Radians { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance in miles.
|
||||
/// </summary>
|
||||
public double Miles
|
||||
{
|
||||
get
|
||||
{
|
||||
return Radians * EarthMeanRadiusMiles;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance in kilometers.
|
||||
/// </summary>
|
||||
public double Kilometers
|
||||
{
|
||||
get
|
||||
{
|
||||
return Radians * EarthMeanRadiusKilometers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a AVGeoDistance from a number of miles.
|
||||
/// </summary>
|
||||
/// <param name="miles">The number of miles.</param>
|
||||
/// <returns>A AVGeoDistance for the given number of miles.</returns>
|
||||
public static AVGeoDistance FromMiles(double miles)
|
||||
{
|
||||
return new AVGeoDistance(miles / EarthMeanRadiusMiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a AVGeoDistance from a number of kilometers.
|
||||
/// </summary>
|
||||
/// <param name="kilometers">The number of kilometers.</param>
|
||||
/// <returns>A AVGeoDistance for the given number of kilometers.</returns>
|
||||
public static AVGeoDistance FromKilometers(double kilometers)
|
||||
{
|
||||
return new AVGeoDistance(kilometers / EarthMeanRadiusKilometers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a AVGeoDistance from a number of radians.
|
||||
/// </summary>
|
||||
/// <param name="radians">The number of radians.</param>
|
||||
/// <returns>A AVGeoDistance for the given number of radians.</returns>
|
||||
public static AVGeoDistance FromRadians(double radians)
|
||||
{
|
||||
return new AVGeoDistance(radians);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// AVGeoPoint represents a latitude / longitude point that may be associated
|
||||
/// with a key in a AVObject or used as a reference point for geo queries.
|
||||
/// This allows proximity-based queries on the key.
|
||||
///
|
||||
/// Only one key in a class may contain a GeoPoint.
|
||||
/// </summary>
|
||||
public struct AVGeoPoint : IJsonConvertible
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a AVGeoPoint with the specified latitude and longitude.
|
||||
/// </summary>
|
||||
/// <param name="latitude">The point's latitude.</param>
|
||||
/// <param name="longitude">The point's longitude.</param>
|
||||
public AVGeoPoint(double latitude, double longitude)
|
||||
: this()
|
||||
{
|
||||
Latitude = latitude;
|
||||
Longitude = longitude;
|
||||
}
|
||||
|
||||
private double latitude;
|
||||
/// <summary>
|
||||
/// Gets or sets the latitude of the GeoPoint. Valid range is [-90, 90].
|
||||
/// Extremes should not be used.
|
||||
/// </summary>
|
||||
public double Latitude
|
||||
{
|
||||
get
|
||||
{
|
||||
return latitude;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value > 90 || value < -90)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("value",
|
||||
"Latitude must be within the range [-90, 90]");
|
||||
}
|
||||
latitude = value;
|
||||
}
|
||||
}
|
||||
|
||||
private double longitude;
|
||||
/// <summary>
|
||||
/// Gets or sets the longitude. Valid range is [-180, 180].
|
||||
/// Extremes should not be used.
|
||||
/// </summary>
|
||||
public double Longitude
|
||||
{
|
||||
get
|
||||
{
|
||||
return longitude;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value > 180 || value < -180)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("value",
|
||||
"Longitude must be within the range [-180, 180]");
|
||||
}
|
||||
longitude = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the distance in radians between this point and another GeoPoint. This is the smallest angular
|
||||
/// distance between the two points.
|
||||
/// </summary>
|
||||
/// <param name="point">GeoPoint describing the other point being measured against.</param>
|
||||
/// <returns>The distance in between the two points.</returns>
|
||||
public AVGeoDistance DistanceTo(AVGeoPoint point)
|
||||
{
|
||||
double d2r = Math.PI / 180; // radian conversion factor
|
||||
double lat1rad = Latitude * d2r;
|
||||
double long1rad = longitude * d2r;
|
||||
double lat2rad = point.Latitude * d2r;
|
||||
double long2rad = point.Longitude * d2r;
|
||||
double deltaLat = lat1rad - lat2rad;
|
||||
double deltaLong = long1rad - long2rad;
|
||||
double sinDeltaLatDiv2 = Math.Sin(deltaLat / 2);
|
||||
double sinDeltaLongDiv2 = Math.Sin(deltaLong / 2);
|
||||
// Square of half the straight line chord distance between both points.
|
||||
// [0.0, 1.0]
|
||||
double a = sinDeltaLatDiv2 * sinDeltaLatDiv2 +
|
||||
Math.Cos(lat1rad) * Math.Cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2;
|
||||
a = Math.Min(1.0, a);
|
||||
return new AVGeoDistance(2 * Math.Asin(Math.Sqrt(a)));
|
||||
}
|
||||
|
||||
IDictionary<string, object> IJsonConvertible.ToJSON()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "__type", "GeoPoint" },
|
||||
{ "latitude", Latitude },
|
||||
{ "longitude", Longitude }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue