Merge pull request #35 from onerain88/standard
Standard
commit
bc9fa45c1e
|
@ -2,6 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AppRouterState {
|
||||
namespace LeanCloud.Common {
|
||||
public class AppRouter {
|
||||
// 华东应用 App Id 后缀
|
||||
const string EAST_CHINA_SUFFIX = "-9Nh9j0Va";
|
||||
// 美国应用 App Id 后缀
|
||||
const string US_SUFFIX = "-MdYXbMMI";
|
||||
|
||||
[JsonProperty("ttl")]
|
||||
|
@ -36,63 +38,73 @@ namespace LeanCloud.Storage.Internal {
|
|||
get; internal set;
|
||||
}
|
||||
|
||||
[JsonProperty("play_server")]
|
||||
public string PlayServer {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
public string Source {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
public DateTime FetchedAt {
|
||||
public DateTimeOffset FetchedAt {
|
||||
get; internal set;
|
||||
}
|
||||
|
||||
public AppRouterState() {
|
||||
FetchedAt = DateTime.Now;
|
||||
public AppRouter() {
|
||||
FetchedAt = DateTimeOffset.Now;
|
||||
}
|
||||
|
||||
public bool IsExpired {
|
||||
get {
|
||||
return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL);
|
||||
if (TTL == -1) {
|
||||
return false;
|
||||
}
|
||||
return DateTimeOffset.Now > FetchedAt.AddSeconds(TTL);
|
||||
}
|
||||
}
|
||||
|
||||
public static AppRouterState GetFallbackServers(string appId) {
|
||||
public static AppRouter 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 {
|
||||
return new AppRouter {
|
||||
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",
|
||||
PlayServer = $"{prefix}.play.lncldapi.com",
|
||||
Source = "fallback",
|
||||
};
|
||||
case US_SUFFIX:
|
||||
// 美国
|
||||
return new AppRouterState {
|
||||
return new AppRouter {
|
||||
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",
|
||||
PlayServer = $"{prefix}.play.lncldglobal.com",
|
||||
Source = "fallback",
|
||||
};
|
||||
default:
|
||||
// 华北
|
||||
return new AppRouterState {
|
||||
return new AppRouter {
|
||||
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",
|
||||
PlayServer = $"{prefix}.play.lncld.net",
|
||||
Source = "fallback",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LeanCloud.Common {
|
||||
public class AppRouterController {
|
||||
readonly string appId;
|
||||
|
||||
AppRouter currentState;
|
||||
|
||||
readonly SemaphoreSlim locker = new SemaphoreSlim(1);
|
||||
|
||||
public AppRouterController(string appId, string server) {
|
||||
if (!IsInternationalApp(appId) && string.IsNullOrEmpty(server)) {
|
||||
// 国内 App 必须设置域名
|
||||
throw new ArgumentException("You must init with your domain.");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(server)) {
|
||||
currentState = new AppRouter {
|
||||
ApiServer = server,
|
||||
EngineServer = server,
|
||||
PushServer = server,
|
||||
RTMServer = server,
|
||||
StatsServer = server,
|
||||
PlayServer = server,
|
||||
TTL = -1
|
||||
};
|
||||
}
|
||||
this.appId = appId;
|
||||
}
|
||||
|
||||
public async Task<AppRouter> Get() {
|
||||
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();
|
||||
} catch (Exception) {
|
||||
currentState = AppRouter.GetFallbackServers(appId);
|
||||
}
|
||||
}
|
||||
return currentState;
|
||||
} finally {
|
||||
locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
async Task<AppRouter> QueryAsync() {
|
||||
HttpClient client = null;
|
||||
HttpRequestMessage request = null;
|
||||
HttpResponseMessage response = null;
|
||||
|
||||
try {
|
||||
string url = string.Format("https://app-router.com/2/route?appId={0}", appId);
|
||||
|
||||
client = new HttpClient();
|
||||
request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(url),
|
||||
Method = HttpMethod.Get
|
||||
};
|
||||
HttpUtils.PrintRequest(client, request);
|
||||
|
||||
response = await client.SendAsync(request);
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
HttpUtils.PrintResponse(response, content);
|
||||
|
||||
AppRouter state = JsonConvert.DeserializeObject<AppRouter>(content);
|
||||
state.Source = "router";
|
||||
|
||||
return state;
|
||||
} finally {
|
||||
if (client != null) {
|
||||
client.Dispose();
|
||||
}
|
||||
if (request != null) {
|
||||
request.Dispose();
|
||||
}
|
||||
if (response != null) {
|
||||
response.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
currentState = null;
|
||||
}
|
||||
|
||||
static bool IsInternationalApp(string appId) {
|
||||
if (appId.Length < 9) {
|
||||
return false;
|
||||
}
|
||||
string suffix = appId.Substring(appId.Length - 9);
|
||||
return suffix == "-MdYXbMMI";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,15 +3,15 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
<AssemblyName>LeanCloud.Common</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Class1.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\AppRouter\AppRouter\AppRouter.csproj" />
|
||||
<Folder Include="Log\" />
|
||||
<Folder Include="Http\" />
|
||||
<Folder Include="Task\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,49 @@
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace LeanCloud.Common {
|
||||
public static class HttpUtils {
|
||||
public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) {
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
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())}");
|
||||
}
|
||||
if (request.Content != null) {
|
||||
foreach (var header in request.Content.Headers) {
|
||||
sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}");
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(content)) {
|
||||
sb.AppendLine($"Content: {content}");
|
||||
}
|
||||
sb.AppendLine("=== HTTP Request End ===");
|
||||
Logger.Debug(sb.ToString());
|
||||
}
|
||||
|
||||
public static void PrintResponse(HttpResponseMessage response, string content = null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("=== HTTP Response Start ===");
|
||||
sb.AppendLine($"URL: {response.RequestMessage.RequestUri}");
|
||||
sb.AppendLine($"Status Code: {response.StatusCode}");
|
||||
if (!string.IsNullOrEmpty(content)) {
|
||||
sb.AppendLine($"Content: {content}");
|
||||
}
|
||||
sb.AppendLine("=== HTTP Response End ===");
|
||||
Logger.Debug(sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
namespace LeanCloud.Common {
|
||||
/// <summary>
|
||||
/// 为 Json 解析提供异步接口
|
||||
/// </summary>
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright (c) 2015-present, Parse, LLC. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. An additional grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Common {
|
||||
/// <summary>
|
||||
/// Provides helper methods that allow us to use terser code elsewhere.
|
||||
/// </summary>
|
||||
public static class TaskExtensions {
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <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) {
|
||||
if (self.TryGetValue(key, out TValue value)) {
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static bool CollectionsEqual<T>(this IEnumerable<T> a, IEnumerable<T> b) {
|
||||
return Equals(a, b) ||
|
||||
(a != null && b != null &&
|
||||
a.SequenceEqual(b));
|
||||
}
|
||||
|
||||
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<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 OnSuccess(this Task task, Action<Task> continuation) {
|
||||
return task.OnSuccess((Func<Task, object>)(t => {
|
||||
continuation(t);
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
// TaskScheduler
|
||||
public static Task<TResult> OnSuccess<TResult>(this Task task,
|
||||
Func<Task, TResult> continuation, TaskScheduler scheduler) {
|
||||
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));
|
||||
}
|
||||
}, scheduler).Unwrap();
|
||||
}
|
||||
|
||||
public static Task<TResult> OnSuccess<TIn, TResult>(this Task<TIn> task,
|
||||
Func<Task<TIn>, TResult> continuation, TaskScheduler scheduler) {
|
||||
return ((Task)task).OnSuccess(t => continuation((Task<TIn>)t), scheduler);
|
||||
}
|
||||
|
||||
public static Task OnSuccess<TIn>(this Task<TIn> task,
|
||||
Action<Task<TIn>> continuation, TaskScheduler scheduler) {
|
||||
return task.OnSuccess((Func<Task<TIn>, object>)(t => {
|
||||
continuation(t);
|
||||
return null;
|
||||
}), scheduler);
|
||||
}
|
||||
|
||||
public static Task OnSuccess(this Task task,
|
||||
Action<Task> continuation, TaskScheduler scheduler) {
|
||||
return task.OnSuccess((Func<Task, object>)(t => {
|
||||
continuation(t);
|
||||
return null;
|
||||
}), scheduler);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -1,53 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{EA1C601E-D853-41F7-B9EB-276CBF7D1FA5}</ProjectGuid>
|
||||
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>LiveQuery.PCL</RootNamespace>
|
||||
<AssemblyName>LiveQuery.PCL</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile>Profile111</TargetFrameworkProfile>
|
||||
<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>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="..\Source\AVLiveQuery.cs">
|
||||
<Link>Source\AVLiveQuery.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\AVLiveQueryEventArgs.cs">
|
||||
<Link>Source\AVLiveQueryEventArgs.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\AVLiveQueryExtensions.cs">
|
||||
<Link>Source\AVLiveQueryExtensions.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RTM\RTM.PCL\RTM.PCL.csproj">
|
||||
<Project>{92B2B40E-A3CD-4672-AC84-2E894E1A6CE5}</Project>
|
||||
<Name>RTM.PCL</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Storage\Storage.PCL\Storage.PCL.csproj">
|
||||
<Project>{659D19F0-9A40-42C0-886C-555E64F16848}</Project>
|
||||
<Name>Storage.PCL</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
</Project>
|
|
@ -1,26 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
|
||||
[assembly: AssemblyTitle("LiveQuery.PCL")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("${AuthorCopyright}")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
|
@ -1,41 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{F907012C-74DF-4575-AFE6-E8DAACC26D24}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>LiveQuery.Test</RootNamespace>
|
||||
<AssemblyName>LiveQuery.Test</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
<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.2.6.4\lib\nunit.framework.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Test.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -1,10 +0,0 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
namespace LiveQuery.Test {
|
||||
[TestFixture()]
|
||||
public class Test {
|
||||
[Test()]
|
||||
public void TestCase() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NUnit" version="2.6.4" targetFramework="net47" />
|
||||
</packages>
|
|
@ -1,56 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{3251B4D8-D11A-4D90-8626-27FEE266B066}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>LiveQuery.Unity</RootNamespace>
|
||||
<AssemblyName>LiveQuery.Unity</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
<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>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="..\Source\AVLiveQuery.cs">
|
||||
<Link>Source\AVLiveQuery.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\AVLiveQueryEventArgs.cs">
|
||||
<Link>Source\AVLiveQueryEventArgs.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\AVLiveQueryExtensions.cs">
|
||||
<Link>Source\AVLiveQueryExtensions.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RTM\RTM.Unity\RTM.Unity.csproj">
|
||||
<Project>{1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C}</Project>
|
||||
<Name>RTM.Unity</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Storage\Storage.Unity\Storage.Unity.csproj">
|
||||
<Project>{A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC}</Project>
|
||||
<Name>Storage.Unity</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -1,26 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
|
||||
[assembly: AssemblyTitle("LiveQuery.Unity")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("${AuthorCopyright}")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
|
@ -1,225 +0,0 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using LeanCloud.Realtime;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace LeanCloud.LiveQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// AVLiveQuery 类
|
||||
/// </summary>
|
||||
public static class AVLiveQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// LiveQuery 传输数据的 AVRealtime 实例
|
||||
/// </summary>
|
||||
public static AVRealtime Channel {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal static long ClientTs {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal static bool Inited {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal static string InstallationId {
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AVLiveQuery 对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class AVLiveQuery<T> where T : AVObject
|
||||
{
|
||||
internal static Dictionary<string, WeakReference<AVLiveQuery<T>>> liveQueryDict = new Dictionary<string, WeakReference<AVLiveQuery<T>>>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 当前 AVLiveQuery 对象的 Id
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 根据 AVQuery 创建 AVLiveQuery 对象
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
public AVLiveQuery(AVQuery<T> query) {
|
||||
this.Query = query;
|
||||
}
|
||||
/// <summary>
|
||||
/// AVLiveQuery 对应的 AVQuery 对象
|
||||
/// </summary>
|
||||
public AVQuery<T> Query { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据推送的触发的事件通知
|
||||
/// </summary>
|
||||
public event EventHandler<AVLiveQueryEventArgs<T>> OnLiveQueryReceived;
|
||||
|
||||
/// <summary>
|
||||
/// 推送抵达时触发事件通知
|
||||
/// </summary>
|
||||
/// <param name="scope">产生这条推送的原因。
|
||||
/// <remarks>
|
||||
/// create:符合查询条件的对象创建;
|
||||
/// update:符合查询条件的对象属性修改。
|
||||
/// enter:对象修改事件,从不符合查询条件变成符合。
|
||||
/// leave:对象修改时间,从符合查询条件变成不符合。
|
||||
/// delete:对象删除
|
||||
/// login:只对 _User 对象有效,表示用户登录。
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
/// <param name="onRecevived"></param>
|
||||
public void On(string scope, Action<T> onRecevived)
|
||||
{
|
||||
this.OnLiveQueryReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Scope == scope)
|
||||
{
|
||||
onRecevived.Invoke(e.Payload);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订阅操作
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<AVLiveQuery<T>> SubscribeAsync(CancellationToken cancellationToken = default(CancellationToken)) {
|
||||
if (Query == null) {
|
||||
throw new Exception("Query can not be null when subcribe.");
|
||||
}
|
||||
if (!AVLiveQuery.Inited) {
|
||||
await Login();
|
||||
AVLiveQuery.Channel.OnReconnected += OnChannelReconnected;
|
||||
AVLiveQuery.Channel.NoticeReceived += OnChannelNoticeReceived;
|
||||
AVLiveQuery.Inited = true;
|
||||
}
|
||||
await InternalSubscribe();
|
||||
var liveQueryRef = new WeakReference<AVLiveQuery<T>>(this);
|
||||
liveQueryDict.Add(Id, liveQueryRef);
|
||||
return this;
|
||||
}
|
||||
|
||||
static async void OnChannelReconnected(object sender, AVIMReconnectedEventArgs e) {
|
||||
await Login();
|
||||
lock (liveQueryDict) {
|
||||
foreach (var kv in liveQueryDict) {
|
||||
if (kv.Value.TryGetTarget(out var liveQuery)) {
|
||||
liveQuery.InternalSubscribe().ContinueWith(_ => { });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async Task Login() {
|
||||
var installation = await AVPlugins.Instance.InstallationIdController.GetAsync();
|
||||
AVLiveQuery.InstallationId = installation.ToString();
|
||||
AVLiveQuery.Channel.ToggleNotification(true);
|
||||
await AVLiveQuery.Channel.OpenAsync();
|
||||
AVLiveQuery.ClientTs = (long) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
var liveQueryLogInCmd = new AVIMCommand().Command("login")
|
||||
.Argument("installationId", AVLiveQuery.InstallationId)
|
||||
.Argument("clientTs", AVLiveQuery.ClientTs)
|
||||
.Argument("service", 1).AppId(AVClient.CurrentConfiguration.ApplicationId);
|
||||
// open the session for LiveQuery.
|
||||
try {
|
||||
await AVLiveQuery.Channel.AVIMCommandRunner.RunCommandAsync(liveQueryLogInCmd);
|
||||
} catch (Exception e) {
|
||||
AVRealtime.PrintLog(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnChannelNoticeReceived(object sender, AVIMNotice e) {
|
||||
if (e.CommandName == "data") {
|
||||
var ids = AVDecoder.Instance.DecodeList<string>(e.RawData["ids"]);
|
||||
if (e.RawData["msg"] is IEnumerable<object> msg) {
|
||||
var receivedPayloads = from item in msg
|
||||
select item as Dictionary<string, object>;
|
||||
if (receivedPayloads != null) {
|
||||
foreach (var payload in receivedPayloads) {
|
||||
var liveQueryId = payload["query_id"] as string;
|
||||
if (liveQueryDict.TryGetValue(liveQueryId, out var liveQueryRef) &&
|
||||
liveQueryRef.TryGetTarget(out var liveQuery)) {
|
||||
var scope = payload["op"] as string;
|
||||
var objectPayload = payload["object"] as Dictionary<string, object>;
|
||||
string[] keys = null;
|
||||
if (payload.TryGetValue("updatedKeys", out object updatedKeys)) {
|
||||
// enter, leave, update
|
||||
keys = (updatedKeys as List<object>).Select(x => x.ToString()).ToArray();
|
||||
}
|
||||
liveQuery.Emit(scope, objectPayload, keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task InternalSubscribe() {
|
||||
var queryMap = new Dictionary<string, object> {
|
||||
{ "where", Query.Condition},
|
||||
{ "className", Query.GetClassName()}
|
||||
};
|
||||
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "query", queryMap },
|
||||
{ "id", AVLiveQuery.InstallationId },
|
||||
{ "clientTimestamp", AVLiveQuery.ClientTs }
|
||||
};
|
||||
string sessionToken = AVUser.CurrentUser?.SessionToken;
|
||||
if (!string.IsNullOrEmpty(sessionToken)) {
|
||||
data.Add("sessionToken", sessionToken);
|
||||
}
|
||||
var command = new AVCommand("LiveQuery/subscribe",
|
||||
"POST",
|
||||
sessionToken,
|
||||
data: data);
|
||||
var res = await AVPlugins.Instance.CommandRunner.RunCommandAsync(command);
|
||||
Id = res.Item2["query_id"] as string;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消对当前 LiveQuery 对象的订阅
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task UnsubscribeAsync() {
|
||||
Dictionary<string, object> strs = new Dictionary<string, object> {
|
||||
{ "id", AVLiveQuery.InstallationId },
|
||||
{ "query_id", Id },
|
||||
};
|
||||
string sessionToken = AVUser.CurrentUser?.SessionToken;
|
||||
var command = new AVCommand("LiveQuery/unsubscribe",
|
||||
"POST",
|
||||
sessionToken,
|
||||
data: strs);
|
||||
await AVPlugins.Instance.CommandRunner.RunCommandAsync(command);
|
||||
lock (liveQueryDict) {
|
||||
liveQueryDict.Remove(Id);
|
||||
}
|
||||
}
|
||||
|
||||
void Emit(string scope, IDictionary<string, object> payloadMap, string[] keys) {
|
||||
var objectState = AVObjectCoder.Instance.Decode(payloadMap, AVDecoder.Instance);
|
||||
var payloadObject = AVObject.FromState<T>(objectState, Query.GetClassName<T>());
|
||||
var args = new AVLiveQueryEventArgs<T> {
|
||||
Scope = scope,
|
||||
Keys = keys,
|
||||
Payload = payloadObject
|
||||
};
|
||||
OnLiveQueryReceived?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud;
|
||||
using System.Collections;
|
||||
|
||||
namespace LeanCloud.LiveQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// AVLiveQuery 回调参数
|
||||
/// </summary>
|
||||
public class AVLiveQueryEventArgs<T> : EventArgs
|
||||
where T : AVObject
|
||||
{
|
||||
internal AVLiveQueryEventArgs()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新事件
|
||||
/// </summary>
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新字段
|
||||
/// </summary>
|
||||
public IEnumerable<string> Keys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新数据
|
||||
/// </summary>
|
||||
public T Payload { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.LiveQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// AVLiveQuery 扩展类
|
||||
/// </summary>
|
||||
public static class AVLiveQueryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// AVQuery 订阅 AVLiveQuery 的扩展方法
|
||||
/// </summary>
|
||||
/// <returns>AVLiveQuery 对象</returns>
|
||||
/// <param name="query">Query.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <typeparam name="T">The 1st type parameter.</typeparam>
|
||||
public static async Task<AVLiveQuery<T>> SubscribeAsync<T>(this AVQuery<T> query, CancellationToken cancellationToken = default(CancellationToken)) where T : AVObject {
|
||||
var liveQuery = new AVLiveQuery<T>(query);
|
||||
liveQuery = await liveQuery.SubscribeAsync();
|
||||
return liveQuery;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -206,11 +206,5 @@
|
|||
<HintPath>..\..\packages\Websockets.Pcl.1.1.9\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10\WebSockets.PCL.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Storage\Storage.PCL\Storage.PCL.csproj">
|
||||
<Project>{659D19F0-9A40-42C0-886C-555E64F16848}</Project>
|
||||
<Name>Storage.PCL</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
</Project>
|
|
@ -215,11 +215,5 @@
|
|||
<Link>Internal\WebSocket\Unity\websocket-sharp.dll</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Storage\Storage.Unity\Storage.Unity.csproj">
|
||||
<Project>{A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC}</Project>
|
||||
<Name>Storage.Unity</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Object;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Codec {
|
||||
internal static class LCDecoder {
|
||||
internal static object Decode(object obj) {
|
||||
if (obj is IDictionary dict) {
|
||||
if (dict.Contains("__type")) {
|
||||
string type = dict["__type"].ToString();
|
||||
if (type == "Date") {
|
||||
return DecodeDate(dict);
|
||||
} else if (type == "Bytes") {
|
||||
return DecodeBytes(dict);
|
||||
} else if (type == "Object") {
|
||||
return DecodeObject(dict);
|
||||
} else if (type == "Pointer") {
|
||||
return DecodeObject(dict);
|
||||
} else if (type == "Relation") {
|
||||
return DecodeRelation(dict);
|
||||
} else if (type == "GeoPoint") {
|
||||
return DecodeGeoPoint(dict);
|
||||
}
|
||||
}
|
||||
Dictionary<string, object> d = new Dictionary<string, object>();
|
||||
foreach (DictionaryEntry kv in dict) {
|
||||
string key = kv.Key.ToString();
|
||||
object value = kv.Value;
|
||||
d[key] = Decode(value);
|
||||
}
|
||||
return d;
|
||||
} else if (obj is IList list) {
|
||||
List<object> l = new List<object>();
|
||||
foreach (object o in list) {
|
||||
object v = Decode(o);
|
||||
l.Add(v);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
static DateTime DecodeDate(IDictionary dict) {
|
||||
string str = dict["iso"].ToString();
|
||||
DateTime dateTime = DateTime.Parse(str);
|
||||
return dateTime.ToLocalTime();
|
||||
}
|
||||
|
||||
static byte[] DecodeBytes(IDictionary dict) {
|
||||
string str = dict["base64"].ToString();
|
||||
byte[] bytes = Convert.FromBase64String(str);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static LCObject DecodeObject(IDictionary dict) {
|
||||
string className = dict["className"].ToString();
|
||||
LCObject obj = LCObject.Create(className);
|
||||
LCObjectData objectData = LCObjectData.Decode(dict as Dictionary<string, object>);
|
||||
obj.Merge(objectData);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static LCRelation<LCObject> DecodeRelation(IDictionary dict) {
|
||||
LCRelation<LCObject> relation = new LCRelation<LCObject>();
|
||||
relation.TargetClass = dict["className"].ToString();
|
||||
return relation;
|
||||
}
|
||||
|
||||
static LCGeoPoint DecodeGeoPoint(IDictionary data) {
|
||||
double latitude = double.Parse(data["latitude"].ToString());
|
||||
double longitude = double.Parse(data["longitude"].ToString());
|
||||
LCGeoPoint geoPoint = new LCGeoPoint(latitude, longitude);
|
||||
return geoPoint;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Operation;
|
||||
using LeanCloud.Storage.Internal.Query;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Codec {
|
||||
internal static class LCEncoder {
|
||||
internal static object Encode(object obj) {
|
||||
if (obj is DateTime dateTime) {
|
||||
return EncodeDateTime(dateTime);
|
||||
} else if (obj is byte[] bytes) {
|
||||
return EncodeBytes(bytes);
|
||||
} else if (obj is IList list) {
|
||||
return EncodeList(list);
|
||||
} else if (obj is IDictionary dict) {
|
||||
return EncodeDictionary(dict);
|
||||
} else if (obj is LCObject lcObj) {
|
||||
return EncodeLCObject(lcObj);
|
||||
} else if (obj is ILCOperation op) {
|
||||
return EncodeOperation(op);
|
||||
} else if (obj is ILCQueryCondition cond) {
|
||||
return EncodeQueryCondition(cond);
|
||||
} else if (obj is LCACL acl) {
|
||||
return EncodeACL(acl);
|
||||
} else if (obj is LCRelation<LCObject> relation) {
|
||||
return EncodeRelation(relation);
|
||||
} else if (obj is LCGeoPoint geoPoint) {
|
||||
return EncodeGeoPoint(geoPoint);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
static object EncodeDateTime(DateTime dateTime) {
|
||||
DateTime utc = dateTime.ToUniversalTime();
|
||||
string str = utc.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
|
||||
return new Dictionary<string, object> {
|
||||
{ "__type", "Date" },
|
||||
{ "iso", str }
|
||||
};
|
||||
}
|
||||
|
||||
static object EncodeBytes(byte[] bytes) {
|
||||
string str = Convert.ToBase64String(bytes);
|
||||
return new Dictionary<string, object> {
|
||||
{ "__type", "Bytes" },
|
||||
{ "base64", str }
|
||||
};
|
||||
}
|
||||
|
||||
static object EncodeList(IList list) {
|
||||
List<object> l = new List<object>();
|
||||
foreach (object obj in list) {
|
||||
l.Add(Encode(obj));
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
static object EncodeDictionary(IDictionary dict) {
|
||||
Dictionary<string, object> d = new Dictionary<string, object>();
|
||||
foreach (DictionaryEntry entry in dict) {
|
||||
string key = entry.Key.ToString();
|
||||
object value = entry.Value;
|
||||
d[key] = Encode(value);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static object EncodeLCObject(LCObject obj) {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__type", "Pointer" },
|
||||
{ "className", obj.ClassName },
|
||||
{ "objectId", obj.ObjectId }
|
||||
};
|
||||
}
|
||||
|
||||
static object EncodeOperation(ILCOperation operation) {
|
||||
return operation.Encode();
|
||||
}
|
||||
|
||||
static object EncodeQueryCondition(ILCQueryCondition cond) {
|
||||
return cond.Encode();
|
||||
}
|
||||
|
||||
static object EncodeACL(LCACL acl) {
|
||||
HashSet<string> readers = acl.readers;
|
||||
HashSet<string> writers = acl.writers;
|
||||
HashSet<string> union = new HashSet<string>(readers);
|
||||
union.UnionWith(writers);
|
||||
Dictionary<string, object> dict = new Dictionary<string, object>();
|
||||
foreach (string k in union) {
|
||||
dict[k] = new Dictionary<string, object> {
|
||||
{ "read", readers.Contains(k) },
|
||||
{ "write", writers.Contains(k) }
|
||||
};
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
static object EncodeRelation(LCRelation<LCObject> relation) {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__type", "Relation" },
|
||||
{ "className", relation.TargetClass }
|
||||
};
|
||||
}
|
||||
|
||||
static object EncodeGeoPoint(LCGeoPoint geoPoint) {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__type", "GeoPoint" },
|
||||
{ "latitude", geoPoint.Latitude },
|
||||
{ "longitude", geoPoint.Longitude }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Common;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.File {
|
||||
internal class LCAWSUploader {
|
||||
string uploadUrl;
|
||||
|
||||
string mimeType;
|
||||
|
||||
byte[] data;
|
||||
|
||||
internal LCAWSUploader(string uploadUrl, string mimeType, byte[] data) {
|
||||
this.uploadUrl = uploadUrl;
|
||||
this.mimeType = mimeType;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
internal async Task Upload(Action<long, long> onProgress) {
|
||||
LCProgressableStreamContent content = new LCProgressableStreamContent(new ByteArrayContent(data), onProgress);
|
||||
|
||||
HttpRequestMessage request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(uploadUrl),
|
||||
Method = HttpMethod.Put,
|
||||
Content = content
|
||||
};
|
||||
HttpClient client = new HttpClient();
|
||||
request.Headers.CacheControl = new CacheControlHeaderValue {
|
||||
Public = true,
|
||||
MaxAge = TimeSpan.FromMilliseconds(31536000)
|
||||
};
|
||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
|
||||
HttpUtils.PrintRequest(client, request);
|
||||
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
request.Dispose();
|
||||
|
||||
string resultString = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
HttpUtils.PrintResponse(response, resultString);
|
||||
|
||||
HttpStatusCode statusCode = response.StatusCode;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.File {
|
||||
internal static class LCMimeTypeMap {
|
||||
static readonly 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" }
|
||||
};
|
||||
|
||||
internal static string GetMimeType(string fileName) {
|
||||
try {
|
||||
string suffix = Path.GetExtension(fileName).Substring(1);
|
||||
if (MIMETypesDictionary.TryGetValue(suffix, out string type)) {
|
||||
return type;
|
||||
}
|
||||
return "unknown/unknown";
|
||||
} catch {
|
||||
return "unknown/unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.File {
|
||||
internal class LCProgressableStreamContent : HttpContent {
|
||||
const int defaultBufferSize = 5 * 4096;
|
||||
|
||||
readonly HttpContent content;
|
||||
|
||||
readonly int bufferSize;
|
||||
|
||||
readonly Action<long, long> progress;
|
||||
|
||||
internal LCProgressableStreamContent(HttpContent content, Action<long, long> progress) : this(content, defaultBufferSize, progress) { }
|
||||
|
||||
internal LCProgressableStreamContent(HttpContent content, int bufferSize, Action<long, long> progress) {
|
||||
if (content == null) {
|
||||
throw new ArgumentNullException("content");
|
||||
}
|
||||
if (bufferSize <= 0) {
|
||||
throw new ArgumentOutOfRangeException("bufferSize");
|
||||
}
|
||||
|
||||
this.content = content;
|
||||
this.bufferSize = bufferSize;
|
||||
this.progress = progress;
|
||||
|
||||
foreach (var h in content.Headers) {
|
||||
Headers.Add(h.Key, h.Value);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) {
|
||||
|
||||
return Task.Run(async () => {
|
||||
var buffer = new byte[bufferSize];
|
||||
TryComputeLength(out long size);
|
||||
var uploaded = 0;
|
||||
|
||||
using (var sinput = await content.ReadAsStreamAsync()) {
|
||||
while (true) {
|
||||
var length = sinput.Read(buffer, 0, buffer.Length);
|
||||
if (length <= 0) break;
|
||||
|
||||
uploaded += length;
|
||||
progress?.Invoke(uploaded, size);
|
||||
|
||||
stream.Write(buffer, 0, length);
|
||||
stream.Flush();
|
||||
}
|
||||
}
|
||||
stream.Flush();
|
||||
});
|
||||
}
|
||||
|
||||
protected override bool TryComputeLength(out long length) {
|
||||
length = content.Headers.ContentLength.GetValueOrDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
content.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using LeanCloud.Common;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.File {
|
||||
internal class LCQiniuUploader {
|
||||
string uploadUrl;
|
||||
|
||||
string token;
|
||||
|
||||
string key;
|
||||
|
||||
byte[] data;
|
||||
|
||||
internal LCQiniuUploader(string uploadUrl, string token, string key, byte[] data) {
|
||||
this.uploadUrl = uploadUrl;
|
||||
this.token = token;
|
||||
this.key = key;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
internal async Task Upload(Action<long, long> onProgress) {
|
||||
MultipartFormDataContent dataContent = new MultipartFormDataContent();
|
||||
dataContent.Add(new StringContent(key), "key");
|
||||
dataContent.Add(new StringContent(token), "token");
|
||||
dataContent.Add(new ByteArrayContent(data), "file");
|
||||
|
||||
LCProgressableStreamContent content = new LCProgressableStreamContent(dataContent, onProgress);
|
||||
|
||||
HttpRequestMessage request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(uploadUrl),
|
||||
Method = HttpMethod.Post,
|
||||
Content = content
|
||||
};
|
||||
HttpClient client = new HttpClient();
|
||||
HttpUtils.PrintRequest(client, request);
|
||||
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
request.Dispose();
|
||||
|
||||
string resultString = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
HttpUtils.PrintResponse(response, resultString);
|
||||
|
||||
HttpStatusCode statusCode = response.StatusCode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
using Newtonsoft.Json;
|
||||
using LeanCloud.Common;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Http {
|
||||
internal class LCHttpClient {
|
||||
readonly string appId;
|
||||
|
||||
readonly string appKey;
|
||||
|
||||
readonly string server;
|
||||
|
||||
readonly string sdkVersion;
|
||||
|
||||
readonly string apiVersion;
|
||||
|
||||
HttpClient client;
|
||||
|
||||
MD5 md5;
|
||||
|
||||
internal LCHttpClient(string appId, string appKey, string server, string sdkVersion, string apiVersion) {
|
||||
this.appId = appId;
|
||||
this.appKey = appKey;
|
||||
this.server = server;
|
||||
this.sdkVersion = sdkVersion;
|
||||
this.apiVersion = apiVersion;
|
||||
|
||||
client = new HttpClient();
|
||||
ProductHeaderValue product = new ProductHeaderValue("LeanCloud-CSharp-SDK", LeanCloud.SDKVersion);
|
||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(product));
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
client.DefaultRequestHeaders.Add("X-LC-Id", appId);
|
||||
|
||||
md5 = MD5.Create();
|
||||
}
|
||||
|
||||
internal async Task<T> Get<T>(string path,
|
||||
Dictionary<string, object> headers = null,
|
||||
Dictionary<string, object> queryParams = null) {
|
||||
string url = BuildUrl(path, queryParams);
|
||||
HttpRequestMessage request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(url),
|
||||
Method = HttpMethod.Get
|
||||
};
|
||||
await FillHeaders(request.Headers, headers);
|
||||
|
||||
HttpUtils.PrintRequest(client, request);
|
||||
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
request.Dispose();
|
||||
|
||||
string resultString = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
HttpUtils.PrintResponse(response, resultString);
|
||||
|
||||
if (response.IsSuccessStatusCode) {
|
||||
T ret = JsonConvert.DeserializeObject<T>(resultString, new LeanCloudJsonConverter());
|
||||
return ret;
|
||||
}
|
||||
throw HandleErrorResponse(response.StatusCode, resultString);
|
||||
}
|
||||
|
||||
internal async Task<T> Post<T>(string path,
|
||||
Dictionary<string, object> headers = null,
|
||||
Dictionary<string, object> data = null,
|
||||
Dictionary<string, object> queryParams = null) {
|
||||
string url = BuildUrl(path, queryParams);
|
||||
HttpRequestMessage request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(url),
|
||||
Method = HttpMethod.Post,
|
||||
};
|
||||
await FillHeaders(request.Headers, headers);
|
||||
|
||||
string content = null;
|
||||
if (data != null) {
|
||||
content = JsonConvert.SerializeObject(data);
|
||||
StringContent requestContent = new StringContent(content);
|
||||
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||
request.Content = requestContent;
|
||||
}
|
||||
HttpUtils.PrintRequest(client, request, content);
|
||||
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
request.Dispose();
|
||||
|
||||
string resultString = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
HttpUtils.PrintResponse(response, resultString);
|
||||
|
||||
if (response.IsSuccessStatusCode) {
|
||||
T ret = JsonConvert.DeserializeObject<T>(resultString, new LeanCloudJsonConverter());
|
||||
return ret;
|
||||
}
|
||||
throw HandleErrorResponse(response.StatusCode, resultString);
|
||||
}
|
||||
|
||||
internal async Task<T> Put<T>(string path,
|
||||
Dictionary<string, object> headers = null,
|
||||
Dictionary<string, object> data = null,
|
||||
Dictionary<string, object> queryParams = null) {
|
||||
string url = BuildUrl(path, queryParams);
|
||||
HttpRequestMessage request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(url),
|
||||
Method = HttpMethod.Put,
|
||||
};
|
||||
await FillHeaders(request.Headers, headers);
|
||||
|
||||
string content = null;
|
||||
if (data != null) {
|
||||
content = JsonConvert.SerializeObject(data);
|
||||
StringContent requestContent = new StringContent(content);
|
||||
requestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||
request.Content = requestContent;
|
||||
}
|
||||
HttpUtils.PrintRequest(client, request, content);
|
||||
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
request.Dispose();
|
||||
|
||||
string resultString = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
HttpUtils.PrintResponse(response, resultString);
|
||||
|
||||
if (response.IsSuccessStatusCode) {
|
||||
T ret = JsonConvert.DeserializeObject<T>(resultString, new LeanCloudJsonConverter());
|
||||
return ret;
|
||||
}
|
||||
throw HandleErrorResponse(response.StatusCode, resultString);
|
||||
}
|
||||
|
||||
internal async Task Delete(string path) {
|
||||
string url = BuildUrl(path);
|
||||
HttpRequestMessage request = new HttpRequestMessage {
|
||||
RequestUri = new Uri(url),
|
||||
Method = HttpMethod.Delete
|
||||
};
|
||||
await FillHeaders(request.Headers);
|
||||
|
||||
HttpUtils.PrintRequest(client, request);
|
||||
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
request.Dispose();
|
||||
|
||||
string resultString = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
HttpUtils.PrintResponse(response, resultString);
|
||||
|
||||
if (response.IsSuccessStatusCode) {
|
||||
Dictionary<string, object> ret = JsonConvert.DeserializeObject<Dictionary<string, object>>(resultString, new LeanCloudJsonConverter());
|
||||
return;
|
||||
}
|
||||
throw HandleErrorResponse(response.StatusCode, resultString);
|
||||
}
|
||||
|
||||
LCException HandleErrorResponse(HttpStatusCode statusCode, string responseContent) {
|
||||
int code = (int)statusCode;
|
||||
string message = responseContent;
|
||||
try {
|
||||
// 尝试获取 LeanCloud 返回错误信息
|
||||
Dictionary<string, object> error = JsonConvert.DeserializeObject<Dictionary<string, object>>(responseContent, new LeanCloudJsonConverter());
|
||||
code = (int)error["code"];
|
||||
message = error["error"].ToString();
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e.Message);
|
||||
}
|
||||
return new LCException(code, message);
|
||||
}
|
||||
|
||||
string BuildUrl(string path, Dictionary<string, object> queryParams = null) {
|
||||
string url = $"{server}/{apiVersion}/{path}";
|
||||
if (queryParams != null) {
|
||||
IEnumerable<string> queryPairs = queryParams.Select(kv => $"{kv.Key}={kv.Value}");
|
||||
string queries = string.Join("&", queryPairs);
|
||||
url = $"{url}?{queries}";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
async Task FillHeaders(HttpRequestHeaders headers, Dictionary<string, object> additionalHeaders = null) {
|
||||
// 额外 headers
|
||||
if (additionalHeaders != null) {
|
||||
foreach (KeyValuePair<string, object> kv in additionalHeaders) {
|
||||
headers.Add(kv.Key, kv.Value.ToString());
|
||||
}
|
||||
}
|
||||
// 签名
|
||||
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
string data = $"{timestamp}{appKey}";
|
||||
string hash = GetMd5Hash(md5, data);
|
||||
string sign = $"{hash},{timestamp}";
|
||||
headers.Add("X-LC-Sign", sign);
|
||||
// 当前用户 Session Token
|
||||
LCUser currentUser = await LCUser.GetCurrent();
|
||||
if (currentUser != null) {
|
||||
headers.Add("X-LC-Session", currentUser.SessionToken);
|
||||
}
|
||||
}
|
||||
|
||||
static string GetMd5Hash(MD5 md5Hash, string input) {
|
||||
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < data.Length; i++) {
|
||||
sb.Append(data[i].ToString("x2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,11 @@ namespace LeanCloud.Storage.Internal {
|
|||
serializer.Populate(reader, arr);
|
||||
return arr;
|
||||
}
|
||||
if (reader.TokenType == JsonToken.Integer) {
|
||||
if ((long)reader.Value < int.MaxValue) {
|
||||
return Convert.ToInt32(reader.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return serializer.Deserialize(reader);
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Object {
|
||||
internal class LCBatch {
|
||||
internal HashSet<LCObject> objects;
|
||||
|
||||
internal LCBatch(IEnumerable<LCObject> objs) {
|
||||
if (objs == null) {
|
||||
objects = new HashSet<LCObject>();
|
||||
} else {
|
||||
objects = new HashSet<LCObject>(objs);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool HasCircleReference(object obj, HashSet<LCObject> parents) {
|
||||
if (obj is LCObject lcObj && parents.Contains(lcObj)) {
|
||||
return true;
|
||||
}
|
||||
IEnumerable deps = null;
|
||||
if (obj is IList list) {
|
||||
deps = list;
|
||||
} else if (obj is IDictionary dict) {
|
||||
deps = dict.Values;
|
||||
} else if (obj is LCObject lcObject) {
|
||||
deps = lcObject.estimatedData.Values;
|
||||
}
|
||||
HashSet<LCObject> depParents = new HashSet<LCObject>(parents);
|
||||
if (obj is LCObject) {
|
||||
depParents.Add(obj as LCObject);
|
||||
}
|
||||
if (deps != null) {
|
||||
foreach (object dep in deps) {
|
||||
HashSet<LCObject> ps = new HashSet<LCObject>(depParents);
|
||||
if (HasCircleReference(dep, ps)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static Stack<LCBatch> BatchObjects(IEnumerable<LCObject> objects, bool containSelf) {
|
||||
Stack<LCBatch> batches = new Stack<LCBatch>();
|
||||
if (containSelf) {
|
||||
batches.Push(new LCBatch(objects));
|
||||
}
|
||||
HashSet<object> deps = new HashSet<object>();
|
||||
foreach (LCObject obj in objects) {
|
||||
deps.UnionWith(obj.operationDict.Values.Select(op => op.GetNewObjectList()));
|
||||
}
|
||||
do {
|
||||
HashSet<object> childSet = new HashSet<object>();
|
||||
foreach (object dep in deps) {
|
||||
IEnumerable children = null;
|
||||
if (dep is IList list) {
|
||||
children = list;
|
||||
} else if (dep is IDictionary dict) {
|
||||
children = dict;
|
||||
} else if (dep is LCObject lcDep && lcDep.ObjectId == null) {
|
||||
children = lcDep.operationDict.Values.Select(op => op.GetNewObjectList());
|
||||
}
|
||||
if (children != null) {
|
||||
childSet.UnionWith(children.Cast<object>());
|
||||
}
|
||||
}
|
||||
IEnumerable<LCObject> depObjs = deps.Where(item => item is LCObject lcItem && lcItem.ObjectId == null)
|
||||
.Cast<LCObject>();
|
||||
if (depObjs != null && depObjs.Count() > 0) {
|
||||
batches.Push(new LCBatch(depObjs));
|
||||
}
|
||||
deps = childSet;
|
||||
} while (deps != null && deps.Count > 0);
|
||||
return batches;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Object {
|
||||
internal class LCObjectData {
|
||||
internal string ClassName {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal string ObjectId {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal DateTime CreatedAt {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal DateTime UpdatedAt {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal Dictionary<string, object> CustomPropertyDict;
|
||||
|
||||
internal LCObjectData() {
|
||||
CustomPropertyDict = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
internal static LCObjectData Decode(IDictionary dict) {
|
||||
if (dict == null) {
|
||||
return null;
|
||||
}
|
||||
LCObjectData objectData = new LCObjectData();
|
||||
foreach (DictionaryEntry kv in dict) {
|
||||
string key = kv.Key.ToString();
|
||||
object value = kv.Value;
|
||||
if (key == "className") {
|
||||
objectData.ClassName = value.ToString();
|
||||
} else if (key == "objectId") {
|
||||
objectData.ObjectId = value.ToString();
|
||||
} else if (key == "createdAt" && DateTime.TryParse(value.ToString(), out DateTime createdAt)) {
|
||||
objectData.CreatedAt = createdAt;
|
||||
} else if (key == "updatedAt" && DateTime.TryParse(value.ToString(), out DateTime updatedAt)) {
|
||||
objectData.UpdatedAt = updatedAt;
|
||||
} else {
|
||||
objectData.CustomPropertyDict[key] = LCDecoder.Decode(value);
|
||||
}
|
||||
}
|
||||
return objectData;
|
||||
}
|
||||
|
||||
internal static Dictionary<string, object> Encode(LCObjectData objectData) {
|
||||
if (objectData == null) {
|
||||
return null;
|
||||
}
|
||||
Dictionary<string, object> dict = new Dictionary<string, object> {
|
||||
{ "className", objectData.ClassName },
|
||||
{ "objectId", objectData.ObjectId },
|
||||
{ "createdAt", objectData.CreatedAt },
|
||||
{ "updatedAt", objectData.UpdatedAt },
|
||||
};
|
||||
if (objectData.CustomPropertyDict != null) {
|
||||
foreach (KeyValuePair<string, object> kv in objectData.CustomPropertyDict) {
|
||||
string key = kv.Key;
|
||||
object value = kv.Value;
|
||||
dict[key] = LCEncoder.Encode(value);
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Object {
|
||||
internal class LCSubclassInfo {
|
||||
internal string ClassName {
|
||||
get;
|
||||
}
|
||||
|
||||
internal Type Type {
|
||||
get;
|
||||
}
|
||||
|
||||
internal Func<LCObject> Constructor {
|
||||
get;
|
||||
}
|
||||
|
||||
internal LCSubclassInfo(string className, Type type, Func<LCObject> constructor) {
|
||||
ClassName = className;
|
||||
Type = type;
|
||||
Constructor = constructor;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Operation {
|
||||
internal interface ILCOperation {
|
||||
ILCOperation MergeWithPrevious(ILCOperation previousOp);
|
||||
|
||||
object Encode();
|
||||
|
||||
object Apply(object oldValue, string key);
|
||||
|
||||
IEnumerable GetNewObjectList();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Operation {
|
||||
internal class LCAddOperation : ILCOperation {
|
||||
internal List<object> valueList;
|
||||
|
||||
internal LCAddOperation(IEnumerable<object> values) {
|
||||
valueList = new List<object>(values);
|
||||
}
|
||||
|
||||
ILCOperation ILCOperation.MergeWithPrevious(ILCOperation previousOp) {
|
||||
if (previousOp is LCSetOperation || previousOp is LCDeleteOperation) {
|
||||
return previousOp;
|
||||
}
|
||||
if (previousOp is LCAddOperation addOp) {
|
||||
valueList.AddRange(addOp.valueList);
|
||||
return this;
|
||||
}
|
||||
if (previousOp is LCAddUniqueOperation addUniqueOp) {
|
||||
valueList.AddRange(addUniqueOp.values);
|
||||
return this;
|
||||
}
|
||||
throw new ArgumentException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
object ILCOperation.Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__op", "Add" },
|
||||
{ "objects", LCEncoder.Encode(valueList) }
|
||||
};
|
||||
}
|
||||
|
||||
object ILCOperation.Apply(object oldValue, string key) {
|
||||
List<object> list = new List<object>();
|
||||
if (oldValue != null) {
|
||||
list.AddRange(oldValue as IEnumerable<object>);
|
||||
}
|
||||
list.AddRange(valueList);
|
||||
return list;
|
||||
}
|
||||
|
||||
IEnumerable ILCOperation.GetNewObjectList() {
|
||||
return valueList;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Operation {
|
||||
internal class LCAddRelationOperation : ILCOperation {
|
||||
List<LCObject> valueList;
|
||||
|
||||
internal LCAddRelationOperation(IEnumerable<LCObject> objects) {
|
||||
valueList = new List<LCObject>(objects);
|
||||
}
|
||||
|
||||
public ILCOperation MergeWithPrevious(ILCOperation previousOp) {
|
||||
if (previousOp is LCSetOperation || previousOp is LCDeleteOperation) {
|
||||
return previousOp;
|
||||
}
|
||||
if (previousOp is LCAddRelationOperation addRelationOp) {
|
||||
valueList.AddRange(addRelationOp.valueList);
|
||||
return this;
|
||||
}
|
||||
throw new ArgumentException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__op", "AddRelation" },
|
||||
{ "objects", LCEncoder.Encode(valueList) }
|
||||
};
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
LCRelation<LCObject> relation = new LCRelation<LCObject>();
|
||||
relation.TargetClass = valueList[0].ClassName;
|
||||
return relation;
|
||||
}
|
||||
|
||||
public IEnumerable GetNewObjectList() {
|
||||
return valueList;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Operation {
|
||||
internal class LCAddUniqueOperation : ILCOperation {
|
||||
internal HashSet<object> values;
|
||||
|
||||
internal LCAddUniqueOperation(IEnumerable<object> values) {
|
||||
this.values = new HashSet<object>(values);
|
||||
}
|
||||
|
||||
public ILCOperation MergeWithPrevious(ILCOperation previousOp) {
|
||||
if (previousOp is LCSetOperation || previousOp is LCDeleteOperation) {
|
||||
return previousOp;
|
||||
}
|
||||
if (previousOp is LCAddUniqueOperation addUniqueOp) {
|
||||
values.UnionWith(addUniqueOp.values);
|
||||
return this;
|
||||
}
|
||||
throw new ArgumentException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__op", "AddUnique" },
|
||||
{ "objects", LCEncoder.Encode(values.ToList()) }
|
||||
};
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
HashSet<object> set = new HashSet<object>();
|
||||
if (oldValue != null) {
|
||||
set.UnionWith(oldValue as IEnumerable<object>);
|
||||
}
|
||||
set.UnionWith(values);
|
||||
return set.ToList();
|
||||
}
|
||||
|
||||
public IEnumerable GetNewObjectList() {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Operation {
|
||||
internal class LCDeleteOperation : ILCOperation {
|
||||
internal LCDeleteOperation() {
|
||||
}
|
||||
|
||||
public ILCOperation MergeWithPrevious(ILCOperation previousOp) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__op", "Delete" }
|
||||
};
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable GetNewObjectList() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Operation {
|
||||
internal class LCNumberOperation : ILCOperation {
|
||||
static readonly IDictionary<Tuple<Type, Type>, Func<object, object, object>> adders;
|
||||
|
||||
static LCNumberOperation() {
|
||||
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}
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
protected object value;
|
||||
|
||||
internal LCNumberOperation(object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ILCOperation MergeWithPrevious(ILCOperation previousOp) {
|
||||
if (previousOp is LCSetOperation || previousOp is LCDeleteOperation) {
|
||||
return previousOp;
|
||||
}
|
||||
if (previousOp is LCNumberOperation incrementOp) {
|
||||
object otherAmount = incrementOp.value;
|
||||
return new LCNumberOperation(Add(otherAmount, value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__op", "Increment" },
|
||||
{ "amount", value }
|
||||
};
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
oldValue = oldValue ?? 0;
|
||||
return Add(oldValue, value);
|
||||
}
|
||||
|
||||
public IEnumerable GetNewObjectList() {
|
||||
return null;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Operation {
|
||||
internal class LCRemoveOperation : ILCOperation {
|
||||
List<object> valueList;
|
||||
|
||||
internal LCRemoveOperation(IEnumerable<object> values) {
|
||||
valueList = new List<object>(values);
|
||||
}
|
||||
|
||||
public ILCOperation MergeWithPrevious(ILCOperation previousOp) {
|
||||
if (previousOp is LCSetOperation || previousOp is LCDeleteOperation) {
|
||||
return previousOp;
|
||||
}
|
||||
if (previousOp is LCRemoveOperation removeOp) {
|
||||
valueList.AddRange(removeOp.valueList);
|
||||
}
|
||||
throw new ArgumentException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__op", "Remove" },
|
||||
{ "objects", LCEncoder.Encode(valueList) }
|
||||
};
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
List<object> list = new List<object>(oldValue as IEnumerable<object>);
|
||||
list.RemoveAll(item => valueList.Contains(item));
|
||||
return list;
|
||||
}
|
||||
|
||||
public IEnumerable GetNewObjectList() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Operation {
|
||||
internal class LCRemoveRelationOperation : ILCOperation {
|
||||
List<LCObject> valueList;
|
||||
|
||||
internal LCRemoveRelationOperation(LCObject obj) {
|
||||
valueList = new List<LCObject> { obj };
|
||||
}
|
||||
|
||||
public ILCOperation MergeWithPrevious(ILCOperation previousOp) {
|
||||
if (previousOp is LCSetOperation || previousOp is LCDeleteOperation) {
|
||||
return previousOp;
|
||||
}
|
||||
if (previousOp is LCRemoveRelationOperation removeRelationOp) {
|
||||
valueList.AddRange(removeRelationOp.valueList);
|
||||
return this;
|
||||
}
|
||||
throw new ArgumentException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{ "__op", "RemoveRelation" },
|
||||
{ "objects", LCEncoder.Encode(valueList) }
|
||||
};
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
LCRelation<LCObject> relation = new LCRelation<LCObject>();
|
||||
relation.TargetClass = valueList[0].ClassName;
|
||||
return relation;
|
||||
}
|
||||
|
||||
public IEnumerable GetNewObjectList() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Operation {
|
||||
internal class LCSetOperation : ILCOperation {
|
||||
object value;
|
||||
|
||||
internal LCSetOperation(object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public ILCOperation MergeWithPrevious(ILCOperation previousOp) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return LCEncoder.Encode(value);
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
return value;
|
||||
}
|
||||
|
||||
public IEnumerable GetNewObjectList() {
|
||||
if (value is IEnumerable enumerable) {
|
||||
return enumerable;
|
||||
}
|
||||
return new List<object> { value };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Query {
|
||||
internal interface ILCQueryCondition {
|
||||
bool Equals(ILCQueryCondition other);
|
||||
Dictionary<string, object> Encode();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Query {
|
||||
internal class LCCompositionalCondition : ILCQueryCondition {
|
||||
internal const string And = "$and";
|
||||
internal const string Or = "$or";
|
||||
|
||||
readonly string composition;
|
||||
|
||||
List<ILCQueryCondition> conditionList;
|
||||
|
||||
List<string> orderByList;
|
||||
HashSet<string> includes;
|
||||
HashSet<string> selectedKeys;
|
||||
|
||||
internal int Skip {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal int Limit {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal LCCompositionalCondition(string composition = And) {
|
||||
this.composition = composition;
|
||||
Skip = 0;
|
||||
Limit = 30;
|
||||
}
|
||||
|
||||
// 查询条件
|
||||
internal void WhereEqualTo(string key, object value) {
|
||||
Add(new LCEqualCondition(key, value));
|
||||
}
|
||||
|
||||
internal void WhereNotEqualTo(string key, object value) {
|
||||
AddOperation(key, "$ne", value);
|
||||
}
|
||||
|
||||
internal void WhereContainedIn(string key, IEnumerable values) {
|
||||
AddOperation(key, "$in", values);
|
||||
}
|
||||
|
||||
internal void WhereNotContainedIn(string key, IEnumerable values) {
|
||||
AddOperation(key, "nin", values);
|
||||
}
|
||||
|
||||
internal void WhereContainsAll(string key, IEnumerable values) {
|
||||
AddOperation(key, "$all", values);
|
||||
}
|
||||
|
||||
internal void WhereExists(string key) {
|
||||
AddOperation(key, "$exists", true);
|
||||
}
|
||||
|
||||
internal void WhereDoesNotExist(string key) {
|
||||
AddOperation(key, "$exists", false);
|
||||
}
|
||||
|
||||
internal void WhereSizeEqualTo(string key, int size) {
|
||||
AddOperation(key, "$size", size);
|
||||
}
|
||||
|
||||
internal void WhereGreaterThan(string key, object value) {
|
||||
AddOperation(key, "$gt", value);
|
||||
}
|
||||
|
||||
internal void WhereGreaterThanOrEqualTo(string key, object value) {
|
||||
AddOperation(key, "$gte", value);
|
||||
}
|
||||
|
||||
internal void WhereLessThan(string key, object value) {
|
||||
AddOperation(key, "$lt", value);
|
||||
}
|
||||
|
||||
internal void WhereLessThanOrEqualTo(string key, object value) {
|
||||
AddOperation(key, "$lte", value);
|
||||
}
|
||||
|
||||
internal void WhereNear(string key, LCGeoPoint point) {
|
||||
AddOperation(key, "$nearSphere", point);
|
||||
}
|
||||
|
||||
internal void WhereWithinGeoBox(string key, LCGeoPoint southwest, LCGeoPoint northeast) {
|
||||
Dictionary<string, object> value = new Dictionary<string, object> {
|
||||
{ "$box", new List<object> { southwest, northeast } }
|
||||
};
|
||||
AddOperation(key, "$within", value);
|
||||
}
|
||||
|
||||
internal void WhereRelatedTo(LCObject parent, string key) {
|
||||
Add(new LCRelatedCondition(parent, key));
|
||||
}
|
||||
|
||||
internal void WhereStartsWith(string key, string prefix) {
|
||||
AddOperation(key, "$regex", $"^{prefix}.*");
|
||||
}
|
||||
|
||||
internal void WhereEndsWith(string key, string suffix) {
|
||||
AddOperation(key, "$regex", $".*{suffix}$");
|
||||
}
|
||||
|
||||
internal void WhereContains(string key, string subString) {
|
||||
AddOperation(key, "$regex", $".*{subString}.*");
|
||||
}
|
||||
|
||||
void AddOperation(string key, string op, object value) {
|
||||
LCOperationCondition cond = new LCOperationCondition(key, op, value);
|
||||
Add(cond);
|
||||
}
|
||||
|
||||
internal void Add(ILCQueryCondition cond) {
|
||||
if (cond == null) {
|
||||
return;
|
||||
}
|
||||
if (conditionList == null) {
|
||||
conditionList = new List<ILCQueryCondition>();
|
||||
}
|
||||
conditionList.RemoveAll(item => item.Equals(cond));
|
||||
conditionList.Add(cond);
|
||||
}
|
||||
|
||||
// 筛选条件
|
||||
internal void OrderBy(string key) {
|
||||
if (orderByList == null) {
|
||||
orderByList = new List<string>();
|
||||
}
|
||||
orderByList.Add(key);
|
||||
}
|
||||
|
||||
internal void OrderByDescending(string key) {
|
||||
OrderBy($"-{key}");
|
||||
}
|
||||
|
||||
internal void Include(string key) {
|
||||
if (includes == null) {
|
||||
includes = new HashSet<string>();
|
||||
}
|
||||
includes.Add(key);
|
||||
}
|
||||
|
||||
internal void Select(string key) {
|
||||
if (selectedKeys == null) {
|
||||
selectedKeys = new HashSet<string>();
|
||||
}
|
||||
selectedKeys.Add(key);
|
||||
}
|
||||
|
||||
public bool Equals(ILCQueryCondition other) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> Encode() {
|
||||
if (conditionList == null || conditionList.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
if (conditionList.Count == 1) {
|
||||
ILCQueryCondition cond = conditionList[0];
|
||||
return cond.Encode();
|
||||
}
|
||||
return new Dictionary<string, object> {
|
||||
{ composition, LCEncoder.Encode(conditionList) }
|
||||
};
|
||||
}
|
||||
|
||||
internal Dictionary<string, object> BuildParams() {
|
||||
Dictionary<string, object> dict = new Dictionary<string, object> {
|
||||
{ "skip", Skip },
|
||||
{ "limit", Limit }
|
||||
};
|
||||
if (conditionList != null && conditionList.Count > 0) {
|
||||
dict["where"] = JsonConvert.SerializeObject(Encode());
|
||||
}
|
||||
if (orderByList != null && orderByList.Count > 0) {
|
||||
dict["order"] = string.Join(",", orderByList);
|
||||
}
|
||||
if (includes != null && includes.Count > 0) {
|
||||
dict["include"] = string.Join(",", includes);
|
||||
}
|
||||
if (selectedKeys != null && selectedKeys.Count > 0) {
|
||||
dict["keys"] = string.Join(",", selectedKeys);
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
internal string BuildWhere() {
|
||||
if (conditionList == null || conditionList.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
return JsonConvert.SerializeObject(Encode());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Query {
|
||||
internal class LCEqualCondition : ILCQueryCondition {
|
||||
readonly string key;
|
||||
readonly object value;
|
||||
|
||||
internal LCEqualCondition(string key, object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public bool Equals(ILCQueryCondition other) {
|
||||
if (other is LCEqualCondition cond) {
|
||||
return cond.key == key;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{ key, LCEncoder.Encode(value) }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Query {
|
||||
internal class LCOperationCondition : ILCQueryCondition {
|
||||
readonly string key;
|
||||
readonly string op;
|
||||
readonly object value;
|
||||
|
||||
internal LCOperationCondition(string key, string op, object value) {
|
||||
this.key = key;
|
||||
this.op = op;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public bool Equals(ILCQueryCondition other) {
|
||||
if (other is LCOperationCondition cond) {
|
||||
return cond.key == key && cond.op == op;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{ key, new Dictionary<string, object> {
|
||||
{ op, LCEncoder.Encode(value) }
|
||||
} }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage.Internal.Query {
|
||||
internal class LCRelatedCondition : ILCQueryCondition {
|
||||
readonly LCObject parent;
|
||||
readonly string key;
|
||||
|
||||
internal LCRelatedCondition(LCObject parent, string key) {
|
||||
this.parent = parent;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public bool Equals(ILCQueryCondition other) {
|
||||
if (other is LCRelatedCondition cond) {
|
||||
return cond.key == key;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{ "$relatedTo", new Dictionary<string, object> {
|
||||
{ "object", LCEncoder.Encode(parent) },
|
||||
{ "key", key }
|
||||
} }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
/// <summary>
|
||||
/// 访问控制类
|
||||
/// </summary>
|
||||
public class LCACL {
|
||||
const string PublicKey = "*";
|
||||
|
||||
const string RoleKeyPrefix = "role:";
|
||||
|
||||
internal HashSet<string> readers;
|
||||
internal HashSet<string> writers;
|
||||
|
||||
public bool PublicReadAccess {
|
||||
get {
|
||||
return GetAccess(readers, PublicKey);
|
||||
} set {
|
||||
SetAccess(readers, PublicKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool PublicWriteAccess {
|
||||
get {
|
||||
return GetAccess(writers, PublicKey);
|
||||
} set {
|
||||
SetAccess(writers, PublicKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetUserIdReadAccess(string userId) {
|
||||
if (string.IsNullOrEmpty(userId)) {
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
}
|
||||
return GetAccess(readers, userId);
|
||||
}
|
||||
|
||||
public void SetUserIdReadAccess(string userId, bool value) {
|
||||
if (string.IsNullOrEmpty(userId)) {
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
}
|
||||
SetAccess(readers, userId, value);
|
||||
}
|
||||
|
||||
public bool GetUserIdWriteAccess(string userId) {
|
||||
if (string.IsNullOrEmpty(userId)) {
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
}
|
||||
return GetAccess(writers, userId);
|
||||
}
|
||||
|
||||
public void SetUserIdWriteAccess(string userId, bool value) {
|
||||
if (string.IsNullOrEmpty(userId)) {
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
}
|
||||
SetAccess(writers, userId, value);
|
||||
}
|
||||
|
||||
public bool GetUserReadAccess(LCUser user) {
|
||||
if (user == null) {
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
return GetUserIdReadAccess(user.ObjectId);
|
||||
}
|
||||
|
||||
public void SetUserReadAccess(LCUser user, bool value) {
|
||||
if (user == null) {
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
SetUserIdReadAccess(user.ObjectId, value);
|
||||
}
|
||||
|
||||
public bool GetUserWriteAccess(LCUser user) {
|
||||
if (user == null) {
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
return GetUserIdWriteAccess(user.ObjectId);
|
||||
}
|
||||
|
||||
public void SetUserWriteAccess(LCUser user, bool value) {
|
||||
if (user == null) {
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
SetUserIdWriteAccess(user.ObjectId, value);
|
||||
}
|
||||
|
||||
public bool GetRoleReadAccess(LCRole role) {
|
||||
if (role == null) {
|
||||
throw new ArgumentNullException(nameof(role));
|
||||
}
|
||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||
return GetAccess(readers, roleKey);
|
||||
}
|
||||
|
||||
public void SetRoleReadAccess(LCRole role, bool value) {
|
||||
if (role == null) {
|
||||
throw new ArgumentNullException(nameof(role));
|
||||
}
|
||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||
SetAccess(readers, roleKey, value);
|
||||
}
|
||||
|
||||
public bool GetRoleWriteAccess(LCRole role) {
|
||||
if (role == null) {
|
||||
throw new ArgumentNullException(nameof(role));
|
||||
}
|
||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||
return GetAccess(writers, roleKey);
|
||||
}
|
||||
|
||||
public void SetRoleWriteAccess(LCRole role, bool value) {
|
||||
if (role == null) {
|
||||
throw new ArgumentNullException(nameof(role));
|
||||
}
|
||||
string roleKey = $"{RoleKeyPrefix}{role.ObjectId}";
|
||||
SetAccess(writers, roleKey, value);
|
||||
}
|
||||
|
||||
public LCACL() {
|
||||
readers = new HashSet<string>();
|
||||
writers = new HashSet<string>();
|
||||
}
|
||||
|
||||
public static LCACL CreateWithOwner(LCUser owner) {
|
||||
if (owner == null) {
|
||||
throw new ArgumentNullException(nameof(owner));
|
||||
}
|
||||
LCACL acl = new LCACL();
|
||||
acl.SetUserReadAccess(owner, true);
|
||||
acl.SetUserWriteAccess(owner, true);
|
||||
return acl;
|
||||
}
|
||||
|
||||
bool GetAccess(HashSet<string> set, string key) {
|
||||
return set.Contains(key);
|
||||
}
|
||||
|
||||
void SetAccess(HashSet<string> set, string key, bool value) {
|
||||
if (value) {
|
||||
set.Add(key);
|
||||
} else {
|
||||
set.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
/// <summary>
|
||||
/// 云引擎
|
||||
/// </summary>
|
||||
public static class LCCloud {
|
||||
/// <summary>
|
||||
/// 调用云函数,结果为 Dictionary 类型
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Dictionary<string, object>> Run(string name, Dictionary<string, object> parameters = null) {
|
||||
string path = $"functions/{name}";
|
||||
Dictionary<string, object> response = await LeanCloud.HttpClient.Post<Dictionary<string, object>>(path, data: parameters);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static async Task<object> RPC(string name, Dictionary<string, object> parameters = null) {
|
||||
string path = $"call/{name}";
|
||||
Dictionary<string, object> response = await LeanCloud.HttpClient.Post<Dictionary<string, object>>(path, data: parameters);
|
||||
return LCDecoder.Decode(response["result"]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
public class LCException : Exception {
|
||||
public int Code {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public new string Message {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public LCException(int code, string message) {
|
||||
Code = code;
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal.File;
|
||||
using LeanCloud.Storage.Internal.Object;
|
||||
using LeanCloud.Common;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
public class LCFile : LCObject {
|
||||
public const string CLASS_NAME = "_File";
|
||||
|
||||
public string Name {
|
||||
get {
|
||||
return this["name"] as string;
|
||||
} set {
|
||||
this["name"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string MimeType {
|
||||
get {
|
||||
return this["mime_type"] as string;
|
||||
} set {
|
||||
this["mime_type"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Url {
|
||||
get {
|
||||
return this["url"] as string;
|
||||
} set {
|
||||
this["url"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, object> MetaData {
|
||||
get {
|
||||
return this["metaData"] as Dictionary<string, object>;
|
||||
} set {
|
||||
this["metaData"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
readonly byte[] data;
|
||||
|
||||
public LCFile() : base(CLASS_NAME) {
|
||||
MetaData = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
public LCFile(string name, byte[] bytes) : this() {
|
||||
Name = name;
|
||||
data = bytes;
|
||||
}
|
||||
|
||||
public LCFile(string name, string path) : this() {
|
||||
Name = name;
|
||||
MimeType = LCMimeTypeMap.GetMimeType(path);
|
||||
data = File.ReadAllBytes(path);
|
||||
}
|
||||
|
||||
public LCFile(string name, Uri url) : this() {
|
||||
Name = name;
|
||||
Url = url.AbsoluteUri;
|
||||
}
|
||||
|
||||
public void AddMetaData(string key, object value) {
|
||||
MetaData[key] = value;
|
||||
}
|
||||
|
||||
public async Task<LCFile> Save(Action<long, long> onProgress = null) {
|
||||
if (!string.IsNullOrEmpty(Url)) {
|
||||
// 外链方式
|
||||
await base.Save();
|
||||
} else {
|
||||
// 上传文件
|
||||
Dictionary<string, object> uploadToken = await GetUploadToken();
|
||||
string uploadUrl = uploadToken["upload_url"] as string;
|
||||
string key = uploadToken["key"] as string;
|
||||
string token = uploadToken["token"] as string;
|
||||
string provider = uploadToken["provider"] as string;
|
||||
if (provider == "s3") {
|
||||
// AWS
|
||||
LCAWSUploader uploader = new LCAWSUploader(uploadUrl, MimeType, data);
|
||||
await uploader.Upload(onProgress);
|
||||
} else if (provider == "qiniu") {
|
||||
// Qiniu
|
||||
LCQiniuUploader uploader = new LCQiniuUploader(uploadUrl, token, key, data);
|
||||
await uploader.Upload(onProgress);
|
||||
} else {
|
||||
throw new Exception($"{provider} is not support.");
|
||||
}
|
||||
LCObjectData objectData = LCObjectData.Decode(uploadToken);
|
||||
Merge(objectData);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public new async Task Delete() {
|
||||
if (string.IsNullOrEmpty(ObjectId)) {
|
||||
return;
|
||||
}
|
||||
string path = $"files/{ObjectId}";
|
||||
await LeanCloud.HttpClient.Delete(path);
|
||||
}
|
||||
|
||||
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}";
|
||||
}
|
||||
|
||||
async Task<Dictionary<string, object>> GetUploadToken() {
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "name", Name },
|
||||
{ "key", Guid.NewGuid().ToString() },
|
||||
{ "__type", "File" },
|
||||
{ "mime_type", MimeType },
|
||||
{ "metaData", MetaData }
|
||||
};
|
||||
return await LeanCloud.HttpClient.Post<Dictionary<string, object>>("fileTokens", data: data);
|
||||
}
|
||||
|
||||
public static LCQuery<LCFile> GetQuery() {
|
||||
return new LCQuery<LCFile>(CLASS_NAME);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
public class LCGeoPoint {
|
||||
/// <summary>
|
||||
/// 纬度
|
||||
/// </summary>
|
||||
public double Latitude {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 经度
|
||||
/// </summary>
|
||||
public double Longitude {
|
||||
get;
|
||||
}
|
||||
|
||||
public LCGeoPoint(double latitude, double longtitude) {
|
||||
Latitude = latitude;
|
||||
Longitude = longtitude;
|
||||
}
|
||||
|
||||
public static LCGeoPoint Origin {
|
||||
get {
|
||||
return new LCGeoPoint(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 据某点的距离(单位:千米)
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
/// <returns></returns>
|
||||
public double KilometersTo(LCGeoPoint point) {
|
||||
if (point == null) {
|
||||
throw new ArgumentNullException(nameof(point));
|
||||
}
|
||||
return RadiansTo(point) * 6371.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 据某点的距离(单位:英里)
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
/// <returns></returns>
|
||||
public double MilesTo(LCGeoPoint point) {
|
||||
if (point == null) {
|
||||
throw new ArgumentNullException(nameof(point));
|
||||
}
|
||||
return RadiansTo(point) * 3958.8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 据某点的距离(单位:弧度)
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
/// <returns></returns>
|
||||
public double RadiansTo(LCGeoPoint point) {
|
||||
if (point == null) {
|
||||
throw new ArgumentNullException(nameof(point));
|
||||
}
|
||||
double d2r = Math.PI / 180.0;
|
||||
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);
|
||||
double a = sinDeltaLatDiv2 * sinDeltaLatDiv2 +
|
||||
Math.Cos(lat1rad) * Math.Cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2;
|
||||
a = Math.Min(1.0, a);
|
||||
return 2 * Math.Sin(Math.Sqrt(a));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,481 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal.Object;
|
||||
using LeanCloud.Storage.Internal.Operation;
|
||||
using LeanCloud.Storage.Internal.Codec;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
/// <summary>
|
||||
/// 对象类
|
||||
/// </summary>
|
||||
public class LCObject {
|
||||
/// <summary>
|
||||
/// 最近一次与服务端同步的数据
|
||||
/// </summary>
|
||||
LCObjectData data;
|
||||
|
||||
/// <summary>
|
||||
/// 预算数据
|
||||
/// </summary>
|
||||
internal Dictionary<string, object> estimatedData;
|
||||
|
||||
/// <summary>
|
||||
/// 操作字典
|
||||
/// </summary>
|
||||
internal Dictionary<string, ILCOperation> operationDict;
|
||||
|
||||
static readonly Dictionary<Type, LCSubclassInfo> subclassTypeDict = new Dictionary<Type, LCSubclassInfo>();
|
||||
static readonly Dictionary<string, LCSubclassInfo> subclassNameDict = new Dictionary<string, LCSubclassInfo>();
|
||||
|
||||
public string ClassName {
|
||||
get {
|
||||
return data.ClassName;
|
||||
}
|
||||
}
|
||||
|
||||
public string ObjectId {
|
||||
get {
|
||||
return data.ObjectId;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime CreatedAt {
|
||||
get {
|
||||
return data.CreatedAt;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime UpdatedAt {
|
||||
get {
|
||||
return data.UpdatedAt;
|
||||
}
|
||||
}
|
||||
|
||||
public LCACL ACL {
|
||||
get {
|
||||
return this["ACL"] as LCACL ;
|
||||
} set {
|
||||
this["ACL"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
bool isNew;
|
||||
|
||||
bool IsDirty {
|
||||
get {
|
||||
return isNew || estimatedData.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public LCObject(string className) {
|
||||
if (string.IsNullOrEmpty(className)) {
|
||||
throw new ArgumentNullException(nameof(className));
|
||||
}
|
||||
data = new LCObjectData();
|
||||
estimatedData = new Dictionary<string, object>();
|
||||
operationDict = new Dictionary<string, ILCOperation>();
|
||||
|
||||
data.ClassName = className;
|
||||
isNew = true;
|
||||
}
|
||||
|
||||
public static LCObject CreateWithoutData(string className, string objectId) {
|
||||
if (string.IsNullOrEmpty(objectId)) {
|
||||
throw new ArgumentNullException(nameof(objectId));
|
||||
}
|
||||
LCObject obj = new LCObject(className);
|
||||
obj.data.ObjectId = objectId;
|
||||
obj.isNew = false;
|
||||
return obj;
|
||||
}
|
||||
|
||||
internal static LCObject Create(string className) {
|
||||
if (subclassNameDict.TryGetValue(className, out LCSubclassInfo subclassInfo)) {
|
||||
return subclassInfo.Constructor.Invoke();
|
||||
}
|
||||
return new LCObject(className);
|
||||
}
|
||||
|
||||
internal static LCObject Create(Type type) {
|
||||
if (subclassTypeDict.TryGetValue(type, out LCSubclassInfo subclassInfo)) {
|
||||
return subclassInfo.Constructor.Invoke();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object this[string key] {
|
||||
get {
|
||||
if (estimatedData.TryGetValue(key, out object value)) {
|
||||
if (value is LCRelation<LCObject> relation) {
|
||||
relation.Key = key;
|
||||
relation.Parent = this;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (key.StartsWith("_")) {
|
||||
throw new ArgumentException("key should not start with '_'");
|
||||
}
|
||||
if (key == "objectId" || key == "createdAt" || key == "updatedAt") {
|
||||
throw new ArgumentException($"{key} is reserved by LeanCloud");
|
||||
}
|
||||
LCSetOperation setOp = new LCSetOperation(value);
|
||||
ApplyOperation(key, setOp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除字段
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
public void Unset(string key) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
LCDeleteOperation deleteOp = new LCDeleteOperation();
|
||||
ApplyOperation(key, deleteOp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加关联
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
public void AddRelation(string key, LCObject value) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (value == null) {
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
LCAddRelationOperation op = new LCAddRelationOperation(new List<LCObject> { value });
|
||||
ApplyOperation(key, op);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除关联
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
public void RemoveRelation(string key, LCObject value) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (value == null) {
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
LCRemoveRelationOperation op = new LCRemoveRelationOperation(value);
|
||||
ApplyOperation(key, op);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加数字属性值
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
public void Increment(string key, object value) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (value == null) {
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
LCNumberOperation op = new LCNumberOperation(value);
|
||||
ApplyOperation(key, op);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在数组属性中增加一个元素
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
public void Add(string key, object value) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (value == null) {
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
LCAddOperation op = new LCAddOperation(new List<object> { value });
|
||||
ApplyOperation(key, op);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在数组属性中增加一组元素
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="values"></param>
|
||||
public void AddAll(string key, IEnumerable values) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (values == null) {
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
LCAddOperation op = new LCAddOperation(new List<object>(values.Cast<object>()));
|
||||
ApplyOperation(key, op);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在数组属性中增加一个唯一元素
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
public void AddUnique(string key, object value) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (value == null) {
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
LCAddUniqueOperation op = new LCAddUniqueOperation(new List<object> { value });
|
||||
ApplyOperation(key, op);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在数组属性中增加一组唯一元素
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
public void AddAllUnique(string key, IEnumerable values) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (values == null) {
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
LCAddUniqueOperation op = new LCAddUniqueOperation(new List<object>(values.Cast<object>()));
|
||||
ApplyOperation(key, op);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除某个元素
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
public void Remove(string key, object value) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (value == null) {
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
LCRemoveOperation op = new LCRemoveOperation(new List<object> { value });
|
||||
ApplyOperation(key, op);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除一组元素
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="values"></param>
|
||||
public void RemoveAll(string key, IEnumerable values) {
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
if (values == null) {
|
||||
throw new ArgumentNullException(nameof(values));
|
||||
}
|
||||
LCRemoveOperation op = new LCRemoveOperation(new List<object>(values.Cast<object>()));
|
||||
ApplyOperation(key, op);
|
||||
}
|
||||
|
||||
static async Task SaveBatches(Stack<LCBatch> batches) {
|
||||
while (batches.Count > 0) {
|
||||
LCBatch batch = batches.Pop();
|
||||
List<LCObject> dirtyObjects = batch.objects.Where(item => item.IsDirty)
|
||||
.ToList();
|
||||
|
||||
List<Dictionary<string, object>> requestList = dirtyObjects.Select(item => {
|
||||
string path = item.ObjectId == null ?
|
||||
$"/1.1/classes/{item.ClassName}" :
|
||||
$"/1.1/classes/{item.ClassName}/{item.ClassName}";
|
||||
string method = item.ObjectId == null ? "POST" : "PUT";
|
||||
Dictionary<string, object> body = LCEncoder.Encode(item.operationDict) as Dictionary<string, object>;
|
||||
return new Dictionary<string, object> {
|
||||
{ "path", path },
|
||||
{ "method", method },
|
||||
{ "body", body }
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "requests", LCEncoder.Encode(requestList) }
|
||||
};
|
||||
|
||||
List<Dictionary<string, object>> results = await LeanCloud.HttpClient.Post<List<Dictionary<string, object>>>("batch", data: data);
|
||||
List<LCObjectData> resultList = results.Select(item => {
|
||||
if (item.TryGetValue("error", out object error)) {
|
||||
Dictionary<string, object> err = error as Dictionary<string, object>;
|
||||
int code = (int)err["code"];
|
||||
string message = (string)err["error"];
|
||||
throw new LCException(code, message as string);
|
||||
}
|
||||
return LCObjectData.Decode(item["success"] as IDictionary);
|
||||
}).ToList();
|
||||
|
||||
for (int i = 0; i < dirtyObjects.Count; i++) {
|
||||
LCObject obj = dirtyObjects[i];
|
||||
LCObjectData objData = resultList[i];
|
||||
obj.Merge(objData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LCObject> Save(bool fetchWhenSave = false, LCQuery<LCObject> query = null) {
|
||||
if (LCBatch.HasCircleReference(this, new HashSet<LCObject>())) {
|
||||
throw new ArgumentException("Found a circle dependency when save.");
|
||||
}
|
||||
|
||||
Stack<LCBatch> batches = LCBatch.BatchObjects(new List<LCObject> { this }, false);
|
||||
if (batches.Count > 0) {
|
||||
await SaveBatches(batches);
|
||||
}
|
||||
|
||||
string path = ObjectId == null ? $"classes/{ClassName}" : $"classes/{ClassName}/{ObjectId}";
|
||||
Dictionary<string, object> queryParams = new Dictionary<string, object>();
|
||||
if (fetchWhenSave) {
|
||||
queryParams["fetchWhenSave"] = true;
|
||||
}
|
||||
if (query != null) {
|
||||
queryParams["where"] = query.BuildWhere();
|
||||
}
|
||||
Dictionary<string, object> response = ObjectId == null ?
|
||||
await LeanCloud.HttpClient.Post<Dictionary<string, object>>(path, data: LCEncoder.Encode(operationDict) as Dictionary<string, object>, queryParams: queryParams) :
|
||||
await LeanCloud.HttpClient.Put<Dictionary<string, object>>(path, data: LCEncoder.Encode(operationDict) as Dictionary<string, object>, queryParams: queryParams);
|
||||
LCObjectData data = LCObjectData.Decode(response);
|
||||
Merge(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static async Task<List<LCObject>> SaveAll(List<LCObject> objectList) {
|
||||
if (objectList == null) {
|
||||
throw new ArgumentNullException(nameof(objectList));
|
||||
}
|
||||
foreach (LCObject obj in objectList) {
|
||||
if (LCBatch.HasCircleReference(obj, new HashSet<LCObject>())) {
|
||||
throw new ArgumentException("Found a circle dependency when save.");
|
||||
}
|
||||
}
|
||||
Stack<LCBatch> batches = LCBatch.BatchObjects(objectList, true);
|
||||
await SaveBatches(batches);
|
||||
return objectList;
|
||||
}
|
||||
|
||||
public async Task Delete() {
|
||||
if (ObjectId == null) {
|
||||
return;
|
||||
}
|
||||
string path = $"classes/{ClassName}/{ObjectId}";
|
||||
await LeanCloud.HttpClient.Delete(path);
|
||||
}
|
||||
|
||||
public static async Task DeleteAll(List<LCObject> objectList) {
|
||||
if (objectList == null || objectList.Count == 0) {
|
||||
throw new ArgumentNullException(nameof(objectList));
|
||||
}
|
||||
IEnumerable<LCObject> objects = objectList.Where(item => item.ObjectId != null);
|
||||
HashSet<LCObject> objectSet = new HashSet<LCObject>(objects);
|
||||
List<Dictionary<string, object>> requestList = objectSet.Select(item => {
|
||||
string path = $"/{LeanCloud.APIVersion}/classes/{item.ClassName}/{item.ObjectId}";
|
||||
return new Dictionary<string, object> {
|
||||
{ "path", path },
|
||||
{ "method", "DELETE" }
|
||||
};
|
||||
}).ToList();
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "requests", LCEncoder.Encode(requestList) }
|
||||
};
|
||||
await LeanCloud.HttpClient.Post<List<object>>("batch", data: data);
|
||||
}
|
||||
|
||||
public async Task<LCObject> Fetch(IEnumerable<string> keys = null, IEnumerable<string> includes = null) {
|
||||
Dictionary<string, object> queryParams = new Dictionary<string, object>();
|
||||
if (keys != null) {
|
||||
queryParams["keys"] = string.Join(",", keys);
|
||||
}
|
||||
if (includes != null) {
|
||||
queryParams["include"] = string.Join(",", includes);
|
||||
}
|
||||
string path = $"classes/{ClassName}/{ObjectId}";
|
||||
Dictionary<string, object> response = await LeanCloud.HttpClient.Get<Dictionary<string, object>>(path, queryParams: queryParams);
|
||||
LCObjectData objectData = LCObjectData.Decode(response);
|
||||
Merge(objectData);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static void RegisterSubclass<T>(string className, Func<LCObject> constructor) where T : LCObject {
|
||||
Type classType = typeof(T);
|
||||
LCSubclassInfo subclassInfo = new LCSubclassInfo(className, classType, constructor);
|
||||
subclassNameDict[className] = subclassInfo;
|
||||
subclassTypeDict[classType] = subclassInfo;
|
||||
}
|
||||
|
||||
void ApplyOperation(string key, ILCOperation op) {
|
||||
if (operationDict.TryGetValue(key, out ILCOperation previousOp)) {
|
||||
operationDict[key] = op.MergeWithPrevious(previousOp);
|
||||
} else {
|
||||
operationDict[key] = op;
|
||||
}
|
||||
if (op is LCDeleteOperation) {
|
||||
estimatedData.Remove(key);
|
||||
} else {
|
||||
if (estimatedData.TryGetValue(key, out object oldValue)) {
|
||||
estimatedData[key] = op.Apply(oldValue, key);
|
||||
} else {
|
||||
estimatedData[key] = op.Apply(null, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Merge(LCObjectData objectData) {
|
||||
data.ClassName = objectData.ClassName ?? data.ClassName;
|
||||
data.ObjectId = objectData.ObjectId ?? data.ObjectId;
|
||||
data.CreatedAt = objectData.CreatedAt != null ? objectData.CreatedAt : data.CreatedAt;
|
||||
data.UpdatedAt = objectData.UpdatedAt != null ? objectData.UpdatedAt : data.UpdatedAt;
|
||||
// 先将本地的预估数据直接替换
|
||||
data.CustomPropertyDict = estimatedData;
|
||||
// 再将服务端的数据覆盖
|
||||
foreach (KeyValuePair<string, object> kv in objectData.CustomPropertyDict) {
|
||||
string key = kv.Key;
|
||||
object value = kv.Value;
|
||||
data.CustomPropertyDict[key] = value;
|
||||
}
|
||||
|
||||
// 最后重新生成预估数据,用于后续访问和操作
|
||||
RebuildEstimatedData();
|
||||
// 清空操作
|
||||
operationDict.Clear();
|
||||
isNew = false;
|
||||
}
|
||||
|
||||
void RebuildEstimatedData() {
|
||||
estimatedData = new Dictionary<string, object>();
|
||||
foreach (KeyValuePair<string, object> kv in data.CustomPropertyDict) {
|
||||
string key = kv.Key;
|
||||
object value = kv.Value;
|
||||
if (value is IList list) {
|
||||
estimatedData[key] = new List<object>(list.Cast<object>());
|
||||
} else if (value is IDictionary dict) {
|
||||
Dictionary<string, object> d = new Dictionary<string, object>();
|
||||
foreach (DictionaryEntry entry in dict) {
|
||||
string k = entry.Key.ToString();
|
||||
object v = entry.Value;
|
||||
d[k] = v;
|
||||
}
|
||||
estimatedData[key] = d;
|
||||
} else {
|
||||
estimatedData[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,364 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal.Query;
|
||||
using LeanCloud.Storage.Internal.Object;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
/// <summary>
|
||||
/// 查询类
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class LCQuery<T> where T : LCObject {
|
||||
public string ClassName {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
LCCompositionalCondition condition;
|
||||
|
||||
public LCQuery(string className) {
|
||||
ClassName = className;
|
||||
condition = new LCCompositionalCondition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等于
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereEqualTo(string key, object value) {
|
||||
condition.WhereEqualTo(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 不等于
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereNotEqualTo(string key, object value) {
|
||||
condition.WhereNotEqualTo(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 包含
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="values"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereContainedIn(string key, IEnumerable values) {
|
||||
condition.WhereContainedIn(key, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 包含全部
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="values"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereContainsAll(string key, IEnumerable values) {
|
||||
condition.WhereContainsAll(key, values);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存在
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereExists(string key) {
|
||||
condition.WhereExists(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 不存在
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereDoesNotExist(string key) {
|
||||
condition.WhereDoesNotExist(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 长度等于
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereSizeEqualTo(string key, int size) {
|
||||
condition.WhereSizeEqualTo(key, size);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 大于
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereGreaterThan(string key, object value) {
|
||||
condition.WhereGreaterThan(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 大于等于
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereGreaterThanOrEqualTo(string key, object value) {
|
||||
condition.WhereGreaterThanOrEqualTo(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 小于
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereLessThan(string key, object value) {
|
||||
condition.WhereLessThan(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 小于等于
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereLessThanOrEqualTo(string key, object value) {
|
||||
condition.WhereLessThanOrEqualTo(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 相邻
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="point"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereNear(string key, LCGeoPoint point) {
|
||||
condition.WhereNear(key, point);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在坐标区域内
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="southwest"></param>
|
||||
/// <param name="northeast"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereWithinGeoBox(string key, LCGeoPoint southwest, LCGeoPoint northeast) {
|
||||
condition.WhereWithinGeoBox(key, southwest, northeast);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 相关
|
||||
/// </summary>
|
||||
/// <param name="parent"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereRelatedTo(LCObject parent, string key) {
|
||||
condition.WhereRelatedTo(parent, key);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前缀
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="prefix"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereStartsWith(string key, string prefix) {
|
||||
condition.WhereStartsWith(key, prefix);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后缀
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="suffix"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereEndsWith(string key, string suffix) {
|
||||
condition.WhereEndsWith(key, suffix);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字符串包含
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="subString"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> WhereContains(string key, string subString) {
|
||||
condition.WhereContains(key, subString);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按 key 升序
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> OrderBy(string key) {
|
||||
condition.OrderBy(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按 key 降序
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> OrderByDescending(string key) {
|
||||
condition.OrderByDescending(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拉取 key 的完整对象
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> Include(string key) {
|
||||
condition.Include(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 包含 key
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> Select(string key) {
|
||||
condition.Select(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 跳过
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> Skip(int value) {
|
||||
condition.Skip = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 限制数量
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public LCQuery<T> Limit(int value) {
|
||||
condition.Limit = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public async Task<int> Count() {
|
||||
string path = $"classes/{ClassName}";
|
||||
Dictionary<string, object> parameters = BuildParams();
|
||||
parameters["limit"] = 0;
|
||||
parameters["count"] = 1;
|
||||
Dictionary<string, object> ret = await LeanCloud.HttpClient.Get<Dictionary<string, object>>(path, queryParams: parameters);
|
||||
return (int)ret["count"];
|
||||
}
|
||||
|
||||
public async Task<T> Get(string objectId) {
|
||||
if (string.IsNullOrEmpty(objectId)) {
|
||||
throw new ArgumentNullException(nameof(objectId));
|
||||
}
|
||||
WhereEqualTo("objectId", objectId);
|
||||
Limit(1);
|
||||
List<T> results = await Find();
|
||||
if (results != null) {
|
||||
if (results.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
return results[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<List<T>> Find() {
|
||||
string path = $"classes/{ClassName}";
|
||||
Dictionary<string, object> parameters = BuildParams();
|
||||
Dictionary<string, object> response = await LeanCloud.HttpClient.Get<Dictionary<string, object>>(path, queryParams: parameters);
|
||||
List<object> results = response["results"] as List<object>;
|
||||
List<T> list = new List<T>();
|
||||
foreach (object item in results) {
|
||||
LCObjectData objectData = LCObjectData.Decode(item as Dictionary<string, object>);
|
||||
T obj = LCObject.Create(ClassName) as T;
|
||||
obj.Merge(objectData);
|
||||
list.Add(obj);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public async Task<T> First() {
|
||||
Limit(1);
|
||||
List<T> results = await Find();
|
||||
if (results != null && results.Count > 0) {
|
||||
return results[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static LCQuery<T> And(IEnumerable<LCQuery<T>> queries) {
|
||||
if (queries == null || queries.Count() < 1) {
|
||||
throw new ArgumentNullException(nameof(queries));
|
||||
}
|
||||
LCQuery<T> compositionQuery = new LCQuery<T>(null);
|
||||
string className = null;
|
||||
foreach (LCQuery<T> query in queries) {
|
||||
if (className != null && className != query.ClassName) {
|
||||
throw new Exception("All of the queries in an or query must be on the same class.");
|
||||
}
|
||||
className = query.ClassName;
|
||||
compositionQuery.condition.Add(query.condition);
|
||||
}
|
||||
compositionQuery.ClassName = className;
|
||||
return compositionQuery;
|
||||
}
|
||||
|
||||
public static LCQuery<T> Or(IEnumerable<LCQuery<T>> queries) {
|
||||
if (queries == null || queries.Count() < 1) {
|
||||
throw new ArgumentNullException(nameof(queries));
|
||||
}
|
||||
LCQuery<T> compositionQuery = new LCQuery<T>(null);
|
||||
compositionQuery.condition = new LCCompositionalCondition(LCCompositionalCondition.Or);
|
||||
string className = null;
|
||||
foreach (LCQuery<T> query in queries) {
|
||||
if (className != null && className != query.ClassName) {
|
||||
throw new Exception("All of the queries in an or query must be on the same class.");
|
||||
}
|
||||
className = query.ClassName;
|
||||
compositionQuery.condition.Add(query.condition);
|
||||
}
|
||||
compositionQuery.ClassName = className;
|
||||
return compositionQuery;
|
||||
}
|
||||
|
||||
Dictionary<string, object> BuildParams() {
|
||||
return condition.BuildParams();
|
||||
}
|
||||
|
||||
internal string BuildWhere() {
|
||||
return condition.BuildWhere();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/// <summary>
|
||||
/// 关系类
|
||||
/// </summary>
|
||||
namespace LeanCloud.Storage {
|
||||
public class LCRelation<T> where T : LCObject {
|
||||
/// <summary>
|
||||
/// 字段名
|
||||
/// </summary>
|
||||
public string Key {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 父对象
|
||||
/// </summary>
|
||||
public LCObject Parent {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关联类型名
|
||||
/// </summary>
|
||||
public string TargetClass {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public LCRelation() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Relation 的查询对象
|
||||
/// </summary>
|
||||
public LCQuery<T> Query {
|
||||
get {
|
||||
LCQuery<T> query = new LCQuery<T>(TargetClass);
|
||||
query.WhereRelatedTo(Parent, Key);
|
||||
return query;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
namespace LeanCloud.Storage {
|
||||
/// <summary>
|
||||
/// 角色
|
||||
/// </summary>
|
||||
public class LCRole : LCObject {
|
||||
public const string CLASS_NAME = "_Role";
|
||||
|
||||
/// <summary>
|
||||
/// 名字
|
||||
/// </summary>
|
||||
public string Name {
|
||||
get {
|
||||
return this["name"] as string;
|
||||
} set {
|
||||
this["name"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关联角色
|
||||
/// </summary>
|
||||
public LCRelation<LCRole> Roles {
|
||||
get {
|
||||
LCRelation<LCObject> roles = this["roles"] as LCRelation<LCObject>;
|
||||
return new LCRelation<LCRole> {
|
||||
Parent = roles.Parent,
|
||||
Key = "roles"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关联用户
|
||||
/// </summary>
|
||||
public LCRelation<LCUser> Users {
|
||||
get {
|
||||
LCRelation<LCObject> users = this["users"] as LCRelation<LCObject>;
|
||||
return new LCRelation<LCUser> {
|
||||
Parent = users.Parent,
|
||||
Key = "users"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public LCRole() : base(CLASS_NAME) {
|
||||
}
|
||||
|
||||
public static LCRole Create(string name, LCACL acl) {
|
||||
LCRole role = new LCRole() {
|
||||
Name = name,
|
||||
ACL = acl
|
||||
};
|
||||
return role;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色查询对象
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static LCQuery<LCRole> GetQuery() {
|
||||
return new LCQuery<LCRole>(CLASS_NAME);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,553 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal.Object;
|
||||
|
||||
namespace LeanCloud.Storage {
|
||||
public class LCUser : LCObject {
|
||||
public const string CLASS_NAME = "_User";
|
||||
|
||||
public string Username {
|
||||
get {
|
||||
return this["username"] as string;
|
||||
} set {
|
||||
this["username"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Password {
|
||||
get {
|
||||
return this["password"] as string;
|
||||
} set {
|
||||
this["password"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Email {
|
||||
get {
|
||||
return this["email"] as string;
|
||||
} set {
|
||||
this["email"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Mobile {
|
||||
get {
|
||||
return this["mobilePhoneNumber"] as string;
|
||||
} set {
|
||||
this["mobilePhoneNumber"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string SessionToken {
|
||||
get {
|
||||
return this["sessionToken"] as string;
|
||||
} set {
|
||||
this["sessionToken"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool EmailVerified {
|
||||
get {
|
||||
return (bool)this["emailVerified"];
|
||||
}
|
||||
}
|
||||
|
||||
public bool MobileVerified {
|
||||
get {
|
||||
return (bool)this["mobilePhoneVerified"];
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, object> AuthData {
|
||||
get {
|
||||
return this["authData"] as Dictionary<string, object>;
|
||||
} set {
|
||||
this["authData"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
static LCUser currentUser;
|
||||
|
||||
public static Task<LCUser> GetCurrent() {
|
||||
// TODO 加载持久化数据
|
||||
|
||||
return Task.FromResult(currentUser);
|
||||
}
|
||||
|
||||
public LCUser() : base(CLASS_NAME) {
|
||||
|
||||
}
|
||||
|
||||
LCUser(LCObjectData objectData) : this() {
|
||||
Merge(objectData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<LCUser> SignUp() {
|
||||
if (string.IsNullOrEmpty(Username)) {
|
||||
throw new ArgumentNullException(nameof(Username));
|
||||
}
|
||||
if (string.IsNullOrEmpty(Password)) {
|
||||
throw new ArgumentNullException(nameof(Password));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(ObjectId)) {
|
||||
throw new ArgumentException("Cannot sign up a user that already exists.");
|
||||
}
|
||||
await Save();
|
||||
currentUser = this;
|
||||
// TODO Persistence
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求登录注册码
|
||||
/// </summary>
|
||||
/// <param name="mobile"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task RequestLoginSMSCode(string mobile) {
|
||||
if (string.IsNullOrEmpty(mobile)) {
|
||||
throw new ArgumentNullException(nameof(mobile));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "mobilePhoneNumber", mobile }
|
||||
};
|
||||
await LeanCloud.HttpClient.Post<Dictionary<string, object>>("requestLoginSmsCode", data: data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用手机号和验证码注册或登录
|
||||
/// </summary>
|
||||
/// <param name="mobile"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<LCUser> SignUpOrLoginByMobilePhone(string mobile, string code) {
|
||||
if (string.IsNullOrEmpty(mobile)) {
|
||||
throw new ArgumentNullException(nameof(mobile));
|
||||
}
|
||||
if (string.IsNullOrEmpty(mobile)) {
|
||||
throw new ArgumentNullException(nameof(code));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "mobilePhoneNumber", mobile },
|
||||
{ "smsCode", code }
|
||||
};
|
||||
Dictionary<string, object> response = await LeanCloud.HttpClient.Post<Dictionary<string, object>>("usersByMobilePhone", data: data);
|
||||
LCObjectData objectData = LCObjectData.Decode(response);
|
||||
currentUser = new LCUser(objectData);
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以账号和密码登陆
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<LCUser> Login(string username, string password) {
|
||||
if (string.IsNullOrEmpty(username)) {
|
||||
throw new ArgumentNullException(nameof(username));
|
||||
}
|
||||
if (string.IsNullOrEmpty(password)) {
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "username", username },
|
||||
{ "password", password }
|
||||
};
|
||||
return Login(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以邮箱和密码登陆
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<LCUser> LoginByEmail(string email, string password) {
|
||||
if (string.IsNullOrEmpty(email)) {
|
||||
throw new ArgumentNullException(nameof(email));
|
||||
}
|
||||
if (string.IsNullOrEmpty(password)) {
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "email", email },
|
||||
{ "password", password }
|
||||
};
|
||||
return Login(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以手机号和密码登陆
|
||||
/// </summary>
|
||||
/// <param name="mobile"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<LCUser> LoginByMobilePhoneNumber(string mobile, string password) {
|
||||
if (string.IsNullOrEmpty(mobile)) {
|
||||
throw new ArgumentNullException(nameof(mobile));
|
||||
}
|
||||
if (string.IsNullOrEmpty(password)) {
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "mobilePhoneNumber", mobile },
|
||||
{ "password", password }
|
||||
};
|
||||
return Login(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以手机号和验证码登录
|
||||
/// </summary>
|
||||
/// <param name="mobile"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<LCUser> LoginBySMSCode(string mobile, string code) {
|
||||
if (string.IsNullOrEmpty(mobile)) {
|
||||
throw new ArgumentNullException(nameof(mobile));
|
||||
}
|
||||
if (string.IsNullOrEmpty(code)) {
|
||||
throw new ArgumentNullException(nameof(code));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "mobilePhoneNumber", mobile },
|
||||
{ "smsCode", code }
|
||||
};
|
||||
return Login(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用第三方数据登录
|
||||
/// </summary>
|
||||
/// <param name="authData"></param>
|
||||
/// <param name="platform"></param>
|
||||
/// <param name="option"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<LCUser> LoginWithAuthData(Dictionary<string, object> authData, string platform,
|
||||
LCUserAuthDataLoginOption option = null) {
|
||||
if (authData == null) {
|
||||
throw new ArgumentNullException(nameof(authData));
|
||||
}
|
||||
if (string.IsNullOrEmpty(platform)) {
|
||||
throw new ArgumentNullException(nameof(platform));
|
||||
}
|
||||
if (option == null) {
|
||||
option = new LCUserAuthDataLoginOption();
|
||||
}
|
||||
return LoginWithAuthData(platform, authData, option.FailOnNotExist);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用第三方数据和 Union Id 登录
|
||||
/// </summary>
|
||||
/// <param name="authData"></param>
|
||||
/// <param name="platform"></param>
|
||||
/// <param name="unionId"></param>
|
||||
/// <param name="option"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<LCUser> LoginWithAuthDataAndUnionId(Dictionary<string, object> authData, string platform, string unionId,
|
||||
LCUserAuthDataLoginOption option = null) {
|
||||
if (authData == null) {
|
||||
throw new ArgumentNullException(nameof(authData));
|
||||
}
|
||||
if (string.IsNullOrEmpty(platform)) {
|
||||
throw new ArgumentNullException(nameof(platform));
|
||||
}
|
||||
if (string.IsNullOrEmpty(unionId)) {
|
||||
throw new ArgumentNullException(nameof(unionId));
|
||||
}
|
||||
if (option == null) {
|
||||
option = new LCUserAuthDataLoginOption();
|
||||
}
|
||||
MergeAuthData(authData, unionId, option);
|
||||
return LoginWithAuthData(platform, authData, option.FailOnNotExist);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绑定第三方登录
|
||||
/// </summary>
|
||||
/// <param name="authData"></param>
|
||||
/// <param name="platform"></param>
|
||||
/// <returns></returns>
|
||||
public Task AssociateAuthData(Dictionary<string, object> authData, string platform) {
|
||||
if (authData == null) {
|
||||
throw new ArgumentNullException(nameof(authData));
|
||||
}
|
||||
if (string.IsNullOrEmpty(platform)) {
|
||||
throw new ArgumentNullException(nameof(platform));
|
||||
}
|
||||
return LinkWithAuthData(platform, authData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Union Id 绑定第三方登录
|
||||
/// </summary>
|
||||
/// <param name="authData"></param>
|
||||
/// <param name="platform"></param>
|
||||
/// <param name="unionId"></param>
|
||||
/// <param name="option"></param>
|
||||
/// <returns></returns>
|
||||
public Task AssociateAuthDataAndUnionId(Dictionary<string, object> authData, string platform, string unionId,
|
||||
LCUserAuthDataLoginOption option = null) {
|
||||
if (authData == null) {
|
||||
throw new ArgumentNullException(nameof(authData));
|
||||
}
|
||||
if (string.IsNullOrEmpty(platform)) {
|
||||
throw new ArgumentNullException(nameof(platform));
|
||||
}
|
||||
if (string.IsNullOrEmpty(unionId)) {
|
||||
throw new ArgumentNullException(nameof(unionId));
|
||||
}
|
||||
if (option == null) {
|
||||
option = new LCUserAuthDataLoginOption();
|
||||
}
|
||||
MergeAuthData(authData, unionId, option);
|
||||
return LinkWithAuthData(platform, authData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解绑第三方登录
|
||||
/// </summary>
|
||||
/// <param name="platform"></param>
|
||||
/// <returns></returns>
|
||||
public Task DisassociateWithAuthData(string platform) {
|
||||
if (string.IsNullOrEmpty(platform)) {
|
||||
throw new ArgumentNullException(nameof(platform));
|
||||
}
|
||||
return LinkWithAuthData(platform, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 匿名登录
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Task<LCUser> LoginAnonymously() {
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "id", Guid.NewGuid().ToString() }
|
||||
};
|
||||
return LoginWithAuthData(data, "anonymous");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求验证邮箱
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task RequestEmailVerify(string email) {
|
||||
if (string.IsNullOrEmpty(email)) {
|
||||
throw new ArgumentNullException(nameof(email));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "email", email }
|
||||
};
|
||||
await LeanCloud.HttpClient.Post<Dictionary<string, object>>("requestEmailVerify", data: data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求手机验证码
|
||||
/// </summary>
|
||||
/// <param name="mobile"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task RequestMobilePhoneVerify(string mobile) {
|
||||
if (string.IsNullOrEmpty(mobile)) {
|
||||
throw new ArgumentNullException(nameof(mobile));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "mobilePhoneNumber", mobile }
|
||||
};
|
||||
await LeanCloud.HttpClient.Post<Dictionary<string, object>>("requestMobilePhoneVerify", data: data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证手机号
|
||||
/// </summary>
|
||||
/// <param name="mobile"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task VerifyMobilePhone(string mobile, string code) {
|
||||
if (string.IsNullOrEmpty(mobile)) {
|
||||
throw new ArgumentNullException(nameof(mobile));
|
||||
}
|
||||
if (string.IsNullOrEmpty(code)) {
|
||||
throw new ArgumentNullException(nameof(code));
|
||||
}
|
||||
string path = $"verifyMobilePhone/{code}";
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "mobilePhoneNumber", mobile }
|
||||
};
|
||||
await LeanCloud.HttpClient.Post<Dictionary<string, object>>(path, data: data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前用户
|
||||
/// </summary>
|
||||
/// <param name="sessionToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<LCUser> BecomeWithSessionToken(string sessionToken) {
|
||||
if (string.IsNullOrEmpty(sessionToken)) {
|
||||
throw new ArgumentNullException(nameof(sessionToken));
|
||||
}
|
||||
Dictionary<string, object> headers = new Dictionary<string, object> {
|
||||
{ "X-LC-Session", sessionToken }
|
||||
};
|
||||
Dictionary<string, object> response = await LeanCloud.HttpClient.Get<Dictionary<string, object>>("users/me",
|
||||
headers: headers);
|
||||
LCObjectData objectData = LCObjectData.Decode(response);
|
||||
currentUser = new LCUser(objectData);
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求使用邮箱重置密码
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task RequestPasswordReset(string email) {
|
||||
if (string.IsNullOrEmpty(email)) {
|
||||
throw new ArgumentNullException(nameof(email));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "email", email }
|
||||
};
|
||||
await LeanCloud.HttpClient.Post<Dictionary<string, object>>("requestPasswordReset",
|
||||
data: data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求验证码重置密码
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task RequestPasswordRestBySmsCode(string mobile) {
|
||||
if (string.IsNullOrEmpty(mobile)) {
|
||||
throw new ArgumentNullException(nameof(mobile));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "mobilePhoneNumber", mobile }
|
||||
};
|
||||
await LeanCloud.HttpClient.Post<Dictionary<string, object>>("requestPasswordResetBySmsCode",
|
||||
data: data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用验证码重置密码
|
||||
/// </summary>
|
||||
/// <param name="mobile"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="newPassword"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task ResetPasswordBySmsCode(string mobile, string code, string newPassword) {
|
||||
if (string.IsNullOrEmpty(mobile)) {
|
||||
throw new ArgumentNullException(nameof(mobile));
|
||||
}
|
||||
if (string.IsNullOrEmpty(code)) {
|
||||
throw new ArgumentNullException(nameof(code));
|
||||
}
|
||||
if (string.IsNullOrEmpty(newPassword)) {
|
||||
throw new ArgumentNullException(nameof(newPassword));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "mobilePhoneNumber", mobile },
|
||||
{ "password", newPassword }
|
||||
};
|
||||
await LeanCloud.HttpClient.Put<Dictionary<string, object>>($"resetPasswordBySmsCode/{code}",
|
||||
data: data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新密码
|
||||
/// </summary>
|
||||
/// <param name="oldPassword"></param>
|
||||
/// <param name="newPassword"></param>
|
||||
/// <returns></returns>
|
||||
public async Task UpdatePassword(string oldPassword, string newPassword) {
|
||||
if (string.IsNullOrEmpty(oldPassword)) {
|
||||
throw new ArgumentNullException(nameof(oldPassword));
|
||||
}
|
||||
if (string.IsNullOrEmpty(newPassword)) {
|
||||
throw new ArgumentNullException(nameof(newPassword));
|
||||
}
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "old_password", oldPassword },
|
||||
{ "new_password", newPassword }
|
||||
};
|
||||
Dictionary<string, object> response = await LeanCloud.HttpClient.Put<Dictionary<string, object>>(
|
||||
$"users/{ObjectId}/updatePassword", data:data);
|
||||
LCObjectData objectData = LCObjectData.Decode(response);
|
||||
Merge(objectData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销登录
|
||||
/// </summary>
|
||||
public static Task Logout() {
|
||||
currentUser = null;
|
||||
// TODO 清理持久化数据
|
||||
|
||||
return Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否是有效登录
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> IsAuthenticated() {
|
||||
if (SessionToken == null || ObjectId == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
await LeanCloud.HttpClient.Get<Dictionary<string, object>>("users/me");
|
||||
return true;
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 得到 LCUser 类型的查询对象
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static LCQuery<LCUser> GetQuery() {
|
||||
return new LCQuery<LCUser>(CLASS_NAME);
|
||||
}
|
||||
|
||||
Task LinkWithAuthData(string authType, Dictionary<string, object> data) {
|
||||
AuthData = new Dictionary<string, object> {
|
||||
{ authType, data }
|
||||
};
|
||||
return Save();
|
||||
}
|
||||
|
||||
static async Task<LCUser> Login(Dictionary<string, object> data) {
|
||||
Dictionary<string, object> response = await LeanCloud.HttpClient.Post<Dictionary<string, object>>("login", data: data);
|
||||
LCObjectData objectData = LCObjectData.Decode(response);
|
||||
currentUser = new LCUser(objectData);
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
static async Task<LCUser> LoginWithAuthData(string authType, Dictionary<string, object> data, bool failOnNotExist) {
|
||||
Dictionary<string, object> authData = new Dictionary<string, object> {
|
||||
{ authType, data }
|
||||
};
|
||||
string path = failOnNotExist ? "users?failOnNotExist=true" : "users";
|
||||
Dictionary<string, object> response = await LeanCloud.HttpClient.Post<Dictionary<string, object>>(path, data: new Dictionary<string, object> {
|
||||
{ "authData", authData }
|
||||
});
|
||||
LCObjectData objectData = LCObjectData.Decode(response);
|
||||
currentUser = new LCUser(objectData);
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
static void MergeAuthData(Dictionary<string, object> authData, string unionId, LCUserAuthDataLoginOption option) {
|
||||
authData["platform"] = option.UnionIdPlatform;
|
||||
authData["main_account"] = option.AsMainAccount;
|
||||
authData["unionid"] = unionId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/// <summary>
|
||||
/// 第三方登录选项
|
||||
/// </summary>
|
||||
namespace LeanCloud.Storage {
|
||||
public class LCUserAuthDataLoginOption {
|
||||
/// <summary>
|
||||
/// Union Id 平台
|
||||
/// </summary>
|
||||
public string UnionIdPlatform {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否作为主账号
|
||||
/// </summary>
|
||||
public bool AsMainAccount {
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否在不存在的情况下返回失败
|
||||
/// </summary>
|
||||
public bool FailOnNotExist {
|
||||
get; set;
|
||||
}
|
||||
|
||||
public LCUserAuthDataLoginOption() {
|
||||
UnionIdPlatform = "weixin";
|
||||
AsMainAccount = false;
|
||||
FailOnNotExist = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using LeanCloud.Storage;
|
||||
using LeanCloud.Storage.Internal.Http;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// LeanCloud 全局接口
|
||||
/// </summary>
|
||||
public class LeanCloud {
|
||||
// SDK 版本号,用于 User-Agent 统计
|
||||
internal const string SDKVersion = "0.1.0";
|
||||
|
||||
// 接口版本号,用于接口版本管理
|
||||
internal const string APIVersion = "1.1";
|
||||
|
||||
public static bool UseProduction {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal static LCHttpClient HttpClient {
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public static void Initialize(string appId, string appKey, string server = null) {
|
||||
if (string.IsNullOrEmpty(appId)) {
|
||||
throw new ArgumentException(nameof(appId));
|
||||
}
|
||||
if (string.IsNullOrEmpty(appKey)) {
|
||||
throw new ArgumentException(nameof(appKey));
|
||||
}
|
||||
// 注册 LeanCloud 内部子类化类型
|
||||
LCObject.RegisterSubclass<LCUser>(LCUser.CLASS_NAME, () => new LCUser());
|
||||
LCObject.RegisterSubclass<LCRole>(LCRole.CLASS_NAME, () => new LCRole());
|
||||
LCObject.RegisterSubclass<LCFile>(LCFile.CLASS_NAME, () => new LCFile());
|
||||
|
||||
HttpClient = new LCHttpClient(appId, appKey, server, SDKVersion, APIVersion);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,384 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVPlugins : IAVCorePlugins
|
||||
{
|
||||
private static readonly object instanceMutex = new object();
|
||||
private static IAVCorePlugins instance;
|
||||
public static IAVCorePlugins Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (instanceMutex)
|
||||
{
|
||||
instance = instance ?? new AVPlugins();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (instanceMutex)
|
||||
{
|
||||
instance = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object mutex = new object();
|
||||
|
||||
#region Server Controllers
|
||||
|
||||
private IHttpClient httpClient;
|
||||
private IAppRouterController appRouterController;
|
||||
private IAVCommandRunner commandRunner;
|
||||
private IStorageController storageController;
|
||||
|
||||
private IAVCloudCodeController cloudCodeController;
|
||||
private IAVConfigController configController;
|
||||
private IAVFileController fileController;
|
||||
private IAVObjectController objectController;
|
||||
private IAVQueryController queryController;
|
||||
private IAVSessionController sessionController;
|
||||
private IAVUserController userController;
|
||||
private IObjectSubclassingController subclassingController;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Current Instance Controller
|
||||
|
||||
private IAVCurrentUserController currentUserController;
|
||||
private IInstallationIdController installationIdController;
|
||||
|
||||
#endregion
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
HttpClient = null;
|
||||
AppRouterController = null;
|
||||
CommandRunner = null;
|
||||
StorageController = null;
|
||||
|
||||
CloudCodeController = null;
|
||||
FileController = null;
|
||||
ObjectController = null;
|
||||
SessionController = null;
|
||||
UserController = null;
|
||||
SubclassingController = null;
|
||||
|
||||
CurrentUserController = null;
|
||||
InstallationIdController = null;
|
||||
}
|
||||
}
|
||||
|
||||
public IHttpClient HttpClient
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
httpClient = httpClient ?? new HttpClient();
|
||||
return httpClient;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
httpClient = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAppRouterController AppRouterController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
appRouterController = appRouterController ?? new AppRouterController();
|
||||
return appRouterController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
appRouterController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVCommandRunner CommandRunner
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
commandRunner = commandRunner ?? new AVCommandRunner(HttpClient, InstallationIdController);
|
||||
return commandRunner;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
commandRunner = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !UNITY
|
||||
public IStorageController StorageController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
storageController = storageController ?? new StorageController(AVClient.CurrentConfiguration.ApplicationId);
|
||||
return storageController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
storageController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if UNITY
|
||||
public IStorageController StorageController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
storageController = storageController ?? new StorageController(AVInitializeBehaviour.IsWebPlayer, AVClient.CurrentConfiguration.ApplicationId);
|
||||
return storageController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
storageController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public IAVCloudCodeController CloudCodeController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
cloudCodeController = cloudCodeController ?? new AVCloudCodeController(CommandRunner);
|
||||
return cloudCodeController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
cloudCodeController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVFileController FileController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (AVClient.CurrentConfiguration.RegionValue == 0)
|
||||
fileController = fileController ?? new QiniuFileController(CommandRunner);
|
||||
else if (AVClient.CurrentConfiguration.RegionValue == 2)
|
||||
fileController = fileController ?? new QCloudCosFileController(CommandRunner);
|
||||
else if (AVClient.CurrentConfiguration.RegionValue == 1)
|
||||
fileController = fileController ?? new AWSS3FileController(CommandRunner);
|
||||
|
||||
return fileController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
fileController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVConfigController ConfigController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (configController == null)
|
||||
{
|
||||
configController = new AVConfigController(CommandRunner, StorageController);
|
||||
}
|
||||
return configController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
configController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVObjectController ObjectController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
objectController = objectController ?? new AVObjectController(CommandRunner);
|
||||
return objectController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
objectController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVQueryController QueryController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (queryController == null)
|
||||
{
|
||||
queryController = new AVQueryController(CommandRunner);
|
||||
}
|
||||
return queryController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
queryController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVSessionController SessionController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
sessionController = sessionController ?? new AVSessionController(CommandRunner);
|
||||
return sessionController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
sessionController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVUserController UserController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
userController = userController ?? new AVUserController(CommandRunner);
|
||||
return userController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
userController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVCurrentUserController CurrentUserController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
currentUserController = currentUserController ?? new AVCurrentUserController(StorageController);
|
||||
return currentUserController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
currentUserController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IObjectSubclassingController SubclassingController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (subclassingController == null)
|
||||
{
|
||||
subclassingController = new ObjectSubclassingController();
|
||||
subclassingController.AddRegisterHook(typeof(AVUser), () => CurrentUserController.ClearFromMemory());
|
||||
}
|
||||
return subclassingController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
subclassingController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IInstallationIdController InstallationIdController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
installationIdController = installationIdController ?? new InstallationIdController(StorageController);
|
||||
return installationIdController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
installationIdController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AppRouterController : IAppRouterController
|
||||
{
|
||||
private AppRouterState currentState;
|
||||
private object mutex = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Get current app's router state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AppRouterState Get()
|
||||
{
|
||||
if (string.IsNullOrEmpty(AVClient.CurrentConfiguration.ApplicationId))
|
||||
{
|
||||
throw new AVException(AVException.ErrorCode.NotInitialized, "ApplicationId can not be null.");
|
||||
}
|
||||
AppRouterState state;
|
||||
state = AppRouterState.GetInitial(AVClient.CurrentConfiguration.ApplicationId, AVClient.CurrentConfiguration.Region);
|
||||
|
||||
lock (mutex)
|
||||
{
|
||||
if (currentState != null)
|
||||
{
|
||||
if (!currentState.isExpired())
|
||||
state = currentState;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isExpired())
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
state.FetchedAt = DateTime.Now + TimeSpan.FromMinutes(10);
|
||||
}
|
||||
Task.Factory.StartNew(RefreshAsync);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
public Task RefreshAsync()
|
||||
{
|
||||
return QueryAsync(CancellationToken.None).ContinueWith(t =>
|
||||
{
|
||||
if (!t.IsFaulted && !t.IsCanceled)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
currentState = t.Result;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Task<AppRouterState> QueryAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
string appId = AVClient.CurrentConfiguration.ApplicationId;
|
||||
string url = string.Format("https://app-router.leancloud.cn/2/route?appId={0}", appId);
|
||||
|
||||
return AVClient.HttpGetAsync(new Uri(url)).ContinueWith(t =>
|
||||
{
|
||||
var tcs = new TaskCompletionSource<AppRouterState>();
|
||||
if (t.Result.Item1 != HttpStatusCode.OK)
|
||||
{
|
||||
tcs.SetException(new AVException(AVException.ErrorCode.ConnectionFailed, "can not reach router.", null));
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = Json.Parse(t.Result.Item2) as IDictionary<String, Object>;
|
||||
tcs.SetResult(ParseAppRouterState(result));
|
||||
}
|
||||
return tcs.Task;
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
private static AppRouterState ParseAppRouterState(IDictionary<string, object> jsonObj)
|
||||
{
|
||||
var state = new AppRouterState()
|
||||
{
|
||||
TTL = (int)jsonObj["ttl"],
|
||||
StatsServer = jsonObj["stats_server"] as string,
|
||||
RealtimeRouterServer = jsonObj["rtm_router_server"] as string,
|
||||
PushServer = jsonObj["push_server"] as string,
|
||||
EngineServer = jsonObj["engine_server"] as string,
|
||||
ApiServer = jsonObj["api_server"] as string,
|
||||
Source = "network",
|
||||
};
|
||||
return state;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
currentState = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AppRouterState
|
||||
{
|
||||
public long TTL { get; internal set; }
|
||||
public string ApiServer { get; internal set; }
|
||||
public string EngineServer { get; internal set; }
|
||||
public string PushServer { get; internal set; }
|
||||
public string RealtimeRouterServer { get; internal set; }
|
||||
public string StatsServer { get; internal set; }
|
||||
public string Source { get; internal set; }
|
||||
|
||||
public DateTime FetchedAt { get; internal set; }
|
||||
|
||||
private static object mutex = new object();
|
||||
|
||||
public AppRouterState()
|
||||
{
|
||||
FetchedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is this app router state expired.
|
||||
/// </summary>
|
||||
public bool isExpired()
|
||||
{
|
||||
return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the initial usable router state
|
||||
/// </summary>
|
||||
/// <param name="appId">Current app's appId</param>
|
||||
/// <param name="region">Current app's region</param>
|
||||
/// <returns>Initial app router state</returns>
|
||||
public static AppRouterState GetInitial(string appId, AVClient.Configuration.AVRegion region)
|
||||
{
|
||||
var regionValue = (int)region;
|
||||
var prefix = appId.Substring(0, 8).ToLower();
|
||||
switch (regionValue)
|
||||
{
|
||||
case 0:
|
||||
// 华北
|
||||
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),
|
||||
Source = "initial",
|
||||
};
|
||||
case 1:
|
||||
// 美国
|
||||
return new AppRouterState()
|
||||
{
|
||||
TTL = -1,
|
||||
ApiServer = string.Format("{0}.api.lncldglobal.com", prefix),
|
||||
EngineServer = string.Format("{0}.engine.lncldglobal.com", prefix),
|
||||
PushServer = string.Format("{0}.push.lncldglobal.com", prefix),
|
||||
RealtimeRouterServer = string.Format("{0}.rtm.lncldglobal.com", prefix),
|
||||
StatsServer = string.Format("{0}.stats.lncldglobal.com", prefix),
|
||||
Source = "initial",
|
||||
};
|
||||
case 2:
|
||||
// 华东
|
||||
return new AppRouterState() {
|
||||
TTL = -1,
|
||||
ApiServer = string.Format("{0}.api.lncldapi.com", prefix),
|
||||
EngineServer = string.Format("{0}.engine.lncldapi.com", prefix),
|
||||
PushServer = string.Format("{0}.push.lncldapi.com", prefix),
|
||||
RealtimeRouterServer = string.Format("{0}.rtm.lncldapi.com", prefix),
|
||||
StatsServer = string.Format("{0}.stats.lncldapi.com", prefix),
|
||||
Source = "initial",
|
||||
};
|
||||
default:
|
||||
throw new AVException(AVException.ErrorCode.OtherCause, "invalid region");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IAppRouterController
|
||||
{
|
||||
AppRouterState Get();
|
||||
/// <summary>
|
||||
/// Start refresh the app router.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task RefreshAsync();
|
||||
void Clear();
|
||||
/// <summary>
|
||||
/// Query the app router.
|
||||
/// </summary>
|
||||
/// <returns>New AppRouterState</returns>
|
||||
Task<AppRouterState> QueryAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IAVAuthenticationProvider {
|
||||
/// <summary>
|
||||
/// Authenticates with the service.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task<IDictionary<string, object>> AuthenticateAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deauthenticates (logs out) the user associated with this provider. This
|
||||
/// call may block.
|
||||
/// </summary>
|
||||
void Deauthenticate();
|
||||
|
||||
/// <summary>
|
||||
/// Restores authentication that has been serialized, such as session keys,
|
||||
/// etc.
|
||||
/// </summary>
|
||||
/// <param name="authData">The auth data for the provider. This value may be null
|
||||
/// when unlinking an account.</param>
|
||||
/// <returns><c>true</c> iff the authData was successfully synchronized. A <c>false</c> return
|
||||
/// value indicates that the user should no longer be associated because of bad auth
|
||||
/// data.</returns>
|
||||
bool RestoreAuthentication(IDictionary<string, object> authData);
|
||||
|
||||
/// <summary>
|
||||
/// Provides a unique name for the type of authentication the provider does.
|
||||
/// For example, the FacebookAuthenticationProvider would return "facebook".
|
||||
/// </summary>
|
||||
string AuthType { get; }
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Utilities;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVCloudCodeController : IAVCloudCodeController
|
||||
{
|
||||
private readonly IAVCommandRunner commandRunner;
|
||||
|
||||
public AVCloudCodeController(IAVCommandRunner commandRunner)
|
||||
{
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
|
||||
public Task<T> CallFunctionAsync<T>(String name,
|
||||
IDictionary<string, object> parameters,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand(string.Format("functions/{0}", Uri.EscapeUriString(name)),
|
||||
method: "POST",
|
||||
sessionToken: sessionToken,
|
||||
data: NoObjectsEncoder.Instance.Encode(parameters) as IDictionary<string, object>);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary<string, object>;
|
||||
if (!decoded.ContainsKey("result"))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return Conversion.To<T>(decoded["result"]);
|
||||
});
|
||||
}
|
||||
|
||||
public Task<T> RPCFunction<T>(string name, IDictionary<string, object> parameters, string sessionToken, CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand(string.Format("call/{0}", Uri.EscapeUriString(name)),
|
||||
method: "POST",
|
||||
sessionToken: sessionToken,
|
||||
data: PointerOrLocalIdEncoder.Instance.Encode(parameters) as IDictionary<string, object>);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary<string, object>;
|
||||
if (!decoded.ContainsKey("result"))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return Conversion.To<T>(decoded["result"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IAVCloudCodeController
|
||||
{
|
||||
Task<T> CallFunctionAsync<T>(string name,
|
||||
IDictionary<string, object> parameters,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<T> RPCFunction<T>(string name, IDictionary<string, object> parameters,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// AVCommand is an <see cref="HttpRequest"/> with pre-populated
|
||||
/// headers.
|
||||
/// </summary>
|
||||
public class AVCommand : HttpRequest
|
||||
{
|
||||
public IDictionary<string, object> DataObject { get; private set; }
|
||||
public override Stream Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (base.Data != null)
|
||||
{
|
||||
return base.Data;
|
||||
}
|
||||
|
||||
return base.Data = (DataObject != null
|
||||
? new MemoryStream(Encoding.UTF8.GetBytes(Json.Encode(DataObject)))
|
||||
: null);
|
||||
}
|
||||
set { base.Data = value; }
|
||||
}
|
||||
|
||||
public AVCommand(string relativeUri,
|
||||
string method,
|
||||
string sessionToken = null,
|
||||
IList<KeyValuePair<string, string>> headers = null,
|
||||
IDictionary<string, object> data = null) : this(relativeUri: relativeUri,
|
||||
method: method,
|
||||
sessionToken: sessionToken,
|
||||
headers: headers,
|
||||
stream: null,
|
||||
contentType: data != null ? "application/json" : null)
|
||||
{
|
||||
DataObject = data;
|
||||
}
|
||||
|
||||
public AVCommand(string relativeUri,
|
||||
string method,
|
||||
string sessionToken = null,
|
||||
IList<KeyValuePair<string, string>> headers = null,
|
||||
Stream stream = null,
|
||||
string contentType = null)
|
||||
{
|
||||
var state = AVPlugins.Instance.AppRouterController.Get();
|
||||
var urlTemplate = "https://{0}/{1}/{2}";
|
||||
AVClient.Configuration configuration = AVClient.CurrentConfiguration;
|
||||
var apiVersion = "1.1";
|
||||
if (relativeUri.StartsWith("push") || relativeUri.StartsWith("installations"))
|
||||
{
|
||||
Uri = new Uri(string.Format(urlTemplate, state.PushServer, apiVersion, relativeUri));
|
||||
if (configuration.PushServer != null)
|
||||
{
|
||||
Uri = new Uri(string.Format("{0}{1}/{2}", configuration.PushServer, apiVersion, relativeUri));
|
||||
}
|
||||
}
|
||||
else if (relativeUri.StartsWith("stats") || relativeUri.StartsWith("always_collect") || relativeUri.StartsWith("statistics"))
|
||||
{
|
||||
Uri = new Uri(string.Format(urlTemplate, state.StatsServer, apiVersion, relativeUri));
|
||||
if (configuration.StatsServer != null)
|
||||
{
|
||||
Uri = new Uri(string.Format("{0}{1}/{2}", configuration.StatsServer, apiVersion, relativeUri));
|
||||
}
|
||||
}
|
||||
else if (relativeUri.StartsWith("functions") || relativeUri.StartsWith("call"))
|
||||
{
|
||||
Uri = new Uri(string.Format(urlTemplate, state.EngineServer, apiVersion, relativeUri));
|
||||
|
||||
if (configuration.EngineServer != null)
|
||||
{
|
||||
Uri = new Uri(string.Format("{0}{1}/{2}", configuration.EngineServer, apiVersion, relativeUri));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Uri = new Uri(string.Format(urlTemplate, state.ApiServer, apiVersion, relativeUri));
|
||||
|
||||
if (configuration.ApiServer != null)
|
||||
{
|
||||
Uri = new Uri(string.Format("{0}{1}/{2}", configuration.ApiServer, apiVersion, relativeUri));
|
||||
}
|
||||
}
|
||||
Method = method;
|
||||
Data = stream;
|
||||
Headers = new List<KeyValuePair<string, string>>(headers ?? Enumerable.Empty<KeyValuePair<string, string>>());
|
||||
|
||||
string useProduction = AVClient.UseProduction ? "1" : "0";
|
||||
Headers.Add(new KeyValuePair<string, string>("X-LC-Prod", useProduction));
|
||||
|
||||
if (!string.IsNullOrEmpty(sessionToken))
|
||||
{
|
||||
Headers.Add(new KeyValuePair<string, string>("X-LC-Session", sessionToken));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
Headers.Add(new KeyValuePair<string, string>("Content-Type", contentType));
|
||||
}
|
||||
}
|
||||
|
||||
public AVCommand(AVCommand other)
|
||||
{
|
||||
this.Uri = other.Uri;
|
||||
this.Method = other.Method;
|
||||
this.DataObject = other.DataObject;
|
||||
this.Headers = new List<KeyValuePair<string, string>>(other.Headers);
|
||||
this.Data = other.Data;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Command Runner.
|
||||
/// </summary>
|
||||
public class AVCommandRunner : IAVCommandRunner
|
||||
{
|
||||
private readonly IHttpClient httpClient;
|
||||
private readonly IInstallationIdController installationIdController;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="httpClient"></param>
|
||||
/// <param name="installationIdController"></param>
|
||||
public AVCommandRunner(IHttpClient httpClient, IInstallationIdController installationIdController)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.installationIdController = installationIdController;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <param name="uploadProgress"></param>
|
||||
/// <param name="downloadProgress"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public Task<Tuple<HttpStatusCode, IDictionary<string, object>>> RunCommandAsync(AVCommand command,
|
||||
IProgress<AVUploadProgressEventArgs> uploadProgress = null,
|
||||
IProgress<AVDownloadProgressEventArgs> downloadProgress = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return PrepareCommand(command).ContinueWith(commandTask =>
|
||||
{
|
||||
var requestLog = commandTask.Result.ToLog();
|
||||
AVClient.PrintLog("http=>" + requestLog);
|
||||
|
||||
return httpClient.ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var response = t.Result;
|
||||
var contentString = response.Item2;
|
||||
int responseCode = (int)response.Item1;
|
||||
|
||||
var responseLog = responseCode + ";" + contentString;
|
||||
AVClient.PrintLog("http<=" + responseLog);
|
||||
|
||||
if (responseCode >= 500)
|
||||
{
|
||||
// Server error, return InternalServerError.
|
||||
throw new AVException(AVException.ErrorCode.InternalServerError, response.Item2);
|
||||
}
|
||||
else if (contentString != null)
|
||||
{
|
||||
IDictionary<string, object> contentJson = null;
|
||||
try
|
||||
{
|
||||
if (contentString.StartsWith("["))
|
||||
{
|
||||
var arrayJson = Json.Parse(contentString);
|
||||
contentJson = new Dictionary<string, object> { { "results", arrayJson } };
|
||||
}
|
||||
else
|
||||
{
|
||||
contentJson = Json.Parse(contentString) as IDictionary<string, object>;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new AVException(AVException.ErrorCode.OtherCause,
|
||||
"Invalid response from server", e);
|
||||
}
|
||||
if (responseCode < 200 || responseCode > 299)
|
||||
{
|
||||
AVClient.PrintLog("error response code:" + responseCode);
|
||||
int code = (int)(contentJson.ContainsKey("code") ? (int)contentJson["code"] : (int)AVException.ErrorCode.OtherCause);
|
||||
string error = contentJson.ContainsKey("error") ?
|
||||
contentJson["error"] as string : contentString;
|
||||
AVException.ErrorCode ec = (AVException.ErrorCode)code;
|
||||
throw new AVException(ec, error);
|
||||
}
|
||||
return new Tuple<HttpStatusCode, IDictionary<string, object>>(response.Item1,
|
||||
contentJson);
|
||||
}
|
||||
return new Tuple<HttpStatusCode, IDictionary<string, object>>(response.Item1, null);
|
||||
});
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
private const string revocableSessionTokenTrueValue = "1";
|
||||
private Task<AVCommand> PrepareCommand(AVCommand command)
|
||||
{
|
||||
AVCommand newCommand = new AVCommand(command);
|
||||
|
||||
Task<AVCommand> installationIdTask = installationIdController.GetAsync().ContinueWith(t =>
|
||||
{
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-Installation-Id", t.Result.ToString()));
|
||||
return newCommand;
|
||||
});
|
||||
|
||||
AVClient.Configuration configuration = AVClient.CurrentConfiguration;
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-Id", configuration.ApplicationId));
|
||||
|
||||
long timestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds;
|
||||
if (!string.IsNullOrEmpty(configuration.MasterKey) && AVClient.UseMasterKey)
|
||||
{
|
||||
string sign = MD5.GetMd5String(timestamp + configuration.MasterKey);
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-Sign", string.Format("{0},{1},master", sign, timestamp)));
|
||||
}
|
||||
else
|
||||
{
|
||||
string sign = MD5.GetMd5String(timestamp + configuration.ApplicationKey);
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-Sign", string.Format("{0},{1}", sign, timestamp)));
|
||||
}
|
||||
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-Client-Version", AVClient.VersionString));
|
||||
|
||||
if (!string.IsNullOrEmpty(configuration.VersionInfo.BuildVersion))
|
||||
{
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-App-Build-Version", configuration.VersionInfo.BuildVersion));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(configuration.VersionInfo.DisplayVersion))
|
||||
{
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-App-Display-Version", configuration.VersionInfo.DisplayVersion));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(configuration.VersionInfo.OSVersion))
|
||||
{
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-OS-Version", configuration.VersionInfo.OSVersion));
|
||||
}
|
||||
|
||||
if (AVUser.IsRevocableSessionEnabled)
|
||||
{
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LeanCloud-Revocable-Session", revocableSessionTokenTrueValue));
|
||||
}
|
||||
|
||||
if (configuration.AdditionalHTTPHeaders != null)
|
||||
{
|
||||
var headersDictionary = newCommand.Headers.ToDictionary(kv => kv.Key, kv => kv.Value);
|
||||
foreach (var header in configuration.AdditionalHTTPHeaders)
|
||||
{
|
||||
if (headersDictionary.ContainsKey(header.Key))
|
||||
{
|
||||
headersDictionary[header.Key] = header.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
newCommand.Headers.Add(header);
|
||||
}
|
||||
}
|
||||
newCommand.Headers = headersDictionary.ToList();
|
||||
}
|
||||
|
||||
return installationIdTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IAVCommandRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes <see cref="AVCommand"/> and convert the result into Dictionary.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to be run.</param>
|
||||
/// <param name="uploadProgress">Upload progress callback.</param>
|
||||
/// <param name="downloadProgress">Download progress callback.</param>
|
||||
/// <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);
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Config controller.
|
||||
/// </summary>
|
||||
internal class AVConfigController : IAVConfigController {
|
||||
private readonly IAVCommandRunner commandRunner;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AVConfigController"/> class.
|
||||
/// </summary>
|
||||
public AVConfigController(IAVCommandRunner commandRunner, IStorageController storageController) {
|
||||
this.commandRunner = commandRunner;
|
||||
CurrentConfigController = new AVCurrentConfigController(storageController);
|
||||
}
|
||||
|
||||
public IAVCommandRunner CommandRunner { get; internal set; }
|
||||
public IAVCurrentConfigController CurrentConfigController { get; internal set; }
|
||||
|
||||
public Task<AVConfig> FetchConfigAsync(String sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand("config",
|
||||
method: "GET",
|
||||
sessionToken: sessionToken,
|
||||
data: null);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => {
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new AVConfig(task.Result.Item2);
|
||||
}).OnSuccess(task => {
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
CurrentConfigController.SetCurrentConfigAsync(task.Result);
|
||||
return task;
|
||||
}).Unwrap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// LeanCloud current config controller.
|
||||
/// </summary>
|
||||
internal class AVCurrentConfigController : IAVCurrentConfigController {
|
||||
private const string CurrentConfigKey = "CurrentConfig";
|
||||
|
||||
private readonly TaskQueue taskQueue;
|
||||
private AVConfig currentConfig;
|
||||
|
||||
private IStorageController storageController;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LeanCloud.Storage.Internal.AVCurrentConfigController"/> class.
|
||||
/// </summary>
|
||||
public AVCurrentConfigController(IStorageController storageController) {
|
||||
this.storageController = storageController;
|
||||
|
||||
taskQueue = new TaskQueue();
|
||||
}
|
||||
|
||||
public Task<AVConfig> GetCurrentConfigAsync() {
|
||||
return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => {
|
||||
if (currentConfig == null) {
|
||||
return storageController.LoadAsync().OnSuccess(t => {
|
||||
object tmp;
|
||||
t.Result.TryGetValue(CurrentConfigKey, out tmp);
|
||||
|
||||
string propertiesString = tmp as string;
|
||||
if (propertiesString != null) {
|
||||
var dictionary = AVClient.DeserializeJsonString(propertiesString);
|
||||
currentConfig = new AVConfig(dictionary);
|
||||
} else {
|
||||
currentConfig = new AVConfig();
|
||||
}
|
||||
|
||||
return currentConfig;
|
||||
});
|
||||
}
|
||||
|
||||
return Task.FromResult(currentConfig);
|
||||
}), CancellationToken.None).Unwrap();
|
||||
}
|
||||
|
||||
public Task SetCurrentConfigAsync(AVConfig config) {
|
||||
return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => {
|
||||
currentConfig = config;
|
||||
|
||||
var jsonObject = ((IJsonConvertible)config).ToJSON();
|
||||
var jsonString = AVClient.SerializeJsonString(jsonObject);
|
||||
|
||||
return storageController.LoadAsync().OnSuccess(t => t.Result.AddAsync(CurrentConfigKey, jsonString));
|
||||
}).Unwrap().Unwrap(), CancellationToken.None);
|
||||
}
|
||||
|
||||
public Task ClearCurrentConfigAsync() {
|
||||
return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => {
|
||||
currentConfig = null;
|
||||
|
||||
return storageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync(CurrentConfigKey));
|
||||
}).Unwrap().Unwrap(), CancellationToken.None);
|
||||
}
|
||||
|
||||
public Task ClearCurrentConfigInMemoryAsync() {
|
||||
return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => {
|
||||
currentConfig = null;
|
||||
}), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IAVConfigController {
|
||||
/// <summary>
|
||||
/// Gets the current config controller.
|
||||
/// </summary>
|
||||
/// <value>The current config controller.</value>
|
||||
IAVCurrentConfigController CurrentConfigController { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the config from the server asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>The config async.</returns>
|
||||
/// <param name="sessionToken">Session token.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<AVConfig> FetchConfigAsync(string sessionToken, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IAVCurrentConfigController {
|
||||
/// <summary>
|
||||
/// Gets the current config async.
|
||||
/// </summary>
|
||||
/// <returns>The current config async.</returns>
|
||||
Task<AVConfig> GetCurrentConfigAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current config async.
|
||||
/// </summary>
|
||||
/// <returns>The current config async.</returns>
|
||||
/// <param name="config">Config.</param>
|
||||
Task SetCurrentConfigAsync(AVConfig config);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current config async.
|
||||
/// </summary>
|
||||
/// <returns>The current config async.</returns>
|
||||
Task ClearCurrentConfigAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current config in memory async.
|
||||
/// </summary>
|
||||
/// <returns>The current config in memory async.</returns>
|
||||
Task ClearCurrentConfigInMemoryAsync();
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the internal Unity dispatcher used by the LeanCloud SDK.
|
||||
///
|
||||
/// It should be initialized once in your game, usually via AVInitializeBehavior.
|
||||
///
|
||||
/// In certain, advanced use-cases, you may wish to use
|
||||
/// this to set up your dispatcher manually.
|
||||
/// </summary>
|
||||
// TODO: (richardross) Review this interface before going public.
|
||||
public sealed class Dispatcher
|
||||
{
|
||||
static Dispatcher()
|
||||
{
|
||||
Instance = new Dispatcher();
|
||||
}
|
||||
|
||||
private Dispatcher()
|
||||
{
|
||||
DispatcherCoroutine = CreateDispatcherCoroutine();
|
||||
}
|
||||
|
||||
public static Dispatcher Instance { get; private set; }
|
||||
|
||||
public GameObject GameObject { get; set; }
|
||||
public IEnumerator DispatcherCoroutine { get; private set; }
|
||||
|
||||
private readonly ReaderWriterLockSlim dispatchQueueLock = new ReaderWriterLockSlim();
|
||||
private readonly Queue<Action> dispatchQueue = new Queue<Action>();
|
||||
|
||||
public void Post(Action action)
|
||||
{
|
||||
if (dispatchQueueLock.IsWriteLockHeld)
|
||||
{
|
||||
dispatchQueue.Enqueue(action);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchQueueLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
dispatchQueue.Enqueue(action);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dispatchQueueLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator CreateDispatcherCoroutine()
|
||||
{
|
||||
// We must stop the first invocation here, so that we don't actually do anything until we begin looping.
|
||||
yield return null;
|
||||
while (true)
|
||||
{
|
||||
dispatchQueueLock.EnterUpgradeableReadLock();
|
||||
try
|
||||
{
|
||||
// We'll only empty what's already in the dispatch queue in this iteration (so that a
|
||||
// nested dispatch behaves like nextTick()).
|
||||
int count = dispatchQueue.Count;
|
||||
if (count > 0)
|
||||
{
|
||||
dispatchQueueLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
while (count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
dispatchQueue.Dequeue()();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// If an exception occurs, catch it and log it so that dispatches aren't broken.
|
||||
Debug.LogException(e);
|
||||
}
|
||||
count--;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
dispatchQueueLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
dispatchQueueLock.ExitUpgradeableReadLock();
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
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 AVDecoder.ParseDate(obj as string);
|
||||
});
|
||||
DateTime? updatedAt = extractFromDictionary<DateTime?>(mutableData, "updatedAt", (obj) =>
|
||||
{
|
||||
return AVDecoder.ParseDate(obj as string);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System.Net;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// AVF ile controller.
|
||||
/// </summary>
|
||||
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) {
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
/// <summary>
|
||||
/// Saves the async.
|
||||
/// </summary>
|
||||
/// <returns>The async.</returns>
|
||||
/// <param name="state">State.</param>
|
||||
/// <param name="dataStream">Data stream.</param>
|
||||
/// <param name="sessionToken">Session token.</param>
|
||||
/// <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) {
|
||||
if (state.Url != null) {
|
||||
// !isDirty
|
||||
return Task<FileState>.FromResult(state);
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested) {
|
||||
var tcs = new TaskCompletionSource<FileState>();
|
||||
tcs.TrySetCanceled();
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
var oldPosition = dataStream.Position;
|
||||
var command = new AVCommand("files/" + state.Name,
|
||||
method: "POST",
|
||||
sessionToken: sessionToken,
|
||||
contentType: state.MimeType,
|
||||
stream: dataStream);
|
||||
|
||||
return commandRunner.RunCommandAsync(command,
|
||||
uploadProgress: progress,
|
||||
cancellationToken: cancellationToken).OnSuccess(uploadTask => {
|
||||
var result = uploadTask.Result;
|
||||
var jsonData = result.Item2;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
return new FileState {
|
||||
Name = jsonData["name"] as string,
|
||||
Url = new Uri(jsonData["url"] as string, UriKind.Absolute),
|
||||
MimeType = state.MimeType
|
||||
};
|
||||
}).ContinueWith(t => {
|
||||
// Rewind the stream on failure or cancellation (if possible)
|
||||
if ((t.IsFaulted || t.IsCanceled) && dataStream.CanSeek) {
|
||||
dataStream.Seek(oldPosition, SeekOrigin.Begin);
|
||||
}
|
||||
return t;
|
||||
}).Unwrap();
|
||||
}
|
||||
public Task DeleteAsync(FileState state, string sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand("files/" + state.ObjectId,
|
||||
method: "DELETE",
|
||||
sessionToken: sessionToken,
|
||||
data: null);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, 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;
|
||||
IDictionary<string, object> parameters = new Dictionary<string, object>();
|
||||
parameters.Add("name", str);
|
||||
parameters.Add("key", GetUniqueName(fileState));
|
||||
parameters.Add("__type", "File");
|
||||
parameters.Add("mime_type", AVFile.GetMIMEType(str));
|
||||
parameters.Add("metaData", fileState.MetaData);
|
||||
|
||||
rtn = AVClient.RequestAsync("POST", new Uri("fileTokens", UriKind.Relative), currentSessionToken, parameters, cancellationToken);
|
||||
|
||||
return rtn;
|
||||
}
|
||||
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(_ => {
|
||||
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 fileState) {
|
||||
string key = Random(12);
|
||||
string extension = Path.GetExtension(fileState.Name);
|
||||
key += extension;
|
||||
fileState.CloudName = key;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
internal class AWSS3FileController : AVFileController
|
||||
{
|
||||
|
||||
private object mutex = new object();
|
||||
|
||||
|
||||
public AWSS3FileController(IAVCommandRunner commandRunner) : base(commandRunner)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override Task<FileState> SaveAsync(FileState state, Stream dataStream, string sessionToken, IProgress<AVUploadProgressEventArgs> progress, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (state.Url != null)
|
||||
{
|
||||
return Task.FromResult(state);
|
||||
}
|
||||
|
||||
return GetFileToken(state, cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var fileToken = t.Result.Item2;
|
||||
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);
|
||||
|
||||
}).Unwrap().OnSuccess(s =>
|
||||
{
|
||||
return s.Result;
|
||||
});
|
||||
}
|
||||
|
||||
internal Task<FileState> PutFile(FileState state, string uploadUrl, Stream dataStream)
|
||||
{
|
||||
IList<KeyValuePair<string, string>> makeBlockHeaders = new List<KeyValuePair<string, string>>();
|
||||
makeBlockHeaders.Add(new KeyValuePair<string, string>("Content-Type", state.MimeType));
|
||||
|
||||
return AVClient.RequestAsync(new Uri(uploadUrl), "PUT", makeBlockHeaders, dataStream, state.MimeType, CancellationToken.None).OnSuccess(t =>
|
||||
{
|
||||
return state;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IAVFileController
|
||||
{
|
||||
Task<FileState> SaveAsync(FileState state,
|
||||
Stream dataStream,
|
||||
string sessionToken,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task DeleteAsync(FileState state,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<FileState> GetAsync(string objectId,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -1,250 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
internal class QCloudCosFileController : AVFileController
|
||||
{
|
||||
private object mutex = new object();
|
||||
|
||||
FileState fileState;
|
||||
Stream data;
|
||||
string bucket;
|
||||
string token;
|
||||
string uploadUrl;
|
||||
bool done;
|
||||
private long sliceSize = (long)CommonSize.KB512;
|
||||
|
||||
public QCloudCosFileController(IAVCommandRunner commandRunner) : base(commandRunner)
|
||||
{
|
||||
}
|
||||
|
||||
public Task<FileState> SaveAsync(FileState state,
|
||||
Stream dataStream,
|
||||
string sessionToken,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (state.Url != null)
|
||||
{
|
||||
return Task<FileState>.FromResult(state);
|
||||
}
|
||||
fileState = state;
|
||||
data = dataStream;
|
||||
return GetFileToken(fileState, cancellationToken).OnSuccess(_ =>
|
||||
{
|
||||
var fileToken = _.Result.Item2;
|
||||
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();
|
||||
|
||||
}).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);
|
||||
}
|
||||
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 rtn = AVClient.RequestAsync(new Uri(this.uploadUrl), "POST", sliceHeaders, tempStream, null, cancellationToken).OnSuccess(_ =>
|
||||
{
|
||||
var dic = AVClient.ReponseResolve(_.Result, CancellationToken.None);
|
||||
|
||||
return dic;
|
||||
});
|
||||
|
||||
return rtn;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,332 +0,0 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
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 QiniuFileController : AVFileController
|
||||
{
|
||||
private static int BLOCKSIZE = 1024 * 1024 * 4;
|
||||
private const int blockMashk = (1 << blockBits) - 1;
|
||||
private const int blockBits = 22;
|
||||
private int CalcBlockCount(long fsize)
|
||||
{
|
||||
return (int)((fsize + blockMashk) >> blockBits);
|
||||
}
|
||||
internal static string UP_HOST = "https://up.qbox.me";
|
||||
private object mutex = new object();
|
||||
|
||||
public QiniuFileController(IAVCommandRunner commandRunner) : base(commandRunner)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override Task<FileState> SaveAsync(FileState state,
|
||||
Stream dataStream,
|
||||
String sessionToken,
|
||||
IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (state.Url != null)
|
||||
{
|
||||
return Task<FileState>.FromResult(state);
|
||||
}
|
||||
state.frozenData = dataStream;
|
||||
state.CloudName = GetUniqueName(state);
|
||||
return GetQiniuToken(state, CancellationToken.None).ContinueWith(t =>
|
||||
{
|
||||
MergeFromJSON(state, t.Result.Item2);
|
||||
return UploadNextChunk(state, dataStream, string.Empty, 0, progress);
|
||||
}).Unwrap().OnSuccess<FileState>(s =>
|
||||
{
|
||||
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);
|
||||
|
||||
}
|
||||
else if (state.completed % BLOCKSIZE == 0)
|
||||
{
|
||||
var firstChunkBinary = GetChunkBinary(state.completed, dataStream);
|
||||
|
||||
var blockSize = remainingSize > BLOCKSIZE ? BLOCKSIZE : remainingSize;
|
||||
return MakeBlock(state, firstChunkBinary, blockSize).ContinueWith(t =>
|
||||
{
|
||||
|
||||
var dic = AVClient.ReponseResolve(t.Result, CancellationToken.None);
|
||||
var ctx = dic.Item2["ctx"].ToString();
|
||||
|
||||
offset = long.Parse(dic.Item2["offset"].ToString());
|
||||
var host = dic.Item2["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();
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var chunkBinary = GetChunkBinary(state.completed, dataStream);
|
||||
return PutChunk(state, chunkBinary, context, offset).ContinueWith(t =>
|
||||
{
|
||||
var dic = AVClient.ReponseResolve(t.Result, CancellationToken.None);
|
||||
var ctx = dic.Item2["ctx"].ToString();
|
||||
|
||||
offset = long.Parse(dic.Item2["offset"].ToString());
|
||||
var host = dic.Item2["host"].ToString();
|
||||
state.completed += chunkBinary.Length;
|
||||
if (state.completed % BLOCKSIZE == 0 || state.completed == totalSize)
|
||||
{
|
||||
state.block_ctxes.Add(ctx);
|
||||
}
|
||||
//if (AVClient.fileUploaderDebugLog)
|
||||
//{
|
||||
// AVClient.LogTracker(state.counter + "|completed=" + state.completed + "stream:position=" + dataStream.Position + "|");
|
||||
//}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
internal string GetUniqueName(FileState state)
|
||||
{
|
||||
string key = Guid.NewGuid().ToString();//file Key in Qiniu.
|
||||
string extension = Path.GetExtension(state.Name);
|
||||
key += extension;
|
||||
return key;
|
||||
}
|
||||
internal Task<Tuple<HttpStatusCode, IDictionary<string, object>>> GetQiniuToken(FileState state, CancellationToken cancellationToken)
|
||||
{
|
||||
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> rtn;
|
||||
string currentSessionToken = AVUser.CurrentSessionToken;
|
||||
string str = state.Name;
|
||||
|
||||
IDictionary<string, object> parameters = new Dictionary<string, object>();
|
||||
parameters.Add("name", str);
|
||||
parameters.Add("key", state.CloudName);
|
||||
parameters.Add("__type", "File");
|
||||
parameters.Add("mime_type", AVFile.GetMIMEType(str));
|
||||
|
||||
state.MetaData = GetMetaData(state, state.frozenData);
|
||||
|
||||
parameters.Add("metaData", state.MetaData);
|
||||
|
||||
rtn = AVClient.RequestAsync("POST", new Uri("qiniu", UriKind.Relative), currentSessionToken, parameters, cancellationToken);
|
||||
|
||||
return rtn;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
Task<Tuple<HttpStatusCode, string>> MakeBlock(FileState state, byte[] firstChunkBinary, long blcokSize = 4194304)
|
||||
{
|
||||
MemoryStream firstChunkData = new MemoryStream(firstChunkBinary, 0, firstChunkBinary.Length);
|
||||
return AVClient.RequestAsync(new Uri(new Uri(UP_HOST) + string.Format("mkblk/{0}", blcokSize)), "POST", GetQiniuRequestHeaders(state), firstChunkData, "application/octet-stream", CancellationToken.None);
|
||||
}
|
||||
Task<Tuple<HttpStatusCode, string>> PutChunk(FileState state, byte[] chunkBinary, string LastChunkctx, long currentChunkOffsetInBlock)
|
||||
{
|
||||
MemoryStream chunkData = new MemoryStream(chunkBinary, 0, chunkBinary.Length);
|
||||
return AVClient.RequestAsync(new Uri(new Uri(UP_HOST) + string.Format("bput/{0}/{1}", LastChunkctx,
|
||||
currentChunkOffsetInBlock)), "POST",
|
||||
GetQiniuRequestHeaders(state), chunkData,
|
||||
"application/octet-stream", CancellationToken.None);
|
||||
}
|
||||
internal 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());
|
||||
|
||||
IList<KeyValuePair<string, string>> headers = new List<KeyValuePair<string, string>>();
|
||||
//makeBlockDic.Add("Content-Type", "application/octet-stream");
|
||||
|
||||
string authHead = "UpToken " + upToken;
|
||||
headers.Add(new KeyValuePair<string, string>("Authorization", authHead));
|
||||
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 rtn = AVClient.RequestAsync(new Uri(urlBuilder.ToString()), "POST", headers, body, "text/plain", cancellationToken).OnSuccess(_ =>
|
||||
{
|
||||
var dic = AVClient.ReponseResolve(_.Result, CancellationToken.None);
|
||||
return _.Result;
|
||||
});
|
||||
return rtn;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,566 +0,0 @@
|
|||
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;
|
||||
static private byte[] PADDING = new byte[] {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,495 +0,0 @@
|
|||
//
|
||||
// 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();
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
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>();
|
||||
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// <code>IHttpRequest</code> is an interface that provides an API to execute HTTP request data.
|
||||
/// </summary>
|
||||
public class HttpRequest
|
||||
{
|
||||
public Uri Uri { get; set; }
|
||||
public IList<KeyValuePair<string, string>> Headers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Data stream to be uploaded.
|
||||
/// </summary>
|
||||
public virtual Stream Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// HTTP method. One of <c>DELETE</c>, <c>GET</c>, <c>HEAD</c>, <c>POST</c> or <c>PUT</c>
|
||||
/// </summary>
|
||||
public string Method { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IHttpClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes HTTP request to a <see cref="HttpRequest.Uri"/> with <see cref="HttpRequest.Method"/> HTTP verb
|
||||
/// and <see cref="HttpRequest.Headers"/>.
|
||||
/// </summary>
|
||||
/// <param name="httpRequest">The HTTP request to be executed.</param>
|
||||
/// <param name="uploadProgress">Upload progress callback.</param>
|
||||
/// <param name="downloadProgress">Download progress callback.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that resolves to Htt</returns>
|
||||
Task<Tuple<HttpStatusCode, string>> ExecuteAsync(HttpRequest httpRequest,
|
||||
IProgress<AVUploadProgressEventArgs> uploadProgress,
|
||||
IProgress<AVDownloadProgressEventArgs> downloadProgress,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System.Linq;
|
||||
|
||||
using NetHttpClient = System.Net.Http.HttpClient;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class HttpClient : IHttpClient
|
||||
{
|
||||
private static HashSet<string> HttpContentHeaders = new HashSet<string> {
|
||||
{ "Allow" },
|
||||
{ "Content-Disposition" },
|
||||
{ "Content-Encoding" },
|
||||
{ "Content-Language" },
|
||||
{ "Content-Length" },
|
||||
{ "Content-Location" },
|
||||
{ "Content-MD5" },
|
||||
{ "Content-Range" },
|
||||
{ "Content-Type" },
|
||||
{ "Expires" },
|
||||
{ "Last-Modified" }
|
||||
};
|
||||
|
||||
public HttpClient()
|
||||
{
|
||||
client = new NetHttpClient();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "LeanCloud-dotNet-SDK/" + "2.0.0");
|
||||
}
|
||||
|
||||
public HttpClient(NetHttpClient client)
|
||||
{
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
private NetHttpClient client;
|
||||
|
||||
public Task<Tuple<HttpStatusCode, string>> ExecuteAsync(HttpRequest httpRequest,
|
||||
IProgress<AVUploadProgressEventArgs> uploadProgress,
|
||||
IProgress<AVDownloadProgressEventArgs> downloadProgress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
uploadProgress = uploadProgress ?? new Progress<AVUploadProgressEventArgs>();
|
||||
downloadProgress = downloadProgress ?? new Progress<AVDownloadProgressEventArgs>();
|
||||
|
||||
HttpMethod httpMethod = new HttpMethod(httpRequest.Method);
|
||||
HttpRequestMessage message = new HttpRequestMessage(httpMethod, httpRequest.Uri);
|
||||
|
||||
// Fill in zero-length data if method is post.
|
||||
Stream data = httpRequest.Data;
|
||||
if (httpRequest.Data == null && httpRequest.Method.ToLower().Equals("post"))
|
||||
{
|
||||
data = new MemoryStream(new byte[0]);
|
||||
}
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
message.Content = new StreamContent(data);
|
||||
}
|
||||
|
||||
if (httpRequest.Headers != null)
|
||||
{
|
||||
foreach (var header in httpRequest.Headers)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(header.Value))
|
||||
{
|
||||
if (HttpContentHeaders.Contains(header.Key))
|
||||
{
|
||||
message.Content.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid aggressive caching on Windows Phone 8.1.
|
||||
message.Headers.Add("Cache-Control", "no-cache");
|
||||
message.Headers.IfModifiedSince = DateTimeOffset.UtcNow;
|
||||
|
||||
// TODO: (richardross) investigate progress here, maybe there's something we're missing in order to support this.
|
||||
uploadProgress.Report(new AVUploadProgressEventArgs { Progress = 0 });
|
||||
|
||||
return client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
||||
.ContinueWith(httpMessageTask =>
|
||||
{
|
||||
var response = httpMessageTask.Result;
|
||||
|
||||
uploadProgress.Report(new AVUploadProgressEventArgs { Progress = 1 });
|
||||
|
||||
return response.Content.ReadAsStreamAsync().ContinueWith(streamTask =>
|
||||
{
|
||||
var resultStream = new MemoryStream();
|
||||
var responseStream = streamTask.Result;
|
||||
|
||||
int bufferSize = 4096;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
int bytesRead = 0;
|
||||
long totalLength = -1;
|
||||
long readSoFar = 0;
|
||||
|
||||
try
|
||||
{
|
||||
totalLength = responseStream.Length;
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
return InternalExtensions.WhileAsync(() =>
|
||||
{
|
||||
return responseStream.ReadAsync(buffer, 0, bufferSize, cancellationToken).OnSuccess(readTask =>
|
||||
{
|
||||
bytesRead = readTask.Result;
|
||||
return bytesRead > 0;
|
||||
});
|
||||
}, () =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
return resultStream.WriteAsync(buffer, 0, bytesRead, cancellationToken).OnSuccess(_ =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
readSoFar += bytesRead;
|
||||
|
||||
if (totalLength > -1)
|
||||
{
|
||||
downloadProgress.Report(new AVDownloadProgressEventArgs { Progress = 1.0 * readSoFar / totalLength });
|
||||
}
|
||||
});
|
||||
}).ContinueWith(_ =>
|
||||
{
|
||||
responseStream.Dispose();
|
||||
return _;
|
||||
}).Unwrap().OnSuccess(_ =>
|
||||
{
|
||||
// If getting stream size is not supported, then report download only once.
|
||||
if (totalLength == -1)
|
||||
{
|
||||
downloadProgress.Report(new AVDownloadProgressEventArgs { Progress = 1.0 });
|
||||
}
|
||||
|
||||
// Assume UTF-8 encoding.
|
||||
var resultAsArray = resultStream.ToArray();
|
||||
var resultString = Encoding.UTF8.GetString(resultAsArray, 0, resultAsArray.Length);
|
||||
resultStream.Dispose();
|
||||
return new Tuple<HttpStatusCode, string>(response.StatusCode, resultString);
|
||||
});
|
||||
});
|
||||
}).Unwrap().Unwrap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class HttpClient : IHttpClient
|
||||
{
|
||||
private static bool isCompiledByIL2CPP = System.AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain");
|
||||
|
||||
public Task<Tuple<HttpStatusCode, string>> ExecuteAsync(HttpRequest httpRequest,
|
||||
IProgress<AVUploadProgressEventArgs> uploadProgress,
|
||||
IProgress<AVDownloadProgressEventArgs> downloadProgress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<Tuple<HttpStatusCode, string>>();
|
||||
cancellationToken.Register(() => tcs.TrySetCanceled());
|
||||
uploadProgress = uploadProgress ?? new Progress<AVUploadProgressEventArgs>();
|
||||
downloadProgress = downloadProgress ?? new Progress<AVDownloadProgressEventArgs>();
|
||||
|
||||
Task readBytesTask = null;
|
||||
IDisposable toDisposeAfterReading = null;
|
||||
byte[] bytes = null;
|
||||
if (httpRequest.Data != null)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
toDisposeAfterReading = ms;
|
||||
readBytesTask = httpRequest.Data.CopyToAsync(ms).OnSuccess(_ =>
|
||||
{
|
||||
bytes = ms.ToArray();
|
||||
});
|
||||
}
|
||||
|
||||
readBytesTask.Safe().ContinueWith(t =>
|
||||
{
|
||||
if (toDisposeAfterReading != null)
|
||||
{
|
||||
toDisposeAfterReading.Dispose();
|
||||
}
|
||||
return t;
|
||||
}).Unwrap().OnSuccess(_ =>
|
||||
{
|
||||
float oldDownloadProgress = 0;
|
||||
float oldUploadProgress = 0;
|
||||
|
||||
Dispatcher.Instance.Post(() =>
|
||||
{
|
||||
WaitForWebRequest(GenerateRequest(httpRequest, bytes), request =>
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
return;
|
||||
}
|
||||
if (request.isDone)
|
||||
{
|
||||
uploadProgress.Report(new AVUploadProgressEventArgs { Progress = 1 });
|
||||
downloadProgress.Report(new AVDownloadProgressEventArgs { Progress = 1 });
|
||||
|
||||
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))
|
||||
{
|
||||
var errorString = string.Format("{{\"error\":\"{0}\"}}", request.error);
|
||||
tcs.TrySetResult(new Tuple<HttpStatusCode, string>(statusCode, errorString));
|
||||
}
|
||||
else
|
||||
{
|
||||
tcs.TrySetResult(new Tuple<HttpStatusCode, string>(statusCode, request.downloadHandler.text));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update upload progress
|
||||
var newUploadProgress = request.uploadProgress;
|
||||
if (oldUploadProgress < newUploadProgress)
|
||||
{
|
||||
uploadProgress.Report(new AVUploadProgressEventArgs { Progress = newUploadProgress });
|
||||
}
|
||||
oldUploadProgress = newUploadProgress;
|
||||
|
||||
// Update download progress
|
||||
var newDownloadProgress = request.downloadProgress;
|
||||
if (oldDownloadProgress < newDownloadProgress)
|
||||
{
|
||||
downloadProgress.Report(new AVDownloadProgressEventArgs { Progress = newDownloadProgress });
|
||||
}
|
||||
oldDownloadProgress = newDownloadProgress;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Get off of the main thread for further processing.
|
||||
return tcs.Task.ContinueWith(t =>
|
||||
{
|
||||
var dispatchTcs = new TaskCompletionSource<object>();
|
||||
// ThreadPool doesn't work well in IL2CPP environment, but Thread does!
|
||||
if (isCompiledByIL2CPP)
|
||||
{
|
||||
var thread = new Thread(_ =>
|
||||
{
|
||||
dispatchTcs.TrySetResult(null);
|
||||
});
|
||||
thread.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(_ => dispatchTcs.TrySetResult(null));
|
||||
}
|
||||
return dispatchTcs.Task;
|
||||
}).Unwrap()
|
||||
.ContinueWith(_ => tcs.Task).Unwrap();
|
||||
}
|
||||
|
||||
private static HttpStatusCode GetResponseStatusCode(UnityWebRequest request)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(HttpStatusCode), (int)request.responseCode))
|
||||
{
|
||||
return (HttpStatusCode)request.responseCode;
|
||||
}
|
||||
return (HttpStatusCode)400;
|
||||
}
|
||||
|
||||
private static UnityWebRequest GenerateRequest(HttpRequest request, byte[] bytes)
|
||||
{
|
||||
var webRequest = new UnityWebRequest();
|
||||
webRequest.method = request.Method;
|
||||
webRequest.url = request.Uri.AbsoluteUri;
|
||||
// Explicitly assume a JSON content.
|
||||
webRequest.SetRequestHeader("Content-Type", "application/json");
|
||||
//webRequest.SetRequestHeader("User-Agent", "net-unity-" + AVVersionInfo.Version);
|
||||
if (request.Headers != null)
|
||||
{
|
||||
foreach (var header in request.Headers)
|
||||
{
|
||||
webRequest.SetRequestHeader(header.Key as string, header.Value as string);
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes != null)
|
||||
{
|
||||
webRequest.uploadHandler = new UploadHandlerRaw(bytes);
|
||||
}
|
||||
webRequest.downloadHandler = new DownloadHandlerBuffer();
|
||||
webRequest.Send();
|
||||
return webRequest;
|
||||
}
|
||||
|
||||
private static void WaitForWebRequest(UnityWebRequest request, Action<UnityWebRequest> action)
|
||||
{
|
||||
Dispatcher.Instance.Post(() =>
|
||||
{
|
||||
var isDone = request.isDone;
|
||||
action(request);
|
||||
if (!isDone)
|
||||
{
|
||||
WaitForWebRequest(request, action);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IAVCorePlugins
|
||||
{
|
||||
void Reset();
|
||||
|
||||
IHttpClient HttpClient { get; }
|
||||
IAppRouterController AppRouterController { get; }
|
||||
IAVCommandRunner CommandRunner { get; }
|
||||
IStorageController StorageController { get; }
|
||||
|
||||
IAVCloudCodeController CloudCodeController { get; }
|
||||
IAVConfigController ConfigController { get; }
|
||||
IAVFileController FileController { get; }
|
||||
IAVObjectController ObjectController { get; }
|
||||
IAVQueryController QueryController { get; }
|
||||
IAVSessionController SessionController { get; }
|
||||
IAVUserController UserController { get; }
|
||||
IObjectSubclassingController SubclassingController { get; }
|
||||
IAVCurrentUserController CurrentUserController { get; }
|
||||
IInstallationIdController InstallationIdController { get; }
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IInstallationIdController {
|
||||
/// <summary>
|
||||
/// Sets current <code>installationId</code> and saves it to local storage.
|
||||
/// </summary>
|
||||
/// <param name="installationId">The <code>installationId</code> to be saved.</param>
|
||||
Task SetAsync(Guid? installationId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets current <code>installationId</code> from local storage. Generates a none exists.
|
||||
/// </summary>
|
||||
/// <returns>Current <code>installationId</code>.</returns>
|
||||
Task<Guid?> GetAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Clears current installationId from memory and local storage.
|
||||
/// </summary>
|
||||
Task ClearAsync();
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class InstallationIdController : IInstallationIdController {
|
||||
private const string InstallationIdKey = "InstallationId";
|
||||
private readonly object mutex = new object();
|
||||
private Guid? installationId;
|
||||
|
||||
private readonly IStorageController storageController;
|
||||
public InstallationIdController(IStorageController storageController) {
|
||||
this.storageController = storageController;
|
||||
}
|
||||
|
||||
public Task SetAsync(Guid? installationId) {
|
||||
lock (mutex) {
|
||||
Task saveTask;
|
||||
|
||||
if (installationId == null) {
|
||||
saveTask = storageController
|
||||
.LoadAsync()
|
||||
.OnSuccess(storage => storage.Result.RemoveAsync(InstallationIdKey))
|
||||
.Unwrap();
|
||||
} else {
|
||||
saveTask = storageController
|
||||
.LoadAsync()
|
||||
.OnSuccess(storage => storage.Result.AddAsync(InstallationIdKey, installationId.ToString()))
|
||||
.Unwrap();
|
||||
}
|
||||
this.installationId = installationId;
|
||||
return saveTask;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Guid?> GetAsync() {
|
||||
lock (mutex) {
|
||||
if (installationId != null) {
|
||||
return Task.FromResult(installationId);
|
||||
}
|
||||
}
|
||||
|
||||
return storageController
|
||||
.LoadAsync()
|
||||
.OnSuccess<IStorageDictionary<string, object>, Task<Guid?>>(s => {
|
||||
object id;
|
||||
s.Result.TryGetValue(InstallationIdKey, out id);
|
||||
try {
|
||||
lock (mutex) {
|
||||
installationId = new Guid((string)id);
|
||||
return Task.FromResult(installationId);
|
||||
}
|
||||
} catch (Exception) {
|
||||
var newInstallationId = Guid.NewGuid();
|
||||
return SetAsync(newInstallationId).OnSuccess<Guid?>(_ => newInstallationId);
|
||||
}
|
||||
})
|
||||
.Unwrap();
|
||||
}
|
||||
|
||||
public Task ClearAsync() {
|
||||
return SetAsync(null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Utilities;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVObjectController : IAVObjectController
|
||||
{
|
||||
private readonly IAVCommandRunner commandRunner;
|
||||
|
||||
public AVObjectController(IAVCommandRunner commandRunner)
|
||||
{
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
|
||||
public Task<IObjectState> FetchAsync(IObjectState state,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand(string.Format("classes/{0}/{1}",
|
||||
Uri.EscapeDataString(state.ClassName),
|
||||
Uri.EscapeDataString(state.ObjectId)),
|
||||
method: "GET",
|
||||
sessionToken: sessionToken,
|
||||
data: null);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance);
|
||||
});
|
||||
}
|
||||
|
||||
public Task<IObjectState> FetchAsync(IObjectState state,
|
||||
IDictionary<string, object> queryString,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand(string.Format("classes/{0}/{1}?{2}",
|
||||
Uri.EscapeDataString(state.ClassName),
|
||||
Uri.EscapeDataString(state.ObjectId),
|
||||
AVClient.BuildQueryString(queryString)),
|
||||
method: "GET",
|
||||
sessionToken: sessionToken,
|
||||
data: null);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance);
|
||||
});
|
||||
}
|
||||
|
||||
public Task<IObjectState> SaveAsync(IObjectState state,
|
||||
IDictionary<string, IAVFieldOperation> operations,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var objectJSON = AVObject.ToJSONObjectForSaving(operations);
|
||||
|
||||
var command = new AVCommand((state.ObjectId == null ?
|
||||
string.Format("classes/{0}", Uri.EscapeDataString(state.ClassName)) :
|
||||
string.Format("classes/{0}/{1}", Uri.EscapeDataString(state.ClassName), state.ObjectId)),
|
||||
method: (state.ObjectId == null ? "POST" : "PUT"),
|
||||
sessionToken: sessionToken,
|
||||
data: objectJSON);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance);
|
||||
serverState = serverState.MutatedClone(mutableClone =>
|
||||
{
|
||||
mutableClone.IsNew = t.Result.Item1 == System.Net.HttpStatusCode.Created;
|
||||
});
|
||||
return serverState;
|
||||
});
|
||||
}
|
||||
|
||||
public IList<Task<IObjectState>> SaveAllAsync(IList<IObjectState> states,
|
||||
IList<IDictionary<string, IAVFieldOperation>> operationsList,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
var requests = states
|
||||
.Zip(operationsList, (item, ops) => new AVCommand(
|
||||
item.ObjectId == null
|
||||
? string.Format("classes/{0}", Uri.EscapeDataString(item.ClassName))
|
||||
: string.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)),
|
||||
method: item.ObjectId == null ? "POST" : "PUT",
|
||||
data: AVObject.ToJSONObjectForSaving(ops)))
|
||||
.ToList();
|
||||
|
||||
var batchTasks = ExecuteBatchRequests(requests, sessionToken, cancellationToken);
|
||||
var stateTasks = new List<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,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand(string.Format("classes/{0}/{1}",
|
||||
state.ClassName, state.ObjectId),
|
||||
method: "DELETE",
|
||||
sessionToken: sessionToken,
|
||||
data: null);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public IList<Task> DeleteAllAsync(IList<IObjectState> states,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var requests = states
|
||||
.Where(item => item.ObjectId != null)
|
||||
.Select(item => new AVCommand(
|
||||
string.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)),
|
||||
method: "DELETE",
|
||||
data: null))
|
||||
.ToList();
|
||||
return ExecuteBatchRequests(requests, sessionToken, cancellationToken).Cast<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,
|
||||
string sessionToken,
|
||||
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, sessionToken, cancellationToken));
|
||||
|
||||
batchSize = remaining.Count();
|
||||
}
|
||||
tasks.AddRange(ExecuteBatchRequest(remaining.ToList(), sessionToken, cancellationToken));
|
||||
|
||||
return tasks;
|
||||
}
|
||||
|
||||
private IList<Task<IDictionary<string, object>>> ExecuteBatchRequest(IList<AVCommand> requests,
|
||||
string sessionToken,
|
||||
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.Uri.AbsolutePath },
|
||||
};
|
||||
|
||||
if (r.DataObject != null)
|
||||
{
|
||||
results["body"] = r.DataObject;
|
||||
}
|
||||
return results;
|
||||
}).Cast<object>().ToList();
|
||||
var command = new AVCommand("batch",
|
||||
method: "POST",
|
||||
sessionToken: sessionToken,
|
||||
data: new Dictionary<string, object> { { "requests", encodedRequests } });
|
||||
|
||||
commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted || t.IsCanceled)
|
||||
{
|
||||
foreach (var tcs in tcss)
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
tcs.TrySetException(t.Exception);
|
||||
}
|
||||
else if (t.IsCanceled)
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var resultsArray = Conversion.As<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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IAVObjectController
|
||||
{
|
||||
//Task<IObjectState> FetchAsync(IObjectState state,
|
||||
// string sessionToken,
|
||||
// CancellationToken cancellationToken);
|
||||
|
||||
Task<IObjectState> FetchAsync(IObjectState state,
|
||||
IDictionary<string,object> queryString,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<IObjectState> SaveAsync(IObjectState state,
|
||||
IDictionary<string, IAVFieldOperation> operations,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
IList<Task<IObjectState>> SaveAllAsync(IList<IObjectState> states,
|
||||
IList<IDictionary<string, IAVFieldOperation>> operationsList,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task DeleteAsync(IObjectState state,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
IList<Task> DeleteAllAsync(IList<IObjectState> states,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// <code>IAVObjectCurrentController</code> controls the single-instance <see cref="AVObject"/>
|
||||
/// persistence used throughout the code-base. Sample usages are <see cref="AVUser.CurrentUser"/> and
|
||||
/// <see cref="AVInstallation.CurrentInstallation"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of object being persisted.</typeparam>
|
||||
public interface IAVObjectCurrentController<T> where T : AVObject {
|
||||
/// <summary>
|
||||
/// Persists current <see cref="AVObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="obj"><see cref="AVObject"/> to be persisted.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task SetAsync(T obj, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the persisted current <see cref="AVObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task<T> GetAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Task"/> that resolves to <code>true</code> if current
|
||||
/// <see cref="AVObject"/> exists.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task<bool> ExistsAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <code>true</code> if the given <see cref="AVObject"/> is the persisted current
|
||||
/// <see cref="AVObject"/>.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to check.</param>
|
||||
/// <returns>True if <code>obj</code> is the current persisted <see cref="AVObject"/>.</returns>
|
||||
bool IsCurrent(T obj);
|
||||
|
||||
/// <summary>
|
||||
/// Nullifies the current <see cref="AVObject"/> from memory.
|
||||
/// </summary>
|
||||
void ClearFromMemory();
|
||||
|
||||
/// <summary>
|
||||
/// Clears current <see cref="AVObject"/> from disk.
|
||||
/// </summary>
|
||||
void ClearFromDisk();
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IObjectSubclassingController
|
||||
{
|
||||
String GetClassName(Type type);
|
||||
Type GetType(String className);
|
||||
|
||||
bool IsTypeValid(String className, Type type);
|
||||
|
||||
void RegisterSubclass(Type t);
|
||||
void UnregisterSubclass(Type t);
|
||||
|
||||
void AddRegisterHook(Type t, Action action);
|
||||
|
||||
AVObject Instantiate(String className);
|
||||
IDictionary<String, String> GetPropertyMappings(String className);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue