chore: 拷贝 pcl 工程;重构 http 请求逻辑
parent
8a7994a33d
commit
19c1975880
|
@ -0,0 +1,31 @@
|
|||
using NUnit.Framework;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LeanCloud;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class ObjectControllerTests {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
AVClient.Initialize(new AVClient.Configuration {
|
||||
ApplicationId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz",
|
||||
ApplicationKey = "pbf6Nk5seyjilexdpyrPwjSp",
|
||||
});
|
||||
AVClient.HttpLog(TestContext.Out.WriteLine);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[AsyncStateMachine(typeof(ObjectControllerTests))]
|
||||
public async Task TestSave() {
|
||||
TestContext.Out.WriteLine($"before at {Thread.CurrentThread.ManagedThreadId}");
|
||||
var obj = AVObject.Create("Foo");
|
||||
obj["content"] = "hello, world";
|
||||
await obj.SaveAsync();
|
||||
TestContext.Out.WriteLine($"saved at {Thread.CurrentThread.ManagedThreadId}");
|
||||
Assert.NotNull(obj.ObjectId);
|
||||
Assert.NotNull(obj.CreatedAt);
|
||||
Assert.NotNull(obj.UpdatedAt);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using NUnit.Framework;
|
||||
using LeanCloud;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LeanCloudTests {
|
||||
public class ObjectTests {
|
||||
[SetUp]
|
||||
public void SetUp() {
|
||||
AVClient.Initialize(new AVClient.Configuration {
|
||||
ApplicationId = "BMYV4RKSTwo8WSqt8q9ezcWF-gzGzoHsz",
|
||||
ApplicationKey = "pbf6Nk5seyjilexdpyrPwjSp",
|
||||
RTMServer = "https://router-g0-push.avoscloud.com",
|
||||
});
|
||||
AVClient.HttpLog(TestContext.Out.WriteLine);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAVObjectConstructor() {
|
||||
AVObject obj = new AVObject("Foo");
|
||||
Assert.AreEqual("Foo", obj.ClassName);
|
||||
Assert.Null(obj.CreatedAt);
|
||||
Assert.True(obj.IsDataAvailable);
|
||||
Assert.True(obj.IsDirty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAVObjectCreate() {
|
||||
AVObject obj = AVObject.CreateWithoutData("Foo", "5d356b1cd5de2b00837162ca");
|
||||
Assert.AreEqual("Foo", obj.ClassName);
|
||||
Assert.AreEqual("5d356b1cd5de2b00837162ca", obj.ObjectId);
|
||||
Assert.Null(obj.CreatedAt);
|
||||
Assert.False(obj.IsDataAvailable);
|
||||
Assert.False(obj.IsDirty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestHttp() {
|
||||
if (SynchronizationContext.Current == null) {
|
||||
TestContext.Out.WriteLine("is null");
|
||||
}
|
||||
TestContext.Out.WriteLine($"current {SynchronizationContext.Current}");
|
||||
var client = new HttpClient();
|
||||
TestContext.Out.WriteLine($"request at {Thread.CurrentThread.ManagedThreadId}");
|
||||
string url = $"{AVClient.CurrentConfiguration.RTMServer}/v1/route?appId={AVClient.CurrentConfiguration.ApplicationId}&secure=1";
|
||||
var res = await client.GetAsync(url);
|
||||
TestContext.Out.WriteLine($"get at {Thread.CurrentThread.ManagedThreadId}");
|
||||
var data = await res.Content.ReadAsStringAsync();
|
||||
res.Dispose();
|
||||
TestContext.Out.WriteLine($"response at {Thread.CurrentThread.ManagedThreadId}");
|
||||
TestContext.Out.WriteLine(data);
|
||||
Assert.Pass();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,48 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\packages\NUnit.3.12.0\build\NUnit.props" Condition="Exists('..\..\packages\NUnit.3.12.0\build\NUnit.props')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{04DA35BB-6473-4D99-8A33-F499D40047E6}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>Storage.Test</RootNamespace>
|
||||
<AssemblyName>Storage.Test</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
<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>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="nunit.framework">
|
||||
<HintPath>..\..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll</HintPath>
|
||||
</Reference>
|
||||
<PackageReference Include="nunit" Version="3.11.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Test.cs" />
|
||||
<ProjectReference Include="..\Storage\Storage.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Storage.PCL\Storage.PCL.csproj">
|
||||
<Project>{659D19F0-9A40-42C0-886C-555E64F16848}</Project>
|
||||
<Name>Storage.PCL</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using LeanCloud;
|
||||
|
||||
namespace Storage.Test {
|
||||
[TestFixture()]
|
||||
public class Test {
|
||||
[Test()]
|
||||
public void TestCase() {
|
||||
Assembly assembly = Assembly.GetEntryAssembly();
|
||||
var attr = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||||
Console.WriteLine(attr.InformationalVersion);
|
||||
|
||||
FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
|
||||
String version = versionInfo.FileVersion;
|
||||
Console.WriteLine(version);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NUnit" version="3.12.0" targetFramework="net47" />
|
||||
</packages>
|
|
@ -0,0 +1,384 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
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"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
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(CancellationToken))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
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(CancellationToken));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVDecoder
|
||||
{
|
||||
// This class isn't really a Singleton, but since it has no state, it's more efficient to get
|
||||
// the default instance.
|
||||
private static readonly AVDecoder instance = new AVDecoder();
|
||||
public static AVDecoder Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent default constructor.
|
||||
private AVDecoder() { }
|
||||
|
||||
public object Decode(object data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dict = data as IDictionary<string, object>;
|
||||
if (dict != null)
|
||||
{
|
||||
if (dict.ContainsKey("__op"))
|
||||
{
|
||||
return AVFieldOperations.Decode(dict);
|
||||
}
|
||||
|
||||
object type;
|
||||
dict.TryGetValue("__type", out type);
|
||||
var typeString = type as string;
|
||||
|
||||
if (typeString == null)
|
||||
{
|
||||
var newDict = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
newDict[pair.Key] = Decode(pair.Value);
|
||||
}
|
||||
return newDict;
|
||||
}
|
||||
|
||||
if (typeString == "Date")
|
||||
{
|
||||
return ParseDate(dict["iso"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "Bytes")
|
||||
{
|
||||
return Convert.FromBase64String(dict["base64"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "Pointer")
|
||||
{
|
||||
//set a include key to fetch or query.
|
||||
if (dict.Keys.Count > 3)
|
||||
{
|
||||
return DecodeAVObject(dict);
|
||||
}
|
||||
return DecodePointer(dict["className"] as string, dict["objectId"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "File")
|
||||
{
|
||||
return DecodeAVFile(dict);
|
||||
}
|
||||
|
||||
if (typeString == "GeoPoint")
|
||||
{
|
||||
return new AVGeoPoint(Conversion.To<double>(dict["latitude"]),
|
||||
Conversion.To<double>(dict["longitude"]));
|
||||
}
|
||||
|
||||
if (typeString == "Object")
|
||||
{
|
||||
return DecodeAVObject(dict);
|
||||
}
|
||||
|
||||
if (typeString == "Relation")
|
||||
{
|
||||
return AVRelationBase.CreateRelation(null, null, dict["className"] as string);
|
||||
}
|
||||
|
||||
var converted = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
converted[pair.Key] = Decode(pair.Value);
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
var list = data as IList<object>;
|
||||
if (list != null)
|
||||
{
|
||||
return (from item in list
|
||||
select Decode(item)).ToList();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
protected virtual object DecodePointer(string className, string objectId)
|
||||
{
|
||||
if (className == "_File")
|
||||
{
|
||||
return AVFile.CreateWithoutData(objectId);
|
||||
}
|
||||
return AVObject.CreateWithoutData(className, objectId);
|
||||
}
|
||||
protected virtual object DecodeAVObject(IDictionary<string, object> dict)
|
||||
{
|
||||
var className = dict["className"] as string;
|
||||
if (className == "_File")
|
||||
{
|
||||
return DecodeAVFile(dict);
|
||||
}
|
||||
var state = AVObjectCoder.Instance.Decode(dict, this);
|
||||
return AVObject.FromState<AVObject>(state, dict["className"] as string);
|
||||
}
|
||||
protected virtual object DecodeAVFile(IDictionary<string, object> dict)
|
||||
{
|
||||
var objectId = dict["objectId"] as string;
|
||||
var file = AVFile.CreateWithoutData(objectId);
|
||||
file.MergeFromJSON(dict);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
public virtual IList<T> DecodeList<T>(object data)
|
||||
{
|
||||
IList<T> rtn = null;
|
||||
var list = (IList<object>)data;
|
||||
if (list != null)
|
||||
{
|
||||
rtn = new List<T>();
|
||||
foreach (var item in list)
|
||||
{
|
||||
rtn.Add((T)item);
|
||||
}
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public static DateTime ParseDate(string input)
|
||||
{
|
||||
var rtn = DateTime.ParseExact(input,
|
||||
AVClient.DateFormatStrings,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeUniversal);
|
||||
return rtn;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A <c>AVEncoder</c> can be used to transform objects such as <see cref="AVObject"/> into JSON
|
||||
/// data structures.
|
||||
/// </summary>
|
||||
/// <seealso cref="AVDecoder"/>
|
||||
public abstract class AVEncoder
|
||||
{
|
||||
#if UNITY
|
||||
private static readonly bool isCompiledByIL2CPP = AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain");
|
||||
#else
|
||||
private static readonly bool isCompiledByIL2CPP = false;
|
||||
#endif
|
||||
|
||||
public static bool IsValidType(object value)
|
||||
{
|
||||
return value == null ||
|
||||
ReflectionHelpers.IsPrimitive(value.GetType()) ||
|
||||
value is string ||
|
||||
value is AVObject ||
|
||||
value is AVACL ||
|
||||
value is AVFile ||
|
||||
value is AVGeoPoint ||
|
||||
value is AVRelationBase ||
|
||||
value is DateTime ||
|
||||
value is byte[] ||
|
||||
Conversion.As<IDictionary<string, object>>(value) != null ||
|
||||
Conversion.As<IList<object>>(value) != null;
|
||||
}
|
||||
|
||||
public object Encode(object value)
|
||||
{
|
||||
// If this object has a special encoding, encode it and return the
|
||||
// encoded object. Otherwise, just return the original object.
|
||||
if (value is DateTime)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"iso", ((DateTime)value).ToUniversalTime().ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture)
|
||||
},
|
||||
{
|
||||
"__type", "Date"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (value is AVFile)
|
||||
{
|
||||
var file = value as AVFile;
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{"__type", "Pointer"},
|
||||
{ "className", "_File"},
|
||||
{ "objectId", file.ObjectId}
|
||||
};
|
||||
}
|
||||
|
||||
var bytes = value as byte[];
|
||||
if (bytes != null)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "__type", "Bytes"},
|
||||
{ "base64", Convert.ToBase64String(bytes)}
|
||||
};
|
||||
}
|
||||
|
||||
var obj = value as AVObject;
|
||||
if (obj != null)
|
||||
{
|
||||
return EncodeAVObject(obj);
|
||||
}
|
||||
|
||||
var jsonConvertible = value as IJsonConvertible;
|
||||
if (jsonConvertible != null)
|
||||
{
|
||||
return jsonConvertible.ToJSON();
|
||||
}
|
||||
|
||||
var dict = Conversion.As<IDictionary<string, object>>(value);
|
||||
if (dict != null)
|
||||
{
|
||||
var json = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
json[pair.Key] = Encode(pair.Value);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
var list = Conversion.As<IList<object>>(value);
|
||||
if (list != null)
|
||||
{
|
||||
return EncodeList(list);
|
||||
}
|
||||
|
||||
// TODO (hallucinogen): convert IAVFieldOperation to IJsonConvertible
|
||||
var operation = value as IAVFieldOperation;
|
||||
if (operation != null)
|
||||
{
|
||||
return operation.Encode();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected abstract IDictionary<string, object> EncodeAVObject(AVObject value);
|
||||
|
||||
private object EncodeList(IList<object> list)
|
||||
{
|
||||
var newArray = new List<object>();
|
||||
// We need to explicitly cast `list` to `List<object>` rather than
|
||||
// `IList<object>` because IL2CPP is stricter than the usual Unity AOT compiler pipeline.
|
||||
if (isCompiledByIL2CPP && list.GetType().IsArray)
|
||||
{
|
||||
list = new List<object>(list);
|
||||
}
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (!IsValidType(item))
|
||||
{
|
||||
throw new ArgumentException("Invalid type for value in an array");
|
||||
}
|
||||
newArray.Add(Encode(item));
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
// TODO: (richardross) refactor entire LeanCloud coder interfaces.
|
||||
public class AVObjectCoder
|
||||
{
|
||||
private static readonly AVObjectCoder instance = new AVObjectCoder();
|
||||
public static AVObjectCoder Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent default constructor.
|
||||
private AVObjectCoder() { }
|
||||
|
||||
public IDictionary<string, object> Encode<T>(T state,
|
||||
IDictionary<string, IAVFieldOperation> operations,
|
||||
AVEncoder encoder) where T : IObjectState
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
foreach (var pair in operations)
|
||||
{
|
||||
// AVRPCSerialize the data
|
||||
var operation = pair.Value;
|
||||
|
||||
result[pair.Key] = encoder.Encode(operation);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IObjectState Decode(IDictionary<string, object> data,
|
||||
AVDecoder decoder)
|
||||
{
|
||||
IDictionary<string, object> serverData = new Dictionary<string, object>();
|
||||
var mutableData = new Dictionary<string, object>(data);
|
||||
string objectId = extractFromDictionary<string>(mutableData, "objectId", (obj) =>
|
||||
{
|
||||
return obj as string;
|
||||
});
|
||||
DateTime? createdAt = extractFromDictionary<DateTime?>(mutableData, "createdAt", (obj) =>
|
||||
{
|
||||
return 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// A <see cref="AVEncoder"/> that throws an exception if it attempts to encode
|
||||
/// a <see cref="AVObject"/>
|
||||
/// </summary>
|
||||
public class NoObjectsEncoder : AVEncoder {
|
||||
// This class isn't really a Singleton, but since it has no state, it's more efficient to get
|
||||
// the default instance.
|
||||
private static readonly NoObjectsEncoder instance = new NoObjectsEncoder();
|
||||
public static NoObjectsEncoder Instance {
|
||||
get {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
protected override IDictionary<string, object> EncodeAVObject(AVObject value) {
|
||||
throw new ArgumentException("AVObjects not allowed here.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="AVEncoder"/> that encode <see cref="AVObject"/> as pointers. If the object
|
||||
/// does not have an <see cref="AVObject.ObjectId"/>, uses a local id.
|
||||
/// </summary>
|
||||
public class PointerOrLocalIdEncoder : AVEncoder
|
||||
{
|
||||
// This class isn't really a Singleton, but since it has no state, it's more efficient to get
|
||||
// the default instance.
|
||||
private static readonly PointerOrLocalIdEncoder instance = new PointerOrLocalIdEncoder();
|
||||
public static PointerOrLocalIdEncoder Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
protected override IDictionary<string, object> EncodeAVObject(AVObject value)
|
||||
{
|
||||
if (value.ObjectId == null)
|
||||
{
|
||||
// TODO (hallucinogen): handle local id. For now we throw.
|
||||
throw new ArgumentException("Cannot create a pointer to an object without an objectId");
|
||||
}
|
||||
|
||||
return new Dictionary<string, object> {
|
||||
{"__type", "Pointer"},
|
||||
{ "className", value.ClassName},
|
||||
{ "objectId", value.ObjectId}
|
||||
};
|
||||
}
|
||||
|
||||
public IDictionary<string, object> EncodeAVObject(AVObject value, bool isPointer)
|
||||
{
|
||||
if (isPointer)
|
||||
{
|
||||
return EncodeAVObject(value);
|
||||
}
|
||||
var operations = value.GetCurrentOperations();
|
||||
var operationJSON = AVObject.ToJSONObjectForSaving(operations);
|
||||
var objectJSON = value.ToDictionary(kvp => kvp.Key, kvp => PointerOrLocalIdEncoder.Instance.Encode(kvp.Value));
|
||||
foreach (var kvp in operationJSON)
|
||||
{
|
||||
objectJSON[kvp.Key] = kvp.Value;
|
||||
}
|
||||
if (value.CreatedAt.HasValue)
|
||||
{
|
||||
objectJSON["createdAt"] = value.CreatedAt.Value.ToString(AVClient.DateFormatStrings.First(),
|
||||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
if (value.UpdatedAt.HasValue)
|
||||
{
|
||||
objectJSON["updatedAt"] = value.UpdatedAt.Value.ToString(AVClient.DateFormatStrings.First(),
|
||||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
if(!string.IsNullOrEmpty(value.ObjectId))
|
||||
{
|
||||
objectJSON["objectId"] = value.ObjectId;
|
||||
}
|
||||
objectJSON["className"] = value.ClassName;
|
||||
objectJSON["__type"] = "Object";
|
||||
return objectJSON;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
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(CancellationToken))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
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(System.Threading.CancellationToken))
|
||||
{
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,566 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// 弥补Windows Phone 8 API没有自带MD5加密的拓展方法。
|
||||
/// </summary>
|
||||
internal class MD5CryptoServiceProvider : MD5
|
||||
{
|
||||
public MD5CryptoServiceProvider()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Summary description for MD5.
|
||||
/// </summary>
|
||||
internal class MD5 : IDisposable
|
||||
{
|
||||
static public MD5 Create(string hashName)
|
||||
{
|
||||
if (hashName == "MD5")
|
||||
return new MD5();
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
static public string GetMd5String(String source)
|
||||
{
|
||||
MD5 md = MD5CryptoServiceProvider.Create();
|
||||
byte[] hash;
|
||||
|
||||
//Create a new instance of ASCIIEncoding to
|
||||
//convert the string into an array of Unicode bytes.
|
||||
UTF8Encoding enc = new UTF8Encoding();
|
||||
// ASCIIEncoding enc = new ASCIIEncoding();
|
||||
|
||||
//Convert the string into an array of bytes.
|
||||
byte[] buffer = enc.GetBytes(source);
|
||||
|
||||
//Create the hash value from the array of bytes.
|
||||
hash = md.ComputeHash(buffer);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (byte b in hash)
|
||||
sb.Append(b.ToString("x2"));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
static public MD5 Create()
|
||||
{
|
||||
return new MD5();
|
||||
}
|
||||
|
||||
#region base implementation of the MD5
|
||||
#region constants
|
||||
private const byte S11 = 7;
|
||||
private const byte S12 = 12;
|
||||
private const byte S13 = 17;
|
||||
private const byte S14 = 22;
|
||||
private const byte S21 = 5;
|
||||
private const byte S22 = 9;
|
||||
private const byte S23 = 14;
|
||||
private const byte S24 = 20;
|
||||
private const byte S31 = 4;
|
||||
private const byte S32 = 11;
|
||||
private const byte S33 = 16;
|
||||
private const byte S34 = 23;
|
||||
private const byte S41 = 6;
|
||||
private const byte S42 = 10;
|
||||
private const byte S43 = 15;
|
||||
private const byte S44 = 21;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,495 @@
|
|||
//
|
||||
// System.Security.Cryptography.SHA1CryptoServiceProvider.cs
|
||||
//
|
||||
// Authors:
|
||||
// Matthew S. Ford (Matthew.S.Ford@Rose-Hulman.Edu)
|
||||
// Sebastien Pouliot (sebastien@ximian.com)
|
||||
//
|
||||
// Copyright 2001 by Matthew S. Ford.
|
||||
// Copyright (C) 2004, 2005, 2008 Novell, Inc (http://www.novell.com)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
// Note:
|
||||
// The MS Framework includes two (almost) identical class for SHA1.
|
||||
// SHA1Managed is a 100% managed implementation.
|
||||
// SHA1CryptoServiceProvider (this file) is a wrapper on CryptoAPI.
|
||||
// Mono must provide those two class for binary compatibility.
|
||||
// In our case both class are wrappers around a managed internal class SHA1Internal.
|
||||
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
|
||||
internal class SHA1Internal
|
||||
{
|
||||
|
||||
private const int BLOCK_SIZE_BYTES = 64;
|
||||
private uint[] _H; // these are my chaining variables
|
||||
private ulong count;
|
||||
private byte[] _ProcessingBuffer; // Used to start data when passed less than a block worth.
|
||||
private int _ProcessingBufferCount; // Counts how much data we have stored that still needs processed.
|
||||
private uint[] buff;
|
||||
|
||||
public SHA1Internal()
|
||||
{
|
||||
_H = new uint[5];
|
||||
_ProcessingBuffer = new byte[BLOCK_SIZE_BYTES];
|
||||
buff = new uint[80];
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void HashCore(byte[] rgb, int ibStart, int cbSize)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (_ProcessingBufferCount != 0)
|
||||
{
|
||||
if (cbSize < (BLOCK_SIZE_BYTES - _ProcessingBufferCount))
|
||||
{
|
||||
System.Buffer.BlockCopy(rgb, ibStart, _ProcessingBuffer, _ProcessingBufferCount, cbSize);
|
||||
_ProcessingBufferCount += cbSize;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
i = (BLOCK_SIZE_BYTES - _ProcessingBufferCount);
|
||||
System.Buffer.BlockCopy(rgb, ibStart, _ProcessingBuffer, _ProcessingBufferCount, i);
|
||||
ProcessBlock(_ProcessingBuffer, 0);
|
||||
_ProcessingBufferCount = 0;
|
||||
ibStart += i;
|
||||
cbSize -= i;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < cbSize - cbSize % BLOCK_SIZE_BYTES; i += BLOCK_SIZE_BYTES)
|
||||
{
|
||||
ProcessBlock(rgb, (uint)(ibStart + i));
|
||||
}
|
||||
|
||||
if (cbSize % BLOCK_SIZE_BYTES != 0)
|
||||
{
|
||||
System.Buffer.BlockCopy(rgb, cbSize - cbSize % BLOCK_SIZE_BYTES + ibStart, _ProcessingBuffer, 0, cbSize % BLOCK_SIZE_BYTES);
|
||||
_ProcessingBufferCount = cbSize % BLOCK_SIZE_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] HashFinal()
|
||||
{
|
||||
byte[] hash = new byte[20];
|
||||
|
||||
ProcessFinalBlock(_ProcessingBuffer, 0, _ProcessingBufferCount);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
hash[i * 4 + j] = (byte)(_H[i] >> (8 * (3 - j)));
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
count = 0;
|
||||
_ProcessingBufferCount = 0;
|
||||
|
||||
_H[0] = 0x67452301;
|
||||
_H[1] = 0xefcdab89;
|
||||
_H[2] = 0x98badcfe;
|
||||
_H[3] = 0x10325476;
|
||||
_H[4] = 0xC3D2E1F0;
|
||||
}
|
||||
|
||||
private void ProcessBlock(byte[] inputBuffer, uint inputOffset)
|
||||
{
|
||||
uint a, b, c, d, e;
|
||||
|
||||
count += BLOCK_SIZE_BYTES;
|
||||
|
||||
// abc removal would not work on the fields
|
||||
uint[] _H = this._H;
|
||||
uint[] buff = this.buff;
|
||||
InitialiseBuff(buff, inputBuffer, inputOffset);
|
||||
FillBuff(buff);
|
||||
|
||||
a = _H[0];
|
||||
b = _H[1];
|
||||
c = _H[2];
|
||||
d = _H[3];
|
||||
e = _H[4];
|
||||
|
||||
// This function was unrolled because it seems to be doubling our performance with current compiler/VM.
|
||||
// Possibly roll up if this changes.
|
||||
|
||||
// ---- Round 1 --------
|
||||
int i = 0;
|
||||
while (i < 20)
|
||||
{
|
||||
e += ((a << 5) | (a >> 27)) + (((c ^ d) & b) ^ d) + 0x5A827999 + buff[i];
|
||||
b = (b << 30) | (b >> 2);
|
||||
|
||||
d += ((e << 5) | (e >> 27)) + (((b ^ c) & a) ^ c) + 0x5A827999 + buff[i + 1];
|
||||
a = (a << 30) | (a >> 2);
|
||||
|
||||
c += ((d << 5) | (d >> 27)) + (((a ^ b) & e) ^ b) + 0x5A827999 + buff[i + 2];
|
||||
e = (e << 30) | (e >> 2);
|
||||
|
||||
b += ((c << 5) | (c >> 27)) + (((e ^ a) & d) ^ a) + 0x5A827999 + buff[i + 3];
|
||||
d = (d << 30) | (d >> 2);
|
||||
|
||||
a += ((b << 5) | (b >> 27)) + (((d ^ e) & c) ^ e) + 0x5A827999 + buff[i + 4];
|
||||
c = (c << 30) | (c >> 2);
|
||||
i += 5;
|
||||
}
|
||||
|
||||
// ---- Round 2 --------
|
||||
while (i < 40)
|
||||
{
|
||||
e += ((a << 5) | (a >> 27)) + (b ^ c ^ d) + 0x6ED9EBA1 + buff[i];
|
||||
b = (b << 30) | (b >> 2);
|
||||
|
||||
d += ((e << 5) | (e >> 27)) + (a ^ b ^ c) + 0x6ED9EBA1 + buff[i + 1];
|
||||
a = (a << 30) | (a >> 2);
|
||||
|
||||
c += ((d << 5) | (d >> 27)) + (e ^ a ^ b) + 0x6ED9EBA1 + buff[i + 2];
|
||||
e = (e << 30) | (e >> 2);
|
||||
|
||||
b += ((c << 5) | (c >> 27)) + (d ^ e ^ a) + 0x6ED9EBA1 + buff[i + 3];
|
||||
d = (d << 30) | (d >> 2);
|
||||
|
||||
a += ((b << 5) | (b >> 27)) + (c ^ d ^ e) + 0x6ED9EBA1 + buff[i + 4];
|
||||
c = (c << 30) | (c >> 2);
|
||||
i += 5;
|
||||
}
|
||||
|
||||
// ---- Round 3 --------
|
||||
while (i < 60)
|
||||
{
|
||||
e += ((a << 5) | (a >> 27)) + ((b & c) | (b & d) | (c & d)) + 0x8F1BBCDC + buff[i];
|
||||
b = (b << 30) | (b >> 2);
|
||||
|
||||
d += ((e << 5) | (e >> 27)) + ((a & b) | (a & c) | (b & c)) + 0x8F1BBCDC + buff[i + 1];
|
||||
a = (a << 30) | (a >> 2);
|
||||
|
||||
c += ((d << 5) | (d >> 27)) + ((e & a) | (e & b) | (a & b)) + 0x8F1BBCDC + buff[i + 2];
|
||||
e = (e << 30) | (e >> 2);
|
||||
|
||||
b += ((c << 5) | (c >> 27)) + ((d & e) | (d & a) | (e & a)) + 0x8F1BBCDC + buff[i + 3];
|
||||
d = (d << 30) | (d >> 2);
|
||||
|
||||
a += ((b << 5) | (b >> 27)) + ((c & d) | (c & e) | (d & e)) + 0x8F1BBCDC + buff[i + 4];
|
||||
c = (c << 30) | (c >> 2);
|
||||
i += 5;
|
||||
}
|
||||
|
||||
// ---- Round 4 --------
|
||||
while (i < 80)
|
||||
{
|
||||
e += ((a << 5) | (a >> 27)) + (b ^ c ^ d) + 0xCA62C1D6 + buff[i];
|
||||
b = (b << 30) | (b >> 2);
|
||||
|
||||
d += ((e << 5) | (e >> 27)) + (a ^ b ^ c) + 0xCA62C1D6 + buff[i + 1];
|
||||
a = (a << 30) | (a >> 2);
|
||||
|
||||
c += ((d << 5) | (d >> 27)) + (e ^ a ^ b) + 0xCA62C1D6 + buff[i + 2];
|
||||
e = (e << 30) | (e >> 2);
|
||||
|
||||
b += ((c << 5) | (c >> 27)) + (d ^ e ^ a) + 0xCA62C1D6 + buff[i + 3];
|
||||
d = (d << 30) | (d >> 2);
|
||||
|
||||
a += ((b << 5) | (b >> 27)) + (c ^ d ^ e) + 0xCA62C1D6 + buff[i + 4];
|
||||
c = (c << 30) | (c >> 2);
|
||||
i += 5;
|
||||
}
|
||||
|
||||
_H[0] += a;
|
||||
_H[1] += b;
|
||||
_H[2] += c;
|
||||
_H[3] += d;
|
||||
_H[4] += e;
|
||||
}
|
||||
|
||||
private static void InitialiseBuff(uint[] buff, byte[] input, uint inputOffset)
|
||||
{
|
||||
buff[0] = (uint)((input[inputOffset + 0] << 24) | (input[inputOffset + 1] << 16) | (input[inputOffset + 2] << 8) | (input[inputOffset + 3]));
|
||||
buff[1] = (uint)((input[inputOffset + 4] << 24) | (input[inputOffset + 5] << 16) | (input[inputOffset + 6] << 8) | (input[inputOffset + 7]));
|
||||
buff[2] = (uint)((input[inputOffset + 8] << 24) | (input[inputOffset + 9] << 16) | (input[inputOffset + 10] << 8) | (input[inputOffset + 11]));
|
||||
buff[3] = (uint)((input[inputOffset + 12] << 24) | (input[inputOffset + 13] << 16) | (input[inputOffset + 14] << 8) | (input[inputOffset + 15]));
|
||||
buff[4] = (uint)((input[inputOffset + 16] << 24) | (input[inputOffset + 17] << 16) | (input[inputOffset + 18] << 8) | (input[inputOffset + 19]));
|
||||
buff[5] = (uint)((input[inputOffset + 20] << 24) | (input[inputOffset + 21] << 16) | (input[inputOffset + 22] << 8) | (input[inputOffset + 23]));
|
||||
buff[6] = (uint)((input[inputOffset + 24] << 24) | (input[inputOffset + 25] << 16) | (input[inputOffset + 26] << 8) | (input[inputOffset + 27]));
|
||||
buff[7] = (uint)((input[inputOffset + 28] << 24) | (input[inputOffset + 29] << 16) | (input[inputOffset + 30] << 8) | (input[inputOffset + 31]));
|
||||
buff[8] = (uint)((input[inputOffset + 32] << 24) | (input[inputOffset + 33] << 16) | (input[inputOffset + 34] << 8) | (input[inputOffset + 35]));
|
||||
buff[9] = (uint)((input[inputOffset + 36] << 24) | (input[inputOffset + 37] << 16) | (input[inputOffset + 38] << 8) | (input[inputOffset + 39]));
|
||||
buff[10] = (uint)((input[inputOffset + 40] << 24) | (input[inputOffset + 41] << 16) | (input[inputOffset + 42] << 8) | (input[inputOffset + 43]));
|
||||
buff[11] = (uint)((input[inputOffset + 44] << 24) | (input[inputOffset + 45] << 16) | (input[inputOffset + 46] << 8) | (input[inputOffset + 47]));
|
||||
buff[12] = (uint)((input[inputOffset + 48] << 24) | (input[inputOffset + 49] << 16) | (input[inputOffset + 50] << 8) | (input[inputOffset + 51]));
|
||||
buff[13] = (uint)((input[inputOffset + 52] << 24) | (input[inputOffset + 53] << 16) | (input[inputOffset + 54] << 8) | (input[inputOffset + 55]));
|
||||
buff[14] = (uint)((input[inputOffset + 56] << 24) | (input[inputOffset + 57] << 16) | (input[inputOffset + 58] << 8) | (input[inputOffset + 59]));
|
||||
buff[15] = (uint)((input[inputOffset + 60] << 24) | (input[inputOffset + 61] << 16) | (input[inputOffset + 62] << 8) | (input[inputOffset + 63]));
|
||||
}
|
||||
|
||||
private static void FillBuff(uint[] buff)
|
||||
{
|
||||
uint val;
|
||||
for (int i = 16; i < 80; i += 8)
|
||||
{
|
||||
val = buff[i - 3] ^ buff[i - 8] ^ buff[i - 14] ^ buff[i - 16];
|
||||
buff[i] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i - 2] ^ buff[i - 7] ^ buff[i - 13] ^ buff[i - 15];
|
||||
buff[i + 1] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i - 1] ^ buff[i - 6] ^ buff[i - 12] ^ buff[i - 14];
|
||||
buff[i + 2] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i + 0] ^ buff[i - 5] ^ buff[i - 11] ^ buff[i - 13];
|
||||
buff[i + 3] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i + 1] ^ buff[i - 4] ^ buff[i - 10] ^ buff[i - 12];
|
||||
buff[i + 4] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i + 2] ^ buff[i - 3] ^ buff[i - 9] ^ buff[i - 11];
|
||||
buff[i + 5] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i + 3] ^ buff[i - 2] ^ buff[i - 8] ^ buff[i - 10];
|
||||
buff[i + 6] = (val << 1) | (val >> 31);
|
||||
|
||||
val = buff[i + 4] ^ buff[i - 1] ^ buff[i - 7] ^ buff[i - 9];
|
||||
buff[i + 7] = (val << 1) | (val >> 31);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
|
||||
{
|
||||
ulong total = count + (ulong)inputCount;
|
||||
int paddingSize = (56 - (int)(total % BLOCK_SIZE_BYTES));
|
||||
|
||||
if (paddingSize < 1)
|
||||
paddingSize += BLOCK_SIZE_BYTES;
|
||||
|
||||
int length = inputCount + paddingSize + 8;
|
||||
byte[] fooBuffer = (length == 64) ? _ProcessingBuffer : new byte[length];
|
||||
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
fooBuffer[i] = inputBuffer[i + inputOffset];
|
||||
}
|
||||
|
||||
fooBuffer[inputCount] = 0x80;
|
||||
for (int i = inputCount + 1; i < inputCount + paddingSize; i++)
|
||||
{
|
||||
fooBuffer[i] = 0x00;
|
||||
}
|
||||
|
||||
// I deal in bytes. The algorithm deals in bits.
|
||||
ulong size = total << 3;
|
||||
AddLength(size, fooBuffer, inputCount + paddingSize);
|
||||
ProcessBlock(fooBuffer, 0);
|
||||
|
||||
if (length == 128)
|
||||
ProcessBlock(fooBuffer, 64);
|
||||
}
|
||||
|
||||
internal void AddLength(ulong length, byte[] buffer, int position)
|
||||
{
|
||||
buffer[position++] = (byte)(length >> 56);
|
||||
buffer[position++] = (byte)(length >> 48);
|
||||
buffer[position++] = (byte)(length >> 40);
|
||||
buffer[position++] = (byte)(length >> 32);
|
||||
buffer[position++] = (byte)(length >> 24);
|
||||
buffer[position++] = (byte)(length >> 16);
|
||||
buffer[position++] = (byte)(length >> 8);
|
||||
buffer[position] = (byte)(length);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SHA1CryptoServiceProvider : SHA1
|
||||
{
|
||||
|
||||
private SHA1Internal sha;
|
||||
|
||||
public SHA1CryptoServiceProvider()
|
||||
{
|
||||
sha = new SHA1Internal();
|
||||
}
|
||||
|
||||
~SHA1CryptoServiceProvider()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
// nothing new to do (managed implementation)
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override void HashCore(byte[] rgb, int ibStart, int cbSize)
|
||||
{
|
||||
State = 1;
|
||||
sha.HashCore(rgb, ibStart, cbSize);
|
||||
}
|
||||
|
||||
protected override byte[] HashFinal()
|
||||
{
|
||||
State = 0;
|
||||
return sha.HashFinal();
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
sha.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SHA1 : HashAlgorithm
|
||||
{
|
||||
protected SHA1()
|
||||
{
|
||||
HashSizeValue = 160;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class HashAlgorithm : IDisposable
|
||||
{
|
||||
protected int HashSizeValue;
|
||||
protected internal byte[] HashValue;
|
||||
protected int State = 0;
|
||||
|
||||
private bool m_bDisposed = false;
|
||||
|
||||
protected HashAlgorithm() { }
|
||||
|
||||
//
|
||||
// public properties
|
||||
//
|
||||
|
||||
public virtual int HashSize
|
||||
{
|
||||
get { return HashSizeValue; }
|
||||
}
|
||||
|
||||
//
|
||||
// public methods
|
||||
//
|
||||
|
||||
public byte[] ComputeHash(Stream inputStream)
|
||||
{
|
||||
if (m_bDisposed)
|
||||
throw new ObjectDisposedException(null);
|
||||
|
||||
// Default the buffer size to 4K.
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
do
|
||||
{
|
||||
bytesRead = inputStream.Read(buffer, 0, 4096);
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
HashCore(buffer, 0, bytesRead);
|
||||
}
|
||||
} while (bytesRead > 0);
|
||||
|
||||
HashValue = HashFinal();
|
||||
byte[] Tmp = (byte[])HashValue.Clone();
|
||||
Initialize();
|
||||
return (Tmp);
|
||||
}
|
||||
|
||||
public byte[] ComputeHash(byte[] buffer)
|
||||
{
|
||||
if (m_bDisposed)
|
||||
throw new ObjectDisposedException(null);
|
||||
|
||||
// Do some validation
|
||||
if (buffer == null) throw new ArgumentNullException("buffer");
|
||||
|
||||
HashCore(buffer, 0, buffer.Length);
|
||||
HashValue = HashFinal();
|
||||
byte[] Tmp = (byte[])HashValue.Clone();
|
||||
Initialize();
|
||||
return (Tmp);
|
||||
}
|
||||
|
||||
// ICryptoTransform methods
|
||||
|
||||
// we assume any HashAlgorithm can take input a byte at a time
|
||||
public virtual int InputBlockSize
|
||||
{
|
||||
get { return (1); }
|
||||
}
|
||||
|
||||
public virtual int OutputBlockSize
|
||||
{
|
||||
get { return (1); }
|
||||
}
|
||||
|
||||
public virtual bool CanTransformMultipleBlocks
|
||||
{
|
||||
get { return (true); }
|
||||
}
|
||||
|
||||
public virtual bool CanReuseTransform
|
||||
{
|
||||
get { return (true); }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
(this as IDisposable).Dispose();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (HashValue != null)
|
||||
Array.Clear(HashValue, 0, HashValue.Length);
|
||||
HashValue = null;
|
||||
m_bDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// abstract public methods
|
||||
//
|
||||
|
||||
public abstract void Initialize();
|
||||
|
||||
protected abstract void HashCore(byte[] array, int ibStart, int cbSize);
|
||||
|
||||
protected abstract byte[] HashFinal();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
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>();
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using NetHttpClient = System.Net.Http.HttpClient;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class HttpClient : IHttpClient {
|
||||
static readonly 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" }
|
||||
};
|
||||
|
||||
readonly NetHttpClient client;
|
||||
|
||||
public HttpClient() {
|
||||
client = new NetHttpClient();
|
||||
// TODO 设置版本号
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "LeanCloud-dotNet-SDK/" + "2.0.0");
|
||||
}
|
||||
|
||||
public HttpClient(NetHttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public async Task<Tuple<HttpStatusCode, string>> ExecuteAsync(HttpRequest httpRequest,
|
||||
IProgress<AVUploadProgressEventArgs> uploadProgress,
|
||||
IProgress<AVDownloadProgressEventArgs> downloadProgress,
|
||||
CancellationToken cancellationToken) {
|
||||
|
||||
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;
|
||||
|
||||
uploadProgress?.Report(new AVUploadProgressEventArgs { Progress = 0 });
|
||||
var response = await client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
uploadProgress?.Report(new AVUploadProgressEventArgs { Progress = 1 });
|
||||
|
||||
var resultString = await response.Content.ReadAsStringAsync();
|
||||
response.Dispose();
|
||||
|
||||
downloadProgress?.Report(new AVDownloadProgressEventArgs { Progress = 1 });
|
||||
|
||||
return new Tuple<HttpStatusCode, string>(response.StatusCode, resultString);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IObjectState : IEnumerable<KeyValuePair<string, object>>
|
||||
{
|
||||
bool IsNew { get; }
|
||||
string ClassName { get; }
|
||||
string ObjectId { get; }
|
||||
DateTime? UpdatedAt { get; }
|
||||
DateTime? CreatedAt { get; }
|
||||
object this[string key] { get; }
|
||||
|
||||
bool ContainsKey(string key);
|
||||
|
||||
IObjectState MutatedClone(Action<MutableObjectState> func);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class MutableObjectState : IObjectState
|
||||
{
|
||||
public bool IsNew { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
public string ObjectId { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
|
||||
// Initialize serverData to avoid further null checking.
|
||||
private IDictionary<string, object> serverData = new Dictionary<string, object>();
|
||||
public IDictionary<string, object> ServerData
|
||||
{
|
||||
get
|
||||
{
|
||||
return serverData;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
serverData = value;
|
||||
}
|
||||
}
|
||||
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServerData[key];
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return ServerData.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void Apply(IDictionary<string, IAVFieldOperation> operationSet)
|
||||
{
|
||||
// Apply operationSet
|
||||
foreach (var pair in operationSet)
|
||||
{
|
||||
object oldValue;
|
||||
ServerData.TryGetValue(pair.Key, out oldValue);
|
||||
var newValue = pair.Value.Apply(oldValue, pair.Key);
|
||||
if (newValue != AVDeleteOperation.DeleteToken)
|
||||
{
|
||||
ServerData[pair.Key] = newValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerData.Remove(pair.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Apply(IObjectState other)
|
||||
{
|
||||
IsNew = other.IsNew;
|
||||
if (other.ObjectId != null)
|
||||
{
|
||||
ObjectId = other.ObjectId;
|
||||
}
|
||||
if (other.UpdatedAt != null)
|
||||
{
|
||||
UpdatedAt = other.UpdatedAt;
|
||||
}
|
||||
if (other.CreatedAt != null)
|
||||
{
|
||||
CreatedAt = other.CreatedAt;
|
||||
}
|
||||
|
||||
foreach (var pair in other)
|
||||
{
|
||||
ServerData[pair.Key] = pair.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public IObjectState MutatedClone(Action<MutableObjectState> func)
|
||||
{
|
||||
var clone = MutableClone();
|
||||
func(clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
protected virtual MutableObjectState MutableClone()
|
||||
{
|
||||
return new MutableObjectState
|
||||
{
|
||||
IsNew = IsNew,
|
||||
ClassName = ClassName,
|
||||
ObjectId = ObjectId,
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt,
|
||||
ServerData = this.ToDictionary(t => t.Key, t => t.Value)
|
||||
};
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
|
||||
{
|
||||
return ServerData.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<KeyValuePair<string, object>>)this).GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
internal class ObjectSubclassInfo
|
||||
{
|
||||
public ObjectSubclassInfo(Type type, ConstructorInfo constructor)
|
||||
{
|
||||
TypeInfo = type.GetTypeInfo();
|
||||
ClassName = GetClassName(TypeInfo);
|
||||
Constructor = constructor;
|
||||
PropertyMappings = ReflectionHelpers.GetProperties(type)
|
||||
.Select(prop => Tuple.Create(prop, prop.GetCustomAttribute<AVFieldNameAttribute>(true)))
|
||||
.Where(t => t.Item2 != null)
|
||||
.Select(t => Tuple.Create(t.Item1, t.Item2.FieldName))
|
||||
.ToDictionary(t => t.Item1.Name, t => t.Item2);
|
||||
}
|
||||
|
||||
public TypeInfo TypeInfo { get; private set; }
|
||||
public String ClassName { get; private set; }
|
||||
public IDictionary<String, String> PropertyMappings { get; private set; }
|
||||
private ConstructorInfo Constructor { get; set; }
|
||||
|
||||
public AVObject Instantiate()
|
||||
{
|
||||
return (AVObject)Constructor.Invoke(null);
|
||||
}
|
||||
|
||||
internal static String GetClassName(TypeInfo type)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<AVClassNameAttribute>();
|
||||
return attribute != null ? attribute.ClassName : null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
internal class ObjectSubclassingController : IObjectSubclassingController
|
||||
{
|
||||
// Class names starting with _ are documented to be reserved. Use this one
|
||||
// here to allow us to 'inherit' certain properties.
|
||||
private static readonly string avObjectClassName = "_AVObject";
|
||||
|
||||
private readonly ReaderWriterLockSlim mutex;
|
||||
private readonly IDictionary<String, ObjectSubclassInfo> registeredSubclasses;
|
||||
private Dictionary<String, Action> registerActions;
|
||||
|
||||
public ObjectSubclassingController()
|
||||
{
|
||||
mutex = new ReaderWriterLockSlim();
|
||||
registeredSubclasses = new Dictionary<String, ObjectSubclassInfo>();
|
||||
registerActions = new Dictionary<string, Action>();
|
||||
|
||||
// Register the AVObject subclass, so we get access to the ACL,
|
||||
// objectId, and other AVFieldName properties.
|
||||
RegisterSubclass(typeof(AVObject));
|
||||
}
|
||||
|
||||
public String GetClassName(Type type)
|
||||
{
|
||||
return type == typeof(AVObject)
|
||||
? avObjectClassName
|
||||
: ObjectSubclassInfo.GetClassName(type.GetTypeInfo());
|
||||
}
|
||||
|
||||
public Type GetType(String className)
|
||||
{
|
||||
ObjectSubclassInfo info = null;
|
||||
mutex.EnterReadLock();
|
||||
registeredSubclasses.TryGetValue(className, out info);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return info != null
|
||||
? info.TypeInfo.AsType()
|
||||
: null;
|
||||
}
|
||||
|
||||
public bool IsTypeValid(String className, Type type)
|
||||
{
|
||||
ObjectSubclassInfo subclassInfo = null;
|
||||
|
||||
mutex.EnterReadLock();
|
||||
registeredSubclasses.TryGetValue(className, out subclassInfo);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return subclassInfo == null
|
||||
? type == typeof(AVObject)
|
||||
: subclassInfo.TypeInfo == type.GetTypeInfo();
|
||||
}
|
||||
|
||||
public void RegisterSubclass(Type type)
|
||||
{
|
||||
TypeInfo typeInfo = type.GetTypeInfo();
|
||||
if (!typeof(AVObject).GetTypeInfo().IsAssignableFrom(typeInfo))
|
||||
{
|
||||
throw new ArgumentException("Cannot register a type that is not a subclass of AVObject");
|
||||
}
|
||||
|
||||
String className = GetClassName(type);
|
||||
|
||||
try
|
||||
{
|
||||
// Perform this as a single independent transaction, so we can never get into an
|
||||
// intermediate state where we *theoretically* register the wrong class due to a
|
||||
// TOCTTOU bug.
|
||||
mutex.EnterWriteLock();
|
||||
|
||||
ObjectSubclassInfo previousInfo = null;
|
||||
if (registeredSubclasses.TryGetValue(className, out previousInfo))
|
||||
{
|
||||
if (typeInfo.IsAssignableFrom(previousInfo.TypeInfo))
|
||||
{
|
||||
// Previous subclass is more specific or equal to the current type, do nothing.
|
||||
return;
|
||||
}
|
||||
else if (previousInfo.TypeInfo.IsAssignableFrom(typeInfo))
|
||||
{
|
||||
// Previous subclass is parent of new child, fallthrough and actually register
|
||||
// this class.
|
||||
/* Do nothing */
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Tried to register both " + previousInfo.TypeInfo.FullName + " and " + typeInfo.FullName +
|
||||
" as the AVObject subclass of " + className + ". Cannot determine the right class " +
|
||||
"to use because neither inherits from the other."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConstructorInfo constructor = type.FindConstructor();
|
||||
if (constructor == null)
|
||||
{
|
||||
throw new ArgumentException("Cannot register a type that does not implement the default constructor!");
|
||||
}
|
||||
|
||||
registeredSubclasses[className] = new ObjectSubclassInfo(type, constructor);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mutex.ExitWriteLock();
|
||||
}
|
||||
|
||||
Action toPerform;
|
||||
|
||||
mutex.EnterReadLock();
|
||||
registerActions.TryGetValue(className, out toPerform);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
if (toPerform != null)
|
||||
{
|
||||
toPerform();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterSubclass(Type type)
|
||||
{
|
||||
mutex.EnterWriteLock();
|
||||
registeredSubclasses.Remove(GetClassName(type));
|
||||
mutex.ExitWriteLock();
|
||||
}
|
||||
|
||||
public void AddRegisterHook(Type t, Action action)
|
||||
{
|
||||
mutex.EnterWriteLock();
|
||||
registerActions.Add(GetClassName(t), action);
|
||||
mutex.ExitWriteLock();
|
||||
}
|
||||
|
||||
public AVObject Instantiate(String className)
|
||||
{
|
||||
ObjectSubclassInfo info = null;
|
||||
|
||||
mutex.EnterReadLock();
|
||||
registeredSubclasses.TryGetValue(className, out info);
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return info != null
|
||||
? info.Instantiate()
|
||||
: new AVObject(className);
|
||||
}
|
||||
|
||||
public IDictionary<String, String> GetPropertyMappings(String className)
|
||||
{
|
||||
ObjectSubclassInfo info = null;
|
||||
mutex.EnterReadLock();
|
||||
registeredSubclasses.TryGetValue(className, out info);
|
||||
if (info == null)
|
||||
{
|
||||
registeredSubclasses.TryGetValue(avObjectClassName, out info);
|
||||
}
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return info.PropertyMappings;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVAddOperation : IAVFieldOperation {
|
||||
private ReadOnlyCollection<object> objects;
|
||||
public AVAddOperation(IEnumerable<object> objects) {
|
||||
this.objects = new ReadOnlyCollection<object>(objects.ToList());
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{"__op", "Add"},
|
||||
{"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)}
|
||||
};
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) {
|
||||
if (previous == null) {
|
||||
return this;
|
||||
}
|
||||
if (previous is AVDeleteOperation) {
|
||||
return new AVSetOperation(objects.ToList());
|
||||
}
|
||||
if (previous is AVSetOperation) {
|
||||
var setOp = (AVSetOperation)previous;
|
||||
var oldList = Conversion.To<IList<object>>(setOp.Value);
|
||||
return new AVSetOperation(oldList.Concat(objects).ToList());
|
||||
}
|
||||
if (previous is AVAddOperation) {
|
||||
return new AVAddOperation(((AVAddOperation)previous).Objects.Concat(objects));
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
if (oldValue == null) {
|
||||
return objects.ToList();
|
||||
}
|
||||
var oldList = Conversion.To<IList<object>>(oldValue);
|
||||
return oldList.Concat(objects).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<object> Objects {
|
||||
get {
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVAddUniqueOperation : IAVFieldOperation {
|
||||
private ReadOnlyCollection<object> objects;
|
||||
public AVAddUniqueOperation(IEnumerable<object> objects) {
|
||||
this.objects = new ReadOnlyCollection<object>(objects.Distinct().ToList());
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return new Dictionary<string, object> {
|
||||
{"__op", "AddUnique"},
|
||||
{"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)}
|
||||
};
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) {
|
||||
if (previous == null) {
|
||||
return this;
|
||||
}
|
||||
if (previous is AVDeleteOperation) {
|
||||
return new AVSetOperation(objects.ToList());
|
||||
}
|
||||
if (previous is AVSetOperation) {
|
||||
var setOp = (AVSetOperation)previous;
|
||||
var oldList = Conversion.To<IList<object>>(setOp.Value);
|
||||
var result = this.Apply(oldList, null);
|
||||
return new AVSetOperation(result);
|
||||
}
|
||||
if (previous is AVAddUniqueOperation) {
|
||||
var oldList = ((AVAddUniqueOperation)previous).Objects;
|
||||
return new AVAddUniqueOperation((IList<object>)this.Apply(oldList, null));
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
if (oldValue == null) {
|
||||
return objects.ToList();
|
||||
}
|
||||
var newList = Conversion.To<IList<object>>(oldValue).ToList();
|
||||
var comparer = AVFieldOperations.AVObjectComparer;
|
||||
foreach (var objToAdd in objects) {
|
||||
if (objToAdd is AVObject) {
|
||||
var matchedObj = newList.FirstOrDefault(listObj => comparer.Equals(objToAdd, listObj));
|
||||
if (matchedObj == null) {
|
||||
newList.Add(objToAdd);
|
||||
} else {
|
||||
var index = newList.IndexOf(matchedObj);
|
||||
newList[index] = objToAdd;
|
||||
}
|
||||
} else if (!newList.Contains<object>(objToAdd, comparer)) {
|
||||
newList.Add(objToAdd);
|
||||
}
|
||||
}
|
||||
return newList;
|
||||
}
|
||||
|
||||
public IEnumerable<object> Objects {
|
||||
get {
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// An operation where a field is deleted from the object.
|
||||
/// </summary>
|
||||
public class AVDeleteOperation : IAVFieldOperation
|
||||
{
|
||||
internal static readonly object DeleteToken = new object();
|
||||
private static AVDeleteOperation _Instance = new AVDeleteOperation();
|
||||
public static AVDeleteOperation Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Instance;
|
||||
}
|
||||
}
|
||||
|
||||
private AVDeleteOperation() { }
|
||||
public object Encode()
|
||||
{
|
||||
return new Dictionary<string, object> {
|
||||
{"__op", "Delete"}
|
||||
};
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key)
|
||||
{
|
||||
return DeleteToken;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVObjectIdComparer : IEqualityComparer<object> {
|
||||
bool IEqualityComparer<object>.Equals(object p1, object p2) {
|
||||
var avObj1 = p1 as AVObject;
|
||||
var avObj2 = p2 as AVObject;
|
||||
if (avObj1 != null && avObj2 != null) {
|
||||
return object.Equals(avObj1.ObjectId, avObj2.ObjectId);
|
||||
}
|
||||
return object.Equals(p1, p2);
|
||||
}
|
||||
|
||||
public int GetHashCode(object p) {
|
||||
var avObject = p as AVObject;
|
||||
if (avObject != null) {
|
||||
return avObject.ObjectId.GetHashCode();
|
||||
}
|
||||
return p.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
static class AVFieldOperations {
|
||||
private static AVObjectIdComparer comparer;
|
||||
|
||||
public static IAVFieldOperation Decode(IDictionary<string, object> json) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static IEqualityComparer<object> AVObjectComparer {
|
||||
get {
|
||||
if (comparer == null) {
|
||||
comparer = new AVObjectIdComparer();
|
||||
}
|
||||
return comparer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVIncrementOperation : IAVFieldOperation
|
||||
{
|
||||
private static readonly IDictionary<Tuple<Type, Type>, Func<object, object, object>> adders;
|
||||
|
||||
static AVIncrementOperation()
|
||||
{
|
||||
// Defines adders for all of the implicit conversions: http://msdn.microsoft.com/en-US/library/y5b434w4(v=vs.80).aspx
|
||||
adders = new Dictionary<Tuple<Type, Type>, Func<object, object, object>> {
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(sbyte)), (left, right) => (sbyte)left + (sbyte)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(short)), (left, right) => (sbyte)left + (short)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(int)), (left, right) => (sbyte)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(long)), (left, right) => (sbyte)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(float)), (left, right) => (sbyte)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(double)), (left, right) => (sbyte)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(sbyte), typeof(decimal)), (left, right) => (sbyte)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(byte)), (left, right) => (byte)left + (byte)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(short)), (left, right) => (byte)left + (short)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(ushort)), (left, right) => (byte)left + (ushort)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(int)), (left, right) => (byte)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(uint)), (left, right) => (byte)left + (uint)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(long)), (left, right) => (byte)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(ulong)), (left, right) => (byte)left + (ulong)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(float)), (left, right) => (byte)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(double)), (left, right) => (byte)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(byte), typeof(decimal)), (left, right) => (byte)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(short)), (left, right) => (short)left + (short)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(int)), (left, right) => (short)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(long)), (left, right) => (short)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(float)), (left, right) => (short)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(double)), (left, right) => (short)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(short), typeof(decimal)), (left, right) => (short)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(ushort)), (left, right) => (ushort)left + (ushort)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(int)), (left, right) => (ushort)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(uint)), (left, right) => (ushort)left + (uint)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(long)), (left, right) => (ushort)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(ulong)), (left, right) => (ushort)left + (ulong)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(float)), (left, right) => (ushort)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(double)), (left, right) => (ushort)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(ushort), typeof(decimal)), (left, right) => (ushort)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(int), typeof(int)), (left, right) => (int)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(int), typeof(long)), (left, right) => (int)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(int), typeof(float)), (left, right) => (int)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(int), typeof(double)), (left, right) => (int)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(int), typeof(decimal)), (left, right) => (int)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(uint)), (left, right) => (uint)left + (uint)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(long)), (left, right) => (uint)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(ulong)), (left, right) => (uint)left + (ulong)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(float)), (left, right) => (uint)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(double)), (left, right) => (uint)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(uint), typeof(decimal)), (left, right) => (uint)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(long), typeof(long)), (left, right) => (long)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(long), typeof(float)), (left, right) => (long)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(long), typeof(double)), (left, right) => (long)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(long), typeof(decimal)), (left, right) => (long)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(char)), (left, right) => (char)left + (char)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(ushort)), (left, right) => (char)left + (ushort)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(int)), (left, right) => (char)left + (int)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(uint)), (left, right) => (char)left + (uint)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(long)), (left, right) => (char)left + (long)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(ulong)), (left, right) => (char)left + (ulong)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(float)), (left, right) => (char)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(double)), (left, right) => (char)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(char), typeof(decimal)), (left, right) => (char)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(float), typeof(float)), (left, right) => (float)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(float), typeof(double)), (left, right) => (float)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(ulong), typeof(ulong)), (left, right) => (ulong)left + (ulong)right},
|
||||
{new Tuple<Type, Type>(typeof(ulong), typeof(float)), (left, right) => (ulong)left + (float)right},
|
||||
{new Tuple<Type, Type>(typeof(ulong), typeof(double)), (left, right) => (ulong)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(ulong), typeof(decimal)), (left, right) => (ulong)left + (decimal)right},
|
||||
{new Tuple<Type, Type>(typeof(double), typeof(double)), (left, right) => (double)left + (double)right},
|
||||
{new Tuple<Type, Type>(typeof(decimal), typeof(decimal)), (left, right) => (decimal)left + (decimal)right}
|
||||
};
|
||||
// Generate the adders in the other direction
|
||||
foreach (var pair in adders.Keys.ToList())
|
||||
{
|
||||
if (pair.Item1.Equals(pair.Item2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var reversePair = new Tuple<Type, Type>(pair.Item2, pair.Item1);
|
||||
var func = adders[pair];
|
||||
adders[reversePair] = (left, right) => func(right, left);
|
||||
}
|
||||
}
|
||||
|
||||
private object amount;
|
||||
|
||||
public AVIncrementOperation(object amount)
|
||||
{
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public object Encode()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{"__op", "Increment"},
|
||||
{"amount", amount}
|
||||
};
|
||||
}
|
||||
|
||||
private static object Add(object obj1, object obj2)
|
||||
{
|
||||
Func<object, object, object> adder;
|
||||
if (adders.TryGetValue(new Tuple<Type, Type>(obj1.GetType(), obj2.GetType()), out adder))
|
||||
{
|
||||
return adder(obj1, obj2);
|
||||
}
|
||||
throw new InvalidCastException("Cannot add " + obj1.GetType() + " to " + obj2.GetType());
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous)
|
||||
{
|
||||
if (previous == null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
if (previous is AVDeleteOperation)
|
||||
{
|
||||
return new AVSetOperation(amount);
|
||||
}
|
||||
if (previous is AVSetOperation)
|
||||
{
|
||||
var otherAmount = ((AVSetOperation)previous).Value;
|
||||
if (otherAmount is string)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot increment a non-number type.");
|
||||
}
|
||||
var myAmount = amount;
|
||||
return new AVSetOperation(Add(otherAmount, myAmount));
|
||||
}
|
||||
if (previous is AVIncrementOperation)
|
||||
{
|
||||
object otherAmount = ((AVIncrementOperation)previous).Amount;
|
||||
object myAmount = amount;
|
||||
return new AVIncrementOperation(Add(otherAmount, myAmount));
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key)
|
||||
{
|
||||
if (oldValue is string)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot increment a non-number type.");
|
||||
}
|
||||
object otherAmount = oldValue ?? 0;
|
||||
object myAmount = amount;
|
||||
return Add(otherAmount, myAmount);
|
||||
}
|
||||
|
||||
public object Amount
|
||||
{
|
||||
get
|
||||
{
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVRelationOperation : IAVFieldOperation {
|
||||
private readonly IList<string> adds;
|
||||
private readonly IList<string> removes;
|
||||
private readonly string targetClassName;
|
||||
|
||||
private AVRelationOperation(IEnumerable<string> adds,
|
||||
IEnumerable<string> removes,
|
||||
string targetClassName) {
|
||||
this.targetClassName = targetClassName;
|
||||
this.adds = new ReadOnlyCollection<string>(adds.ToList());
|
||||
this.removes = new ReadOnlyCollection<string>(removes.ToList());
|
||||
}
|
||||
|
||||
public AVRelationOperation(IEnumerable<AVObject> adds,
|
||||
IEnumerable<AVObject> removes) {
|
||||
adds = adds ?? new AVObject[0];
|
||||
removes = removes ?? new AVObject[0];
|
||||
this.targetClassName = adds.Concat(removes).Select(o => o.ClassName).FirstOrDefault();
|
||||
this.adds = new ReadOnlyCollection<string>(IdsFromObjects(adds).ToList());
|
||||
this.removes = new ReadOnlyCollection<string>(IdsFromObjects(removes).ToList());
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
var adds = this.adds
|
||||
.Select(id => PointerOrLocalIdEncoder.Instance.Encode(
|
||||
AVObject.CreateWithoutData(targetClassName, id)))
|
||||
.ToList();
|
||||
var removes = this.removes
|
||||
.Select(id => PointerOrLocalIdEncoder.Instance.Encode(
|
||||
AVObject.CreateWithoutData(targetClassName, id)))
|
||||
.ToList();
|
||||
var addDict = adds.Count == 0 ? null : new Dictionary<string, object> {
|
||||
{"__op", "AddRelation"},
|
||||
{"objects", adds}
|
||||
};
|
||||
var removeDict = removes.Count == 0 ? null : new Dictionary<string, object> {
|
||||
{"__op", "RemoveRelation"},
|
||||
{"objects", removes}
|
||||
};
|
||||
|
||||
if (addDict != null && removeDict != null) {
|
||||
return new Dictionary<string, object> {
|
||||
{"__op", "Batch"},
|
||||
{"ops", new[] {addDict, removeDict}}
|
||||
};
|
||||
}
|
||||
return addDict ?? removeDict;
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) {
|
||||
if (previous == null) {
|
||||
return this;
|
||||
}
|
||||
if (previous is AVDeleteOperation) {
|
||||
throw new InvalidOperationException("You can't modify a relation after deleting it.");
|
||||
}
|
||||
var other = previous as AVRelationOperation;
|
||||
if (other != null) {
|
||||
if (other.TargetClassName != TargetClassName) {
|
||||
throw new InvalidOperationException(
|
||||
string.Format("Related object must be of class {0}, but {1} was passed in.",
|
||||
other.TargetClassName,
|
||||
TargetClassName));
|
||||
}
|
||||
var newAdd = adds.Union(other.adds.Except(removes)).ToList();
|
||||
var newRemove = removes.Union(other.removes.Except(adds)).ToList();
|
||||
return new AVRelationOperation(newAdd, newRemove, TargetClassName);
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
if (adds.Count == 0 && removes.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
if (oldValue == null) {
|
||||
return AVRelationBase.CreateRelation(null, key, targetClassName);
|
||||
}
|
||||
if (oldValue is AVRelationBase) {
|
||||
var oldRelation = (AVRelationBase)oldValue;
|
||||
var oldClassName = oldRelation.TargetClassName;
|
||||
if (oldClassName != null && oldClassName != targetClassName) {
|
||||
throw new InvalidOperationException("Related object must be a " + oldClassName
|
||||
+ ", but a " + targetClassName + " was passed in.");
|
||||
}
|
||||
oldRelation.TargetClassName = targetClassName;
|
||||
return oldRelation;
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public string TargetClassName { get { return targetClassName; } }
|
||||
|
||||
private IEnumerable<string> IdsFromObjects(IEnumerable<AVObject> objects) {
|
||||
foreach (var obj in objects) {
|
||||
if (obj.ObjectId == null) {
|
||||
throw new ArgumentException(
|
||||
"You can't add an unsaved AVObject to a relation.");
|
||||
}
|
||||
if (obj.ClassName != targetClassName) {
|
||||
throw new ArgumentException(string.Format(
|
||||
"Tried to create a AVRelation with 2 different types: {0} and {1}",
|
||||
targetClassName,
|
||||
obj.ClassName));
|
||||
}
|
||||
}
|
||||
return objects.Select(o => o.ObjectId).Distinct();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVRemoveOperation : IAVFieldOperation
|
||||
{
|
||||
private ReadOnlyCollection<object> objects;
|
||||
public AVRemoveOperation(IEnumerable<object> objects)
|
||||
{
|
||||
this.objects = new ReadOnlyCollection<object>(objects.Distinct().ToList());
|
||||
}
|
||||
|
||||
public object Encode()
|
||||
{
|
||||
return new Dictionary<string, object> {
|
||||
{"__op", "Remove"},
|
||||
{"objects", PointerOrLocalIdEncoder.Instance.Encode(objects)}
|
||||
};
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous)
|
||||
{
|
||||
if (previous == null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
if (previous is AVDeleteOperation)
|
||||
{
|
||||
return previous;
|
||||
}
|
||||
if (previous is AVSetOperation)
|
||||
{
|
||||
var setOp = (AVSetOperation)previous;
|
||||
var oldList = Conversion.As<IList<object>>(setOp.Value);
|
||||
return new AVSetOperation(this.Apply(oldList, null));
|
||||
}
|
||||
if (previous is AVRemoveOperation)
|
||||
{
|
||||
var oldOp = (AVRemoveOperation)previous;
|
||||
return new AVRemoveOperation(oldOp.Objects.Concat(objects));
|
||||
}
|
||||
throw new InvalidOperationException("Operation is invalid after previous operation.");
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key)
|
||||
{
|
||||
if (oldValue == null)
|
||||
{
|
||||
return new List<object>();
|
||||
}
|
||||
var oldList = Conversion.As<IList<object>>(oldValue);
|
||||
return oldList.Except(objects, AVFieldOperations.AVObjectComparer).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<object> Objects
|
||||
{
|
||||
get
|
||||
{
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVSetOperation : IAVFieldOperation {
|
||||
public AVSetOperation(object value) {
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public object Encode() {
|
||||
return PointerOrLocalIdEncoder.Instance.Encode(Value);
|
||||
}
|
||||
|
||||
public IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public object Apply(object oldValue, string key) {
|
||||
return Value;
|
||||
}
|
||||
|
||||
public object Value { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// A AVFieldOperation represents a modification to a value in a AVObject.
|
||||
/// For example, setting, deleting, or incrementing a value are all different kinds of
|
||||
/// AVFieldOperations. AVFieldOperations themselves can be considered to be
|
||||
/// immutable.
|
||||
/// </summary>
|
||||
public interface IAVFieldOperation {
|
||||
/// <summary>
|
||||
/// Converts the AVFieldOperation to a data structure that can be converted to JSON and sent to
|
||||
/// LeanCloud as part of a save operation.
|
||||
/// </summary>
|
||||
/// <returns>An object to be JSONified.</returns>
|
||||
object Encode();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a field operation that is composed of a previous operation followed by
|
||||
/// this operation. This will not mutate either operation. However, it may return
|
||||
/// <code>this</code> if the current operation is not affected by previous changes.
|
||||
/// For example:
|
||||
/// {increment by 2}.MergeWithPrevious({set to 5}) -> {set to 7}
|
||||
/// {set to 5}.MergeWithPrevious({increment by 2}) -> {set to 5}
|
||||
/// {add "foo"}.MergeWithPrevious({delete}) -> {set to ["foo"]}
|
||||
/// {delete}.MergeWithPrevious({add "foo"}) -> {delete} /// </summary>
|
||||
/// <param name="previous">The most recent operation on the field, or null if none.</param>
|
||||
/// <returns>A new AVFieldOperation or this.</returns>
|
||||
IAVFieldOperation MergeWithPrevious(IAVFieldOperation previous);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new estimated value based on a previous value and this operation. This
|
||||
/// value is not intended to be sent to LeanCloud, but it is used locally on the client to
|
||||
/// inspect the most likely current value for a field.
|
||||
///
|
||||
/// The key and object are used solely for AVRelation to be able to construct objects
|
||||
/// that refer back to their parents.
|
||||
/// </summary>
|
||||
/// <param name="oldValue">The previous value for the field.</param>
|
||||
/// <param name="key">The key that this value is for.</param>
|
||||
/// <returns>The new value for the field.</returns>
|
||||
object Apply(object oldValue, string key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
internal class AVQueryController : IAVQueryController
|
||||
{
|
||||
private readonly IAVCommandRunner commandRunner;
|
||||
|
||||
public AVQueryController(IAVCommandRunner commandRunner)
|
||||
{
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<IObjectState>> FindAsync<T>(AVQuery<T> query,
|
||||
AVUser user,
|
||||
CancellationToken cancellationToken) where T : AVObject
|
||||
{
|
||||
string sessionToken = user != null ? user.SessionToken : null;
|
||||
|
||||
return FindAsync(query.RelativeUri, query.BuildParameters(), sessionToken, cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var items = t.Result["results"] as IList<object>;
|
||||
|
||||
return (from item in items
|
||||
select AVObjectCoder.Instance.Decode(item as IDictionary<string, object>, AVDecoder.Instance));
|
||||
});
|
||||
}
|
||||
|
||||
public Task<int> CountAsync<T>(AVQuery<T> query,
|
||||
AVUser user,
|
||||
CancellationToken cancellationToken) where T : AVObject
|
||||
{
|
||||
string sessionToken = user != null ? user.SessionToken : null;
|
||||
var parameters = query.BuildParameters();
|
||||
parameters["limit"] = 0;
|
||||
parameters["count"] = 1;
|
||||
|
||||
return FindAsync(query.RelativeUri, parameters, sessionToken, cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
return Convert.ToInt32(t.Result["count"]);
|
||||
});
|
||||
}
|
||||
|
||||
public Task<IObjectState> FirstAsync<T>(AVQuery<T> query,
|
||||
AVUser user,
|
||||
CancellationToken cancellationToken) where T : AVObject
|
||||
{
|
||||
string sessionToken = user != null ? user.SessionToken : null;
|
||||
var parameters = query.BuildParameters();
|
||||
parameters["limit"] = 1;
|
||||
|
||||
return FindAsync(query.RelativeUri, parameters, sessionToken, cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var items = t.Result["results"] as IList<object>;
|
||||
var item = items.FirstOrDefault() as IDictionary<string, object>;
|
||||
|
||||
// Not found. Return empty state.
|
||||
if (item == null)
|
||||
{
|
||||
return (IObjectState)null;
|
||||
}
|
||||
|
||||
return AVObjectCoder.Instance.Decode(item, AVDecoder.Instance);
|
||||
});
|
||||
}
|
||||
|
||||
private Task<IDictionary<string, object>> FindAsync(string relativeUri,
|
||||
IDictionary<string, object> parameters,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
var command = new AVCommand(string.Format("{0}?{1}",
|
||||
relativeUri,
|
||||
AVClient.BuildQueryString(parameters)),
|
||||
method: "GET",
|
||||
sessionToken: sessionToken,
|
||||
data: null);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
return t.Result.Item2;
|
||||
});
|
||||
}
|
||||
|
||||
//private Task<IDictionary<string, object>> FindAsync(string className,
|
||||
// IDictionary<string, object> parameters,
|
||||
// string sessionToken,
|
||||
// CancellationToken cancellationToken)
|
||||
//{
|
||||
// var command = new AVCommand(string.Format("classes/{0}?{1}",
|
||||
// Uri.EscapeDataString(className),
|
||||
// AVClient.BuildQueryString(parameters)),
|
||||
// method: "GET",
|
||||
// sessionToken: sessionToken,
|
||||
// data: null);
|
||||
|
||||
// return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
// {
|
||||
// return t.Result.Item2;
|
||||
// });
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IAVQueryController {
|
||||
Task<IEnumerable<IObjectState>> FindAsync<T>(AVQuery<T> query,
|
||||
AVUser user,
|
||||
CancellationToken cancellationToken) where T : AVObject;
|
||||
|
||||
Task<int> CountAsync<T>(AVQuery<T> query,
|
||||
AVUser user,
|
||||
CancellationToken cancellationToken) where T : AVObject;
|
||||
|
||||
Task<IObjectState> FirstAsync<T>(AVQuery<T> query,
|
||||
AVUser user,
|
||||
CancellationToken cancellationToken) where T : AVObject;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class AVSessionController : IAVSessionController {
|
||||
private readonly IAVCommandRunner commandRunner;
|
||||
|
||||
public AVSessionController(IAVCommandRunner commandRunner) {
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
|
||||
public Task<IObjectState> GetSessionAsync(string sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand("sessions/me",
|
||||
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 RevokeAsync(string sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand("logout",
|
||||
method: "POST",
|
||||
sessionToken: sessionToken,
|
||||
data: new Dictionary<string, object>());
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IObjectState> UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand("upgradeToRevocableSession",
|
||||
method: "POST",
|
||||
sessionToken: sessionToken,
|
||||
data: new Dictionary<string, object>());
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t => {
|
||||
return AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance);
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsRevocableSessionToken(string sessionToken) {
|
||||
return sessionToken.Contains("r:");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IAVSessionController {
|
||||
Task<IObjectState> GetSessionAsync(string sessionToken, CancellationToken cancellationToken);
|
||||
|
||||
Task RevokeAsync(string sessionToken, CancellationToken cancellationToken);
|
||||
|
||||
Task<IObjectState> UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken);
|
||||
|
||||
bool IsRevocableSessionToken(string sessionToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// An abstraction for accessing persistent storage in the LeanCloud SDK.
|
||||
/// </summary>
|
||||
public interface IStorageController {
|
||||
/// <summary>
|
||||
/// Load the contents of this storage controller asynchronously.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<IStorageDictionary<string, object>> LoadAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Overwrites the contents of this storage controller asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="contents"></param>
|
||||
/// <returns></returns>
|
||||
Task<IStorageDictionary<string, object>> SaveAsync(IDictionary<string, object> contents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface for a dictionary that is persisted to disk asynchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">They key type of the dictionary.</typeparam>
|
||||
/// <typeparam name="TValue">The value type of the dictionary.</typeparam>
|
||||
public interface IStorageDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> {
|
||||
int Count { get; }
|
||||
TValue this[TKey key] { get; }
|
||||
|
||||
IEnumerable<TKey> Keys { get; }
|
||||
IEnumerable<TValue> Values { get; }
|
||||
|
||||
bool ContainsKey(TKey key);
|
||||
bool TryGetValue(TKey key, out TValue value);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key to this dictionary, and saves it asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to insert.</param>
|
||||
/// <param name="value">The value to insert.</param>
|
||||
/// <returns></returns>
|
||||
Task AddAsync(TKey key, TValue value);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a key from this dictionary, and saves it asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
Task RemoveAsync(TKey key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements `IStorageController` for PCL targets, based off of PCLStorage.
|
||||
/// </summary>
|
||||
public class StorageController : IStorageController
|
||||
{
|
||||
private class StorageDictionary : IStorageDictionary<string, object>
|
||||
{
|
||||
private readonly string filePath;
|
||||
|
||||
private Dictionary<string, object> dictionary;
|
||||
readonly ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
|
||||
|
||||
public StorageDictionary(string filePath)
|
||||
{
|
||||
this.filePath = filePath;
|
||||
dictionary = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
internal Task SaveAsync()
|
||||
{
|
||||
string json;
|
||||
locker.EnterReadLock();
|
||||
json = Json.Encode(dictionary);
|
||||
locker.ExitReadLock();
|
||||
using (var sw = new StreamWriter(filePath)) {
|
||||
return sw.WriteAsync(json);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task LoadAsync()
|
||||
{
|
||||
using (var sr = new StreamReader(filePath)) {
|
||||
var text = await sr.ReadToEndAsync();
|
||||
Dictionary<string, object> result = null;
|
||||
try {
|
||||
result = Json.Parse(text) as Dictionary<string, object>;
|
||||
} catch (Exception e) {
|
||||
AVClient.PrintLog(e.Message);
|
||||
}
|
||||
|
||||
locker.EnterWriteLock();
|
||||
dictionary = result ?? new Dictionary<string, object>();
|
||||
locker.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update(IDictionary<string, object> contents)
|
||||
{
|
||||
locker.EnterWriteLock();
|
||||
dictionary = contents.ToDictionary(p => p.Key, p => p.Value);
|
||||
locker.ExitWriteLock();
|
||||
}
|
||||
|
||||
public Task AddAsync(string key, object value)
|
||||
{
|
||||
locker.EnterWriteLock();
|
||||
dictionary[key] = value;
|
||||
locker.ExitWriteLock();
|
||||
return SaveAsync();
|
||||
}
|
||||
|
||||
public Task RemoveAsync(string key)
|
||||
{
|
||||
locker.EnterWriteLock();
|
||||
dictionary.Remove(key);
|
||||
locker.ExitWriteLock();
|
||||
return SaveAsync();
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
try {
|
||||
locker.EnterReadLock();
|
||||
return dictionary.ContainsKey(key);
|
||||
} finally {
|
||||
locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> Keys
|
||||
{
|
||||
get {
|
||||
try {
|
||||
locker.EnterReadLock();
|
||||
return dictionary.Keys;
|
||||
} finally {
|
||||
locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out object value)
|
||||
{
|
||||
try {
|
||||
locker.EnterReadLock();
|
||||
return dictionary.TryGetValue(key, out value);
|
||||
} finally {
|
||||
locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<object> Values
|
||||
{
|
||||
get {
|
||||
try {
|
||||
locker.EnterReadLock();
|
||||
return dictionary.Values;
|
||||
} finally {
|
||||
locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object this[string key]
|
||||
{
|
||||
get {
|
||||
try {
|
||||
locker.EnterReadLock();
|
||||
return dictionary[key];
|
||||
} finally {
|
||||
locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get {
|
||||
try {
|
||||
locker.EnterReadLock();
|
||||
return dictionary.Count;
|
||||
} finally {
|
||||
locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
try {
|
||||
locker.EnterReadLock();
|
||||
return dictionary.GetEnumerator();
|
||||
} finally {
|
||||
locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
try {
|
||||
locker.EnterReadLock();
|
||||
return dictionary.GetEnumerator();
|
||||
} finally {
|
||||
locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const string LeanCloudStorageFileName = "ApplicationSettings";
|
||||
private readonly TaskQueue taskQueue = new TaskQueue();
|
||||
private readonly Task<string> fileTask;
|
||||
private StorageDictionary storageDictionary;
|
||||
|
||||
public StorageController(string fileNamePrefix)
|
||||
{
|
||||
fileTask = taskQueue.Enqueue(t => t.ContinueWith(_ =>
|
||||
{
|
||||
string path = $"{fileNamePrefix}_{LeanCloudStorageFileName}";
|
||||
File.CreateText(path);
|
||||
return path;
|
||||
}), CancellationToken.None);
|
||||
}
|
||||
|
||||
public Task<IStorageDictionary<string, object>> LoadAsync()
|
||||
{
|
||||
return taskQueue.Enqueue(toAwait =>
|
||||
{
|
||||
return toAwait.ContinueWith(_ =>
|
||||
{
|
||||
if (storageDictionary != null)
|
||||
{
|
||||
return Task.FromResult<IStorageDictionary<string, object>>(storageDictionary);
|
||||
}
|
||||
|
||||
storageDictionary = new StorageDictionary(fileTask.Result);
|
||||
return storageDictionary.LoadAsync().OnSuccess(__ => storageDictionary as IStorageDictionary<string, object>);
|
||||
}).Unwrap();
|
||||
}, CancellationToken.None);
|
||||
}
|
||||
|
||||
public Task<IStorageDictionary<string, object>> SaveAsync(IDictionary<string, object> contents)
|
||||
{
|
||||
return taskQueue.Enqueue(toAwait =>
|
||||
{
|
||||
return toAwait.ContinueWith(_ =>
|
||||
{
|
||||
if (storageDictionary == null)
|
||||
{
|
||||
storageDictionary = new StorageDictionary(fileTask.Result);
|
||||
}
|
||||
|
||||
storageDictionary.Update(contents);
|
||||
return storageDictionary.SaveAsync().OnSuccess(__ => storageDictionary as IStorageDictionary<string, object>);
|
||||
}).Unwrap();
|
||||
}, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVCurrentUserController : IAVCurrentUserController
|
||||
{
|
||||
private readonly object mutex = new object();
|
||||
private readonly TaskQueue taskQueue = new TaskQueue();
|
||||
|
||||
private IStorageController storageController;
|
||||
|
||||
public AVCurrentUserController(IStorageController storageController)
|
||||
{
|
||||
this.storageController = storageController;
|
||||
}
|
||||
|
||||
private AVUser currentUser;
|
||||
public AVUser CurrentUser
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
return currentUser;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
currentUser = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task SetAsync(AVUser user, CancellationToken cancellationToken)
|
||||
{
|
||||
Task saveTask = null;
|
||||
if (user == null)
|
||||
{
|
||||
saveTask = storageController
|
||||
.LoadAsync()
|
||||
.OnSuccess(t => t.Result.RemoveAsync("CurrentUser"))
|
||||
.Unwrap();
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = user.ServerDataToJSONObjectForSerialization();
|
||||
data["objectId"] = user.ObjectId;
|
||||
if (user.CreatedAt != null)
|
||||
{
|
||||
data["createdAt"] = user.CreatedAt.Value.ToString(AVClient.DateFormatStrings.First(),
|
||||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
if (user.UpdatedAt != null)
|
||||
{
|
||||
data["updatedAt"] = user.UpdatedAt.Value.ToString(AVClient.DateFormatStrings.First(),
|
||||
CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
saveTask = storageController
|
||||
.LoadAsync()
|
||||
.OnSuccess(t => t.Result.AddAsync("CurrentUser", Json.Encode(data)))
|
||||
.Unwrap();
|
||||
}
|
||||
CurrentUser = user;
|
||||
|
||||
return saveTask;
|
||||
}
|
||||
|
||||
public Task<AVUser> GetAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
AVUser cachedCurrent;
|
||||
|
||||
lock (mutex)
|
||||
{
|
||||
cachedCurrent = CurrentUser;
|
||||
}
|
||||
|
||||
if (cachedCurrent != null)
|
||||
{
|
||||
return Task<AVUser>.FromResult(cachedCurrent);
|
||||
}
|
||||
|
||||
return storageController.LoadAsync().OnSuccess(t =>
|
||||
{
|
||||
object temp;
|
||||
t.Result.TryGetValue("CurrentUser", out temp);
|
||||
var userDataString = temp as string;
|
||||
AVUser user = null;
|
||||
if (userDataString != null)
|
||||
{
|
||||
var userData = Json.Parse(userDataString) as IDictionary<string, object>;
|
||||
var state = AVObjectCoder.Instance.Decode(userData, AVDecoder.Instance);
|
||||
user = AVObject.FromState<AVUser>(state, "_User");
|
||||
}
|
||||
|
||||
CurrentUser = user;
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
public Task<bool> ExistsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (CurrentUser != null)
|
||||
{
|
||||
return Task<bool>.FromResult(true);
|
||||
}
|
||||
|
||||
return storageController.LoadAsync().OnSuccess(t => t.Result.ContainsKey("CurrentUser"));
|
||||
}
|
||||
|
||||
public bool IsCurrent(AVUser user)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
return CurrentUser == user;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearFromMemory()
|
||||
{
|
||||
CurrentUser = null;
|
||||
}
|
||||
|
||||
public void ClearFromDisk()
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
ClearFromMemory();
|
||||
|
||||
storageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync("CurrentUser"));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<string> GetCurrentSessionTokenAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return GetAsync(cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var user = t.Result;
|
||||
return user == null ? null : user.SessionToken;
|
||||
});
|
||||
}
|
||||
|
||||
public Task LogOutAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return GetAsync(cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
ClearFromDisk();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVUserController : IAVUserController
|
||||
{
|
||||
private readonly IAVCommandRunner commandRunner;
|
||||
|
||||
public AVUserController(IAVCommandRunner commandRunner)
|
||||
{
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
|
||||
public Task<IObjectState> SignUpAsync(IObjectState state,
|
||||
IDictionary<string, IAVFieldOperation> operations,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var objectJSON = AVObject.ToJSONObjectForSaving(operations);
|
||||
|
||||
var command = new AVCommand("classes/_User",
|
||||
method: "POST",
|
||||
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 = true;
|
||||
});
|
||||
return serverState;
|
||||
});
|
||||
}
|
||||
|
||||
public Task<IObjectState> LogInAsync(string username, string email,
|
||||
string password,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var data = new Dictionary<string, object>{
|
||||
{ "password", password}
|
||||
};
|
||||
if (username != null) {
|
||||
data.Add("username", username);
|
||||
}
|
||||
if (email != null) {
|
||||
data.Add("email", email);
|
||||
}
|
||||
|
||||
var command = new AVCommand("login",
|
||||
method: "POST",
|
||||
data: data);
|
||||
|
||||
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 Task<IObjectState> LogInAsync(string authType,
|
||||
IDictionary<string, object> data,
|
||||
bool failOnNotExist,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var authData = new Dictionary<string, object>();
|
||||
authData[authType] = data;
|
||||
var path = failOnNotExist ? "users?failOnNotExist=true" : "users";
|
||||
var command = new AVCommand(path,
|
||||
method: "POST",
|
||||
data: new Dictionary<string, object> {
|
||||
{ "authData", authData}
|
||||
});
|
||||
|
||||
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 Task<IObjectState> GetUserAsync(string sessionToken, CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand("users/me",
|
||||
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 RequestPasswordResetAsync(string email, CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand("requestPasswordReset",
|
||||
method: "POST",
|
||||
data: new Dictionary<string, object> {
|
||||
{ "email", email}
|
||||
});
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IObjectState> LogInWithParametersAsync(string relativeUrl, IDictionary<string, object> data,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand(string.Format("{0}", relativeUrl),
|
||||
method: "POST",
|
||||
data: data);
|
||||
|
||||
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 Task UpdatePasswordAsync(string userId, string sessionToken, string oldPassword, string newPassword, CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand(String.Format("users/{0}/updatePassword", userId),
|
||||
method: "PUT",
|
||||
sessionToken: sessionToken,
|
||||
data: new Dictionary<string, object> {
|
||||
{"old_password", oldPassword},
|
||||
{"new_password", newPassword},
|
||||
});
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public Task<IObjectState> RefreshSessionTokenAsync(string userId, string sessionToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand(String.Format("users/{0}/refreshSessionToken", userId),
|
||||
method: "PUT",
|
||||
sessionToken: sessionToken,
|
||||
data: null);
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).OnSuccess(t =>
|
||||
{
|
||||
var serverState = AVObjectCoder.Instance.Decode(t.Result.Item2, AVDecoder.Instance);
|
||||
return serverState;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IAVCurrentUserController : IAVObjectCurrentController<AVUser> {
|
||||
Task<string> GetCurrentSessionTokenAsync(CancellationToken cancellationToken);
|
||||
|
||||
Task LogOutAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IAVUserController
|
||||
{
|
||||
Task<IObjectState> SignUpAsync(IObjectState state,
|
||||
IDictionary<string, IAVFieldOperation> operations,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<IObjectState> LogInAsync(string username,
|
||||
string email,
|
||||
string password,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<IObjectState> LogInWithParametersAsync(string relativeUrl,
|
||||
IDictionary<string, object> data,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<IObjectState> LogInAsync(string authType,
|
||||
IDictionary<string, object> data,
|
||||
bool failOnNotExist,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<IObjectState> GetUserAsync(string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task RequestPasswordResetAsync(string email,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task UpdatePasswordAsync(string usedId, string sessionToken,
|
||||
string oldPassword, string newPassword,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<IObjectState> RefreshSessionTokenAsync(string userId,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc.
|
||||
///
|
||||
/// These cannot be 'internal' anymore if we are fully modularizing things out, because
|
||||
/// they are no longer a part of the same library, especially as we create things like
|
||||
/// Installation inside push library.
|
||||
///
|
||||
/// So this class contains a bunch of extension methods that can live inside another
|
||||
/// namespace, which 'wrap' the intenral APIs that already exist.
|
||||
/// </summary>
|
||||
public static class AVConfigExtensions {
|
||||
public static AVConfig Create(IDictionary<string, object> fetchedConfig) {
|
||||
return new AVConfig(fetchedConfig);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc.
|
||||
///
|
||||
/// These cannot be 'internal' anymore if we are fully modularizing things out, because
|
||||
/// they are no longer a part of the same library, especially as we create things like
|
||||
/// Installation inside push library.
|
||||
///
|
||||
/// So this class contains a bunch of extension methods that can live inside another
|
||||
/// namespace, which 'wrap' the intenral APIs that already exist.
|
||||
/// </summary>
|
||||
public static class AVFileExtensions {
|
||||
public static AVFile Create(string name, Uri uri, string mimeType = null) {
|
||||
return new AVFile(name, uri, mimeType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using System.ComponentModel;
|
||||
using System.Collections;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc.
|
||||
///
|
||||
/// These cannot be 'internal' anymore if we are fully modularizing things out, because
|
||||
/// they are no longer a part of the same library, especially as we create things like
|
||||
/// Installation inside push library.
|
||||
///
|
||||
/// So this class contains a bunch of extension methods that can live inside another
|
||||
/// namespace, which 'wrap' the intenral APIs that already exist.
|
||||
/// </summary>
|
||||
public static class AVObjectExtensions
|
||||
{
|
||||
public static T FromState<T>(IObjectState state, string defaultClassName) where T : AVObject
|
||||
{
|
||||
return AVObject.FromState<T>(state, defaultClassName);
|
||||
}
|
||||
|
||||
public static IObjectState GetState(this AVObject obj)
|
||||
{
|
||||
return obj.State;
|
||||
}
|
||||
|
||||
public static void HandleFetchResult(this AVObject obj, IObjectState serverState)
|
||||
{
|
||||
obj.HandleFetchResult(serverState);
|
||||
}
|
||||
|
||||
public static IDictionary<string, IAVFieldOperation> GetCurrentOperations(this AVObject obj)
|
||||
{
|
||||
return obj.CurrentOperations;
|
||||
}
|
||||
|
||||
public static IDictionary<string, object> Encode(this AVObject obj)
|
||||
{
|
||||
return PointerOrLocalIdEncoder.Instance.EncodeAVObject(obj, false);
|
||||
}
|
||||
|
||||
public static IEnumerable<object> DeepTraversal(object root, bool traverseAVObjects = false, bool yieldRoot = false)
|
||||
{
|
||||
return AVObject.DeepTraversal(root, traverseAVObjects, yieldRoot);
|
||||
}
|
||||
|
||||
public static void SetIfDifferent<T>(this AVObject obj, string key, T value)
|
||||
{
|
||||
obj.SetIfDifferent<T>(key, value);
|
||||
}
|
||||
|
||||
public static IDictionary<string, object> ServerDataToJSONObjectForSerialization(this AVObject obj)
|
||||
{
|
||||
return obj.ServerDataToJSONObjectForSerialization();
|
||||
}
|
||||
|
||||
public static void Set(this AVObject obj, string key, object value)
|
||||
{
|
||||
obj.Set(key, value);
|
||||
}
|
||||
|
||||
public static void DisableHooks(this AVObject obj, IEnumerable<string> hookKeys)
|
||||
{
|
||||
obj.Set("__ignore_hooks", hookKeys);
|
||||
}
|
||||
public static void DisableHook(this AVObject obj, string hookKey)
|
||||
{
|
||||
var newList = new List<string>();
|
||||
if (obj.ContainsKey("__ignore_hooks"))
|
||||
{
|
||||
var hookKeys = obj.Get<IEnumerable<string>>("__ignore_hooks");
|
||||
newList = hookKeys.ToList();
|
||||
}
|
||||
newList.Add(hookKey);
|
||||
obj.DisableHooks(newList);
|
||||
}
|
||||
|
||||
public static void DisableAfterHook(this AVObject obj)
|
||||
{
|
||||
obj.DisableAfterSave();
|
||||
obj.DisableAfterUpdate();
|
||||
obj.DisableAfterDelete();
|
||||
}
|
||||
|
||||
public static void DisableBeforeHook(this AVObject obj)
|
||||
{
|
||||
obj.DisableBeforeSave();
|
||||
obj.DisableBeforeDelete();
|
||||
obj.DisableBeforeUpdate();
|
||||
}
|
||||
|
||||
public static void DisableBeforeSave(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("beforeSave");
|
||||
}
|
||||
public static void DisableAfterSave(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("afterSave");
|
||||
}
|
||||
public static void DisableBeforeUpdate(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("beforeUpdate");
|
||||
}
|
||||
public static void DisableAfterUpdate(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("afterUpdate");
|
||||
}
|
||||
public static void DisableBeforeDelete(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("beforeDelete");
|
||||
}
|
||||
public static void DisableAfterDelete(this AVObject obj)
|
||||
{
|
||||
obj.DisableHook("afterDelete");
|
||||
}
|
||||
|
||||
#region on property updated or changed or collection updated
|
||||
|
||||
/// <summary>
|
||||
/// On the property changed.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnPropertyChanged(this AVObject avObj, string propertyName, PropertyChangedEventHandler handler)
|
||||
{
|
||||
avObj.PropertyChanged += (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == propertyName)
|
||||
{
|
||||
handler(sender, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On the property updated.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnPropertyUpdated(this AVObject avObj, string propertyName, PropertyUpdatedEventHandler handler)
|
||||
{
|
||||
avObj.PropertyUpdated += (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == propertyName)
|
||||
{
|
||||
handler(sender, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On the property updated.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnPropertyUpdated(this AVObject avObj, string propertyName, Action<object, object> handler)
|
||||
{
|
||||
avObj.OnPropertyUpdated(propertyName,(object sender, PropertyUpdatedEventArgs e) =>
|
||||
{
|
||||
handler(e.OldValue, e.NewValue);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On the collection property updated.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnCollectionPropertyUpdated(this AVObject avObj, string propertyName, CollectionPropertyUpdatedEventHandler handler)
|
||||
{
|
||||
avObj.CollectionPropertyUpdated += (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == propertyName)
|
||||
{
|
||||
handler(sender, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On the collection property added.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnCollectionPropertyAdded(this AVObject avObj, string propertyName, Action<IEnumerable> handler)
|
||||
{
|
||||
avObj.OnCollectionPropertyUpdated(propertyName, (sender, e) =>
|
||||
{
|
||||
if (e.CollectionAction == NotifyCollectionUpdatedAction.Add)
|
||||
{
|
||||
handler(e.NewValues);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On the collection property removed.
|
||||
/// </summary>
|
||||
/// <param name="avObj">Av object.</param>
|
||||
/// <param name="propertyName">Property name.</param>
|
||||
/// <param name="handler">Handler.</param>
|
||||
public static void OnCollectionPropertyRemoved(this AVObject avObj, string propertyName, Action<IEnumerable> handler)
|
||||
{
|
||||
avObj.OnCollectionPropertyUpdated(propertyName, (sender, e) =>
|
||||
{
|
||||
if (e.CollectionAction == NotifyCollectionUpdatedAction.Remove)
|
||||
{
|
||||
handler(e.NewValues);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc.
|
||||
///
|
||||
/// These cannot be 'internal' anymore if we are fully modularizing things out, because
|
||||
/// they are no longer a part of the same library, especially as we create things like
|
||||
/// Installation inside push library.
|
||||
///
|
||||
/// So this class contains a bunch of extension methods that can live inside another
|
||||
/// namespace, which 'wrap' the intenral APIs that already exist.
|
||||
/// </summary>
|
||||
public static class AVQueryExtensions {
|
||||
public static string GetClassName<T>(this AVQuery<T> query) where T: AVObject {
|
||||
return query.ClassName;
|
||||
}
|
||||
|
||||
public static IDictionary<String, object> BuildParameters<T>(this AVQuery<T> query) where T: AVObject {
|
||||
return query.BuildParameters(false);
|
||||
}
|
||||
|
||||
public static object GetConstraint<T>(this AVQuery<T> query, string key) where T : AVObject {
|
||||
return query.GetConstraint(key);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc.
|
||||
///
|
||||
/// These cannot be 'internal' anymore if we are fully modularizing things out, because
|
||||
/// they are no longer a part of the same library, especially as we create things like
|
||||
/// Installation inside push library.
|
||||
///
|
||||
/// So this class contains a bunch of extension methods that can live inside another
|
||||
/// namespace, which 'wrap' the intenral APIs that already exist.
|
||||
/// </summary>
|
||||
public static class AVRelationExtensions {
|
||||
public static AVRelation<T> Create<T>(AVObject parent, string childKey) where T : AVObject {
|
||||
return new AVRelation<T>(parent, childKey);
|
||||
}
|
||||
|
||||
public static AVRelation<T> Create<T>(AVObject parent, string childKey, string targetClassName) where T: AVObject {
|
||||
return new AVRelation<T>(parent, childKey, targetClassName);
|
||||
}
|
||||
|
||||
public static string GetTargetClassName<T>(this AVRelation<T> relation) where T : AVObject {
|
||||
return relation.TargetClassName;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc.
|
||||
///
|
||||
/// These cannot be 'internal' anymore if we are fully modularizing things out, because
|
||||
/// they are no longer a part of the same library, especially as we create things like
|
||||
/// Installation inside push library.
|
||||
///
|
||||
/// So this class contains a bunch of extension methods that can live inside another
|
||||
/// namespace, which 'wrap' the intenral APIs that already exist.
|
||||
/// </summary>
|
||||
public static class AVSessionExtensions {
|
||||
public static Task<string> UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) {
|
||||
return AVSession.UpgradeToRevocableSessionAsync(sessionToken, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) {
|
||||
return AVSession.RevokeAsync(sessionToken, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// So here's the deal. We have a lot of internal APIs for AVObject, AVUser, etc.
|
||||
///
|
||||
/// These cannot be 'internal' anymore if we are fully modularizing things out, because
|
||||
/// they are no longer a part of the same library, especially as we create things like
|
||||
/// Installation inside push library.
|
||||
///
|
||||
/// So this class contains a bunch of extension methods that can live inside another
|
||||
/// namespace, which 'wrap' the intenral APIs that already exist.
|
||||
/// </summary>
|
||||
public static class AVUserExtensions
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Provides a Dictionary implementation that can delegate to any other
|
||||
/// dictionary, regardless of its value type. Used for coercion of
|
||||
/// dictionaries when returning them to users.
|
||||
/// </summary>
|
||||
/// <typeparam name="TOut">The resulting type of value in the dictionary.</typeparam>
|
||||
/// <typeparam name="TIn">The original type of value in the dictionary.</typeparam>
|
||||
[Preserve(AllMembers = true, Conditional = false)]
|
||||
public class FlexibleDictionaryWrapper<TOut, TIn> : IDictionary<string, TOut> {
|
||||
private readonly IDictionary<string, TIn> toWrap;
|
||||
public FlexibleDictionaryWrapper(IDictionary<string, TIn> toWrap) {
|
||||
this.toWrap = toWrap;
|
||||
}
|
||||
|
||||
public void Add(string key, TOut value) {
|
||||
toWrap.Add(key, (TIn)Conversion.ConvertTo<TIn>(value));
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key) {
|
||||
return toWrap.ContainsKey(key);
|
||||
}
|
||||
|
||||
public ICollection<string> Keys {
|
||||
get { return toWrap.Keys; }
|
||||
}
|
||||
|
||||
public bool Remove(string key) {
|
||||
return toWrap.Remove(key);
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out TOut value) {
|
||||
TIn outValue;
|
||||
bool result = toWrap.TryGetValue(key, out outValue);
|
||||
value = (TOut)Conversion.ConvertTo<TOut>(outValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
public ICollection<TOut> Values {
|
||||
get {
|
||||
return toWrap.Values
|
||||
.Select(item => (TOut)Conversion.ConvertTo<TOut>(item)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public TOut this[string key] {
|
||||
get {
|
||||
return (TOut)Conversion.ConvertTo<TOut>(toWrap[key]);
|
||||
}
|
||||
set {
|
||||
toWrap[key] = (TIn)Conversion.ConvertTo<TIn>(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, TOut> item) {
|
||||
toWrap.Add(new KeyValuePair<string, TIn>(item.Key,
|
||||
(TIn)Conversion.ConvertTo<TIn>(item.Value)));
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
toWrap.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, TOut> item) {
|
||||
return toWrap.Contains(new KeyValuePair<string, TIn>(item.Key,
|
||||
(TIn)Conversion.ConvertTo<TIn>(item.Value)));
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, TOut>[] array, int arrayIndex) {
|
||||
var converted = from pair in toWrap
|
||||
select new KeyValuePair<string, TOut>(pair.Key,
|
||||
(TOut)Conversion.ConvertTo<TOut>(pair.Value));
|
||||
converted.ToList().CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count {
|
||||
get { return toWrap.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly {
|
||||
get { return toWrap.IsReadOnly; }
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, TOut> item) {
|
||||
return toWrap.Remove(new KeyValuePair<string, TIn>(item.Key,
|
||||
(TIn)Conversion.ConvertTo<TIn>(item.Value)));
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, TOut>> GetEnumerator() {
|
||||
foreach (var pair in toWrap) {
|
||||
yield return new KeyValuePair<string, TOut>(pair.Key,
|
||||
(TOut)Conversion.ConvertTo<TOut>(pair.Value));
|
||||
}
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Provides a List implementation that can delegate to any other
|
||||
/// list, regardless of its value type. Used for coercion of
|
||||
/// lists when returning them to users.
|
||||
/// </summary>
|
||||
/// <typeparam name="TOut">The resulting type of value in the list.</typeparam>
|
||||
/// <typeparam name="TIn">The original type of value in the list.</typeparam>
|
||||
[Preserve(AllMembers = true, Conditional = false)]
|
||||
public class FlexibleListWrapper<TOut, TIn> : IList<TOut> {
|
||||
private IList<TIn> toWrap;
|
||||
public FlexibleListWrapper(IList<TIn> toWrap) {
|
||||
this.toWrap = toWrap;
|
||||
}
|
||||
|
||||
public int IndexOf(TOut item) {
|
||||
return toWrap.IndexOf((TIn)Conversion.ConvertTo<TIn>(item));
|
||||
}
|
||||
|
||||
public void Insert(int index, TOut item) {
|
||||
toWrap.Insert(index, (TIn)Conversion.ConvertTo<TIn>(item));
|
||||
}
|
||||
|
||||
public void RemoveAt(int index) {
|
||||
toWrap.RemoveAt(index);
|
||||
}
|
||||
|
||||
public TOut this[int index] {
|
||||
get {
|
||||
return (TOut)Conversion.ConvertTo<TOut>(toWrap[index]);
|
||||
}
|
||||
set {
|
||||
toWrap[index] = (TIn)Conversion.ConvertTo<TIn>(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TOut item) {
|
||||
toWrap.Add((TIn)Conversion.ConvertTo<TIn>(item));
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
toWrap.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(TOut item) {
|
||||
return toWrap.Contains((TIn)Conversion.ConvertTo<TIn>(item));
|
||||
}
|
||||
|
||||
public void CopyTo(TOut[] array, int arrayIndex) {
|
||||
toWrap.Select(item => (TOut)Conversion.ConvertTo<TOut>(item))
|
||||
.ToList().CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count {
|
||||
get { return toWrap.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly {
|
||||
get { return toWrap.IsReadOnly; }
|
||||
}
|
||||
|
||||
public bool Remove(TOut item) {
|
||||
return toWrap.Remove((TIn)Conversion.ConvertTo<TIn>(item));
|
||||
}
|
||||
|
||||
public IEnumerator<TOut> GetEnumerator() {
|
||||
foreach (var item in (IEnumerable)toWrap) {
|
||||
yield return (TOut)Conversion.ConvertTo<TOut>(item);
|
||||
}
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Represents an object that can be converted into JSON.
|
||||
/// </summary>
|
||||
public interface IJsonConvertible {
|
||||
/// <summary>
|
||||
/// Converts the object to a data structure that can be converted to JSON.
|
||||
/// </summary>
|
||||
/// <returns>An object to be JSONified.</returns>
|
||||
IDictionary<string, object> ToJSON();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// An equality comparer that uses the object identity (i.e. ReferenceEquals)
|
||||
/// rather than .Equals, allowing identity to be used for checking equality in
|
||||
/// ISets and IDictionaries.
|
||||
/// </summary>
|
||||
public class IdentityEqualityComparer<T> : IEqualityComparer<T>
|
||||
{
|
||||
public bool Equals(T x, T y)
|
||||
{
|
||||
return object.ReferenceEquals(x, y);
|
||||
}
|
||||
|
||||
public int GetHashCode(T obj)
|
||||
{
|
||||
return RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Provides helper methods that allow us to use terser code elsewhere.
|
||||
/// </summary>
|
||||
public static class InternalExtensions {
|
||||
/// <summary>
|
||||
/// Ensures a task (even null) is awaitable.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="task"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<T> Safe<T>(this Task<T> task) {
|
||||
return task ?? Task.FromResult<T>(default(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures a task (even null) is awaitable.
|
||||
/// </summary>
|
||||
/// <param name="task"></param>
|
||||
/// <returns></returns>
|
||||
public static Task Safe(this Task task) {
|
||||
return task ?? Task.FromResult<object>(null);
|
||||
}
|
||||
|
||||
public delegate void PartialAccessor<T>(ref T arg);
|
||||
|
||||
public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> self,
|
||||
TKey key,
|
||||
TValue defaultValue) {
|
||||
TValue value;
|
||||
if (self.TryGetValue(key, out value)) {
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static bool CollectionsEqual<T>(this IEnumerable<T> a, IEnumerable<T> b) {
|
||||
return Object.Equals(a, b) ||
|
||||
(a != null && b != null &&
|
||||
a.SequenceEqual(b));
|
||||
}
|
||||
|
||||
public static Task<TResult> OnSuccess<TIn, TResult>(this Task<TIn> task,
|
||||
Func<Task<TIn>, TResult> continuation) {
|
||||
return ((Task)task).OnSuccess(t => continuation((Task<TIn>)t));
|
||||
}
|
||||
|
||||
public static Task OnSuccess<TIn>(this Task<TIn> task, Action<Task<TIn>> continuation) {
|
||||
return task.OnSuccess((Func<Task<TIn>, object>)(t => {
|
||||
continuation(t);
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
public static Task<TResult> OnSuccess<TResult>(this Task task,
|
||||
Func<Task, TResult> continuation) {
|
||||
return task.ContinueWith(t => {
|
||||
if (t.IsFaulted) {
|
||||
var ex = t.Exception.Flatten();
|
||||
if (ex.InnerExceptions.Count == 1) {
|
||||
ExceptionDispatchInfo.Capture(ex.InnerExceptions[0]).Throw();
|
||||
} else {
|
||||
ExceptionDispatchInfo.Capture(ex).Throw();
|
||||
}
|
||||
// Unreachable
|
||||
return Task.FromResult(default(TResult));
|
||||
} else if (t.IsCanceled) {
|
||||
var tcs = new TaskCompletionSource<TResult>();
|
||||
tcs.SetCanceled();
|
||||
return tcs.Task;
|
||||
} else {
|
||||
return Task.FromResult(continuation(t));
|
||||
}
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
public static Task OnSuccess(this Task task, Action<Task> continuation) {
|
||||
return task.OnSuccess((Func<Task, object>)(t => {
|
||||
continuation(t);
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
public static Task WhileAsync(Func<Task<bool>> predicate, Func<Task> body) {
|
||||
Func<Task> iterate = null;
|
||||
iterate = () => {
|
||||
return predicate().OnSuccess(t => {
|
||||
if (!t.Result) {
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
return body().OnSuccess(_ => iterate()).Unwrap();
|
||||
}).Unwrap();
|
||||
};
|
||||
return iterate();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,554 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple recursive-descent JSON Parser based on the grammar defined at http://www.json.org
|
||||
/// and http://tools.ietf.org/html/rfc4627
|
||||
/// </summary>
|
||||
public class Json
|
||||
{
|
||||
/// <summary>
|
||||
/// Place at the start of a regex to force the match to begin wherever the search starts (i.e.
|
||||
/// anchored at the index of the first character of the search, even when that search starts
|
||||
/// in the middle of the string).
|
||||
/// </summary>
|
||||
private static readonly string startOfString = "\\G";
|
||||
private static readonly char startObject = '{';
|
||||
private static readonly char endObject = '}';
|
||||
private static readonly char startArray = '[';
|
||||
private static readonly char endArray = ']';
|
||||
private static readonly char valueSeparator = ',';
|
||||
private static readonly char nameSeparator = ':';
|
||||
private static readonly char[] falseValue = "false".ToCharArray();
|
||||
private static readonly char[] trueValue = "true".ToCharArray();
|
||||
private static readonly char[] nullValue = "null".ToCharArray();
|
||||
private static readonly Regex numberValue = new Regex(startOfString +
|
||||
@"-?(?:0|[1-9]\d*)(?<frac>\.\d+)?(?<exp>(?:e|E)(?:-|\+)?\d+)?");
|
||||
private static readonly Regex stringValue = new Regex(startOfString +
|
||||
"\"(?<content>(?:[^\\\\\"]|(?<escape>\\\\(?:[\\\\\"/bfnrt]|u[0-9a-fA-F]{4})))*)\"",
|
||||
RegexOptions.Multiline);
|
||||
|
||||
private static readonly Regex escapePattern = new Regex("\\\\|\"|[\u0000-\u001F]");
|
||||
|
||||
private class JsonStringParser
|
||||
{
|
||||
public string Input { get; private set; }
|
||||
|
||||
public char[] InputAsArray { get; private set; }
|
||||
|
||||
private int currentIndex;
|
||||
public int CurrentIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return currentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public void Skip(int skip)
|
||||
{
|
||||
currentIndex += skip;
|
||||
}
|
||||
|
||||
public JsonStringParser(string input)
|
||||
{
|
||||
Input = input;
|
||||
InputAsArray = input.ToCharArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses JSON object syntax (e.g. '{}')
|
||||
/// </summary>
|
||||
internal bool AVObject(out object output)
|
||||
{
|
||||
output = null;
|
||||
int initialCurrentIndex = CurrentIndex;
|
||||
if (!Accept(startObject))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var dict = new Dictionary<string, object>();
|
||||
while (true)
|
||||
{
|
||||
object pairValue;
|
||||
if (!ParseMember(out pairValue))
|
||||
{
|
||||
break;
|
||||
}
|
||||
var pair = pairValue as Tuple<string, object>;
|
||||
dict[pair.Item1] = pair.Item2;
|
||||
if (!Accept(valueSeparator))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!Accept(endObject))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
output = dict;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses JSON member syntax (e.g. '"keyname" : null')
|
||||
/// </summary>
|
||||
private bool ParseMember(out object output)
|
||||
{
|
||||
output = null;
|
||||
object key;
|
||||
if (!ParseString(out key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!Accept(nameSeparator))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
object value;
|
||||
if (!ParseValue(out value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
output = new Tuple<string, object>((string)key, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses JSON array syntax (e.g. '[]')
|
||||
/// </summary>
|
||||
internal bool ParseArray(out object output)
|
||||
{
|
||||
output = null;
|
||||
if (!Accept(startArray))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var list = new List<object>();
|
||||
while (true)
|
||||
{
|
||||
object value;
|
||||
if (!ParseValue(out value))
|
||||
{
|
||||
break;
|
||||
}
|
||||
list.Add(value);
|
||||
if (!Accept(valueSeparator))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!Accept(endArray))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
output = list;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a value (i.e. the right-hand side of an object member assignment or
|
||||
/// an element in an array)
|
||||
/// </summary>
|
||||
private bool ParseValue(out object output)
|
||||
{
|
||||
if (Accept(falseValue))
|
||||
{
|
||||
output = false;
|
||||
return true;
|
||||
}
|
||||
else if (Accept(nullValue))
|
||||
{
|
||||
output = null;
|
||||
return true;
|
||||
}
|
||||
else if (Accept(trueValue))
|
||||
{
|
||||
output = true;
|
||||
return true;
|
||||
}
|
||||
return AVObject(out output) ||
|
||||
ParseArray(out output) ||
|
||||
ParseNumber(out output) ||
|
||||
ParseString(out output);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a JSON string (e.g. '"foo\u1234bar\n"')
|
||||
/// </summary>
|
||||
private bool ParseString(out object output)
|
||||
{
|
||||
output = null;
|
||||
Match m;
|
||||
if (!Accept(stringValue, out m))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// handle escapes:
|
||||
int offset = 0;
|
||||
var contentCapture = m.Groups["content"];
|
||||
var builder = new StringBuilder(contentCapture.Value);
|
||||
foreach (Capture escape in m.Groups["escape"].Captures)
|
||||
{
|
||||
int index = (escape.Index - contentCapture.Index) - offset;
|
||||
offset += escape.Length - 1;
|
||||
builder.Remove(index + 1, escape.Length - 1);
|
||||
switch (escape.Value[1])
|
||||
{
|
||||
case '\"':
|
||||
builder[index] = '\"';
|
||||
break;
|
||||
case '\\':
|
||||
builder[index] = '\\';
|
||||
break;
|
||||
case '/':
|
||||
builder[index] = '/';
|
||||
break;
|
||||
case 'b':
|
||||
builder[index] = '\b';
|
||||
break;
|
||||
case 'f':
|
||||
builder[index] = '\f';
|
||||
break;
|
||||
case 'n':
|
||||
builder[index] = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
builder[index] = '\r';
|
||||
break;
|
||||
case 't':
|
||||
builder[index] = '\t';
|
||||
break;
|
||||
case 'u':
|
||||
builder[index] = (char)ushort.Parse(escape.Value.Substring(2), NumberStyles.AllowHexSpecifier);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unexpected escape character in string: " + escape.Value);
|
||||
}
|
||||
}
|
||||
output = builder.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a number. Returns a long if the number is an integer or has an exponent,
|
||||
/// otherwise returns a double.
|
||||
/// </summary>
|
||||
private bool ParseNumber(out object output)
|
||||
{
|
||||
output = null;
|
||||
Match m;
|
||||
if (!Accept(numberValue, out m))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (m.Groups["frac"].Length > 0 || m.Groups["exp"].Length > 0)
|
||||
{
|
||||
// It's a double.
|
||||
output = double.Parse(m.Value, CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int temp = 0;
|
||||
if (int.TryParse(m.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out temp))
|
||||
{
|
||||
output = temp;
|
||||
return true;
|
||||
}
|
||||
output = long.Parse(m.Value, CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Matches the string to a regex, consuming part of the string and returning the match.
|
||||
/// </summary>
|
||||
private bool Accept(Regex matcher, out Match match)
|
||||
{
|
||||
match = matcher.Match(Input, CurrentIndex);
|
||||
if (match.Success)
|
||||
{
|
||||
Skip(match.Length);
|
||||
}
|
||||
return match.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the first occurrences of a character, consuming part of the string.
|
||||
/// </summary>
|
||||
private bool Accept(char condition)
|
||||
{
|
||||
int step = 0;
|
||||
int strLen = InputAsArray.Length;
|
||||
int currentStep = currentIndex;
|
||||
char currentChar;
|
||||
|
||||
// Remove whitespace
|
||||
while (currentStep < strLen &&
|
||||
((currentChar = InputAsArray[currentStep]) == ' ' ||
|
||||
currentChar == '\r' ||
|
||||
currentChar == '\t' ||
|
||||
currentChar == '\n'))
|
||||
{
|
||||
++step;
|
||||
++currentStep;
|
||||
}
|
||||
|
||||
bool match = (currentStep < strLen) && (InputAsArray[currentStep] == condition);
|
||||
if (match)
|
||||
{
|
||||
++step;
|
||||
++currentStep;
|
||||
|
||||
// Remove whitespace
|
||||
while (currentStep < strLen &&
|
||||
((currentChar = InputAsArray[currentStep]) == ' ' ||
|
||||
currentChar == '\r' ||
|
||||
currentChar == '\t' ||
|
||||
currentChar == '\n'))
|
||||
{
|
||||
++step;
|
||||
++currentStep;
|
||||
}
|
||||
|
||||
Skip(step);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the first occurrences of a string, consuming part of the string.
|
||||
/// </summary>
|
||||
private bool Accept(char[] condition)
|
||||
{
|
||||
int step = 0;
|
||||
int strLen = InputAsArray.Length;
|
||||
int currentStep = currentIndex;
|
||||
char currentChar;
|
||||
|
||||
// Remove whitespace
|
||||
while (currentStep < strLen &&
|
||||
((currentChar = InputAsArray[currentStep]) == ' ' ||
|
||||
currentChar == '\r' ||
|
||||
currentChar == '\t' ||
|
||||
currentChar == '\n'))
|
||||
{
|
||||
++step;
|
||||
++currentStep;
|
||||
}
|
||||
|
||||
bool strMatch = true;
|
||||
for (int i = 0; currentStep < strLen && i < condition.Length; ++i, ++currentStep)
|
||||
{
|
||||
if (InputAsArray[currentStep] != condition[i])
|
||||
{
|
||||
strMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool match = (currentStep < strLen) && strMatch;
|
||||
if (match)
|
||||
{
|
||||
Skip(step + condition.Length);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a JSON-text as defined in http://tools.ietf.org/html/rfc4627, returning an
|
||||
/// IDictionary<string, object> or an IList<object> depending on whether
|
||||
/// the value was an array or dictionary. Nested objects also match these types.
|
||||
/// </summary>
|
||||
public static object Parse(string input)
|
||||
{
|
||||
object output;
|
||||
input = input.Trim();
|
||||
JsonStringParser parser = new JsonStringParser(input);
|
||||
|
||||
if ((parser.AVObject(out output) ||
|
||||
parser.ParseArray(out output)) &&
|
||||
parser.CurrentIndex == input.Length)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
throw new ArgumentException("Input JSON was invalid.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a dictionary into a JSON string. Supports values that are
|
||||
/// IDictionary<string, object>, IList<object>, strings,
|
||||
/// nulls, and any of the primitive types.
|
||||
/// </summary>
|
||||
public static string Encode(IDictionary<string, object> dict)
|
||||
{
|
||||
if (dict == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (dict.Count == 0)
|
||||
{
|
||||
return "{}";
|
||||
}
|
||||
var builder = new StringBuilder("{");
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
builder.Append(Encode(pair.Key));
|
||||
builder.Append(":");
|
||||
builder.Append(Encode(pair.Value));
|
||||
builder.Append(",");
|
||||
}
|
||||
builder[builder.Length - 1] = '}';
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a list into a JSON string. Supports values that are
|
||||
/// IDictionary<string, object>, IList<object>, strings,
|
||||
/// nulls, and any of the primitive types.
|
||||
/// </summary>
|
||||
public static string Encode(IList<object> list)
|
||||
{
|
||||
if (list == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (list.Count == 0)
|
||||
{
|
||||
return "[]";
|
||||
}
|
||||
var builder = new StringBuilder("[");
|
||||
foreach (var item in list)
|
||||
{
|
||||
builder.Append(Encode(item));
|
||||
builder.Append(",");
|
||||
}
|
||||
builder[builder.Length - 1] = ']';
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static string Encode(IList<string> strList)
|
||||
{
|
||||
if (strList == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (strList.Count == 0)
|
||||
{
|
||||
return "[]";
|
||||
}
|
||||
StringBuilder stringBuilder = new StringBuilder("[");
|
||||
foreach (object obj in strList)
|
||||
{
|
||||
stringBuilder.Append(Json.Encode(obj));
|
||||
stringBuilder.Append(",");
|
||||
}
|
||||
stringBuilder[stringBuilder.Length - 1] = ']';
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
public static string Encode(IList<IDictionary<string, object>> dicList)
|
||||
{
|
||||
if (dicList == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (dicList.Count == 0)
|
||||
{
|
||||
return "[]";
|
||||
}
|
||||
StringBuilder stringBuilder = new StringBuilder("[");
|
||||
foreach (object obj in dicList)
|
||||
{
|
||||
stringBuilder.Append(Json.Encode(obj));
|
||||
stringBuilder.Append(",");
|
||||
}
|
||||
stringBuilder[stringBuilder.Length - 1] = ']';
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes an object into a JSON string.
|
||||
/// </summary>
|
||||
public static string Encode(object obj)
|
||||
{
|
||||
var dict = obj as IDictionary<string, object>;
|
||||
if (dict != null)
|
||||
{
|
||||
return Encode(dict);
|
||||
}
|
||||
var list = obj as IList<object>;
|
||||
if (list != null)
|
||||
{
|
||||
return Encode(list);
|
||||
}
|
||||
var dicList = obj as IList<IDictionary<string, object>>;
|
||||
if (dicList != null)
|
||||
{
|
||||
return Encode(dicList);
|
||||
}
|
||||
var strLists = obj as IList<string>;
|
||||
if (strLists != null)
|
||||
{
|
||||
return Encode(strLists);
|
||||
}
|
||||
var str = obj as string;
|
||||
if (str != null)
|
||||
{
|
||||
str = escapePattern.Replace(str, m =>
|
||||
{
|
||||
switch (m.Value[0])
|
||||
{
|
||||
case '\\':
|
||||
return "\\\\";
|
||||
case '\"':
|
||||
return "\\\"";
|
||||
case '\b':
|
||||
return "\\b";
|
||||
case '\f':
|
||||
return "\\f";
|
||||
case '\n':
|
||||
return "\\n";
|
||||
case '\r':
|
||||
return "\\r";
|
||||
case '\t':
|
||||
return "\\t";
|
||||
default:
|
||||
return "\\u" + ((ushort)m.Value[0]).ToString("x4");
|
||||
}
|
||||
});
|
||||
return "\"" + str + "\"";
|
||||
}
|
||||
if (obj == null)
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
if (obj is bool)
|
||||
{
|
||||
if ((bool)obj)
|
||||
{
|
||||
return "true";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "false";
|
||||
}
|
||||
}
|
||||
if (!obj.GetType().GetTypeInfo().IsPrimitive)
|
||||
{
|
||||
throw new ArgumentException("Unable to encode objects of type " + obj.GetType());
|
||||
}
|
||||
return Convert.ToString(obj, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public class LockSet {
|
||||
private static readonly ConditionalWeakTable<object, IComparable> stableIds =
|
||||
new ConditionalWeakTable<object, IComparable>();
|
||||
private static long nextStableId = 0;
|
||||
|
||||
private readonly IEnumerable<object> mutexes;
|
||||
|
||||
public LockSet(IEnumerable<object> mutexes) {
|
||||
this.mutexes = (from mutex in mutexes
|
||||
orderby GetStableId(mutex)
|
||||
select mutex).ToList();
|
||||
}
|
||||
|
||||
public void Enter() {
|
||||
foreach (var mutex in mutexes) {
|
||||
Monitor.Enter(mutex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Exit() {
|
||||
foreach (var mutex in mutexes) {
|
||||
Monitor.Exit(mutex);
|
||||
}
|
||||
}
|
||||
|
||||
private static IComparable GetStableId(object mutex) {
|
||||
lock (stableIds) {
|
||||
return stableIds.GetValue(mutex, k => nextStableId++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public static class ReflectionHelpers
|
||||
{
|
||||
public static IEnumerable<PropertyInfo> GetProperties(Type type)
|
||||
{
|
||||
#if MONO || UNITY
|
||||
return type.GetProperties();
|
||||
#else
|
||||
return type.GetRuntimeProperties();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static MethodInfo GetMethod(Type type, string name, Type[] parameters)
|
||||
{
|
||||
#if MONO || UNITY
|
||||
return type.GetMethod(name, parameters);
|
||||
#else
|
||||
return type.GetRuntimeMethod(name, parameters);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool IsPrimitive(Type type)
|
||||
{
|
||||
#if MONO || UNITY
|
||||
return type.IsPrimitive;
|
||||
#else
|
||||
return type.GetTypeInfo().IsPrimitive;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetInterfaces(Type type)
|
||||
{
|
||||
#if MONO || UNITY
|
||||
return type.GetInterfaces();
|
||||
#else
|
||||
return type.GetTypeInfo().ImplementedInterfaces;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool IsConstructedGenericType(Type type)
|
||||
{
|
||||
#if UNITY
|
||||
return type.IsGenericType && !type.IsGenericTypeDefinition;
|
||||
#else
|
||||
return type.IsConstructedGenericType;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static IEnumerable<ConstructorInfo> GetConstructors(Type type)
|
||||
{
|
||||
#if UNITY
|
||||
BindingFlags searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
return type.GetConstructors(searchFlags);
|
||||
#else
|
||||
return type.GetTypeInfo().DeclaredConstructors
|
||||
.Where(c => (c.Attributes & MethodAttributes.Static) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Type[] GetGenericTypeArguments(Type type)
|
||||
{
|
||||
#if UNITY
|
||||
return type.GetGenericArguments();
|
||||
#else
|
||||
return type.GenericTypeArguments;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static PropertyInfo GetProperty(Type type, string name)
|
||||
{
|
||||
#if MONO || UNITY
|
||||
return type.GetProperty(name);
|
||||
#else
|
||||
return type.GetRuntimeProperty(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method helps simplify the process of getting a constructor for a type.
|
||||
/// A method like this exists in .NET but is not allowed in a Portable Class Library,
|
||||
/// so we've built our own.
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
/// <param name="parameterTypes"></param>
|
||||
/// <returns></returns>
|
||||
public static ConstructorInfo FindConstructor(this Type self, params Type[] parameterTypes)
|
||||
{
|
||||
var constructors =
|
||||
from constructor in GetConstructors(self)
|
||||
let parameters = constructor.GetParameters()
|
||||
let types = from p in parameters select p.ParameterType
|
||||
where types.SequenceEqual(parameterTypes)
|
||||
select constructor;
|
||||
return constructors.SingleOrDefault();
|
||||
}
|
||||
|
||||
public static bool IsNullable(Type t)
|
||||
{
|
||||
bool isGeneric;
|
||||
#if UNITY
|
||||
isGeneric = t.IsGenericType && !t.IsGenericTypeDefinition;
|
||||
#else
|
||||
isGeneric = t.IsConstructedGenericType;
|
||||
#endif
|
||||
return isGeneric && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
|
||||
}
|
||||
|
||||
public static IEnumerable<T> GetCustomAttributes<T>(this Assembly assembly) where T : Attribute
|
||||
{
|
||||
#if UNITY
|
||||
return assembly.GetCustomAttributes(typeof(T), false).Select(attr => attr as T);
|
||||
#else
|
||||
return CustomAttributeExtensions.GetCustomAttributes<T>(assembly);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Represents an event handler that calls back from the synchronization context
|
||||
/// that subscribed.
|
||||
/// <typeparam name="T">Should look like an EventArgs, but may not inherit EventArgs if T is implemented by the Windows team.</typeparam>
|
||||
/// </summary>
|
||||
public class SynchronizedEventHandler<T> {
|
||||
private LinkedList<Tuple<Delegate, TaskFactory>> delegates =
|
||||
new LinkedList<Tuple<Delegate, TaskFactory>>();
|
||||
public void Add(Delegate del) {
|
||||
lock (delegates) {
|
||||
TaskFactory factory;
|
||||
if (SynchronizationContext.Current != null) {
|
||||
factory =
|
||||
new TaskFactory(CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.FromCurrentSynchronizationContext());
|
||||
} else {
|
||||
factory = Task.Factory;
|
||||
}
|
||||
foreach (var d in del.GetInvocationList()) {
|
||||
delegates.AddLast(new Tuple<Delegate, TaskFactory>(d, factory));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(Delegate del) {
|
||||
lock (delegates) {
|
||||
if (delegates.Count == 0) {
|
||||
return;
|
||||
}
|
||||
foreach (var d in del.GetInvocationList()) {
|
||||
var node = delegates.First;
|
||||
while (node != null) {
|
||||
if (node.Value.Item1 == d) {
|
||||
delegates.Remove(node);
|
||||
break;
|
||||
}
|
||||
node = node.Next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task Invoke(object sender, T args) {
|
||||
IEnumerable<Tuple<Delegate, TaskFactory>> toInvoke;
|
||||
var toContinue = new[] { Task.FromResult(0) };
|
||||
lock (delegates) {
|
||||
toInvoke = delegates.ToList();
|
||||
}
|
||||
var invocations = toInvoke
|
||||
.Select(p => p.Item2.ContinueWhenAll(toContinue,
|
||||
_ => p.Item1.DynamicInvoke(sender, args)))
|
||||
.ToList();
|
||||
return Task.WhenAll(invocations);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// A helper class for enqueuing tasks
|
||||
/// </summary>
|
||||
public class TaskQueue {
|
||||
/// <summary>
|
||||
/// We only need to keep the tail of the queue. Cancelled tasks will
|
||||
/// just complete normally/immediately when their turn arrives.
|
||||
/// </summary>
|
||||
private Task tail;
|
||||
private readonly object mutex = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cancellable task that can be safely awaited and is dependent
|
||||
/// on the current tail of the queue. This essentially gives us a proxy
|
||||
/// for the tail end of the queue whose awaiting can be cancelled.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that cancels
|
||||
/// the task even if the task is still in the queue. This allows the
|
||||
/// running task to return immediately without breaking the dependency
|
||||
/// chain. It also ensures that errors do not propagate.</param>
|
||||
/// <returns>A new task that should be awaited by enqueued tasks.</returns>
|
||||
private Task GetTaskToAwait(CancellationToken cancellationToken) {
|
||||
lock (mutex) {
|
||||
Task toAwait = tail ?? Task.FromResult(true);
|
||||
return toAwait.ContinueWith(task => { }, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues a task created by <paramref name="taskStart"/>. If the task is
|
||||
/// cancellable (or should be able to be cancelled while it is waiting in the
|
||||
/// queue), pass a cancellationToken.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of task.</typeparam>
|
||||
/// <param name="taskStart">A function given a task to await once state is
|
||||
/// snapshotted (e.g. after capturing session tokens at the time of the save call).
|
||||
/// Awaiting this task will wait for the created task's turn in the queue.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to
|
||||
/// cancel waiting in the queue.</param>
|
||||
/// <returns>The task created by the taskStart function.</returns>
|
||||
public T Enqueue<T>(Func<Task, T> taskStart, CancellationToken cancellationToken)
|
||||
where T : Task {
|
||||
Task oldTail;
|
||||
T task;
|
||||
lock (mutex) {
|
||||
oldTail = this.tail ?? Task.FromResult(true);
|
||||
// The task created by taskStart is responsible for waiting the
|
||||
// task passed to it before doing its work (this gives it an opportunity
|
||||
// to do startup work or save state before waiting for its turn in the queue
|
||||
task = taskStart(GetTaskToAwait(cancellationToken));
|
||||
|
||||
// The tail task should be dependent on the old tail as well as the newly-created
|
||||
// task. This prevents cancellation of the new task from causing the queue to run
|
||||
// out of order.
|
||||
this.tail = Task.WhenAll(oldTail, task);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
public object Mutex { get { return mutex; } }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,426 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// A reimplementation of Xamarin's PreserveAttribute.
|
||||
/// This allows us to support AOT and linking for Xamarin platforms.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
internal class PreserveAttribute : Attribute {
|
||||
public bool AllMembers;
|
||||
public bool Conditional;
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
internal class LinkerSafeAttribute : Attribute {
|
||||
public LinkerSafeAttribute() { }
|
||||
}
|
||||
|
||||
[Preserve(AllMembers = true)]
|
||||
internal class PreserveWrapperTypes {
|
||||
/// <summary>
|
||||
/// Exists to ensure that generic types are AOT-compiled for the conversions we support.
|
||||
/// Any new value types that we add support for will need to be registered here.
|
||||
/// The method itself is never called, but by virtue of the Preserve attribute being set
|
||||
/// on the class, these types will be AOT-compiled.
|
||||
///
|
||||
/// This also applies to Unity.
|
||||
/// </summary>
|
||||
private static List<object> CreateWrapperTypes() {
|
||||
return new List<object> {
|
||||
typeof(FlexibleListWrapper<object, object>),
|
||||
typeof(FlexibleListWrapper<object, bool>),
|
||||
typeof(FlexibleListWrapper<object, byte>),
|
||||
typeof(FlexibleListWrapper<object, sbyte>),
|
||||
typeof(FlexibleListWrapper<object, short>),
|
||||
typeof(FlexibleListWrapper<object, ushort>),
|
||||
typeof(FlexibleListWrapper<object, int>),
|
||||
typeof(FlexibleListWrapper<object, uint>),
|
||||
typeof(FlexibleListWrapper<object, long>),
|
||||
typeof(FlexibleListWrapper<object, ulong>),
|
||||
typeof(FlexibleListWrapper<object, char>),
|
||||
typeof(FlexibleListWrapper<object, double>),
|
||||
typeof(FlexibleListWrapper<object, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<bool, object>),
|
||||
typeof(FlexibleListWrapper<bool, bool>),
|
||||
typeof(FlexibleListWrapper<bool, byte>),
|
||||
typeof(FlexibleListWrapper<bool, sbyte>),
|
||||
typeof(FlexibleListWrapper<bool, short>),
|
||||
typeof(FlexibleListWrapper<bool, ushort>),
|
||||
typeof(FlexibleListWrapper<bool, int>),
|
||||
typeof(FlexibleListWrapper<bool, uint>),
|
||||
typeof(FlexibleListWrapper<bool, long>),
|
||||
typeof(FlexibleListWrapper<bool, ulong>),
|
||||
typeof(FlexibleListWrapper<bool, char>),
|
||||
typeof(FlexibleListWrapper<bool, double>),
|
||||
typeof(FlexibleListWrapper<bool, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<byte, object>),
|
||||
typeof(FlexibleListWrapper<byte, bool>),
|
||||
typeof(FlexibleListWrapper<byte, byte>),
|
||||
typeof(FlexibleListWrapper<byte, sbyte>),
|
||||
typeof(FlexibleListWrapper<byte, short>),
|
||||
typeof(FlexibleListWrapper<byte, ushort>),
|
||||
typeof(FlexibleListWrapper<byte, int>),
|
||||
typeof(FlexibleListWrapper<byte, uint>),
|
||||
typeof(FlexibleListWrapper<byte, long>),
|
||||
typeof(FlexibleListWrapper<byte, ulong>),
|
||||
typeof(FlexibleListWrapper<byte, char>),
|
||||
typeof(FlexibleListWrapper<byte, double>),
|
||||
typeof(FlexibleListWrapper<byte, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<sbyte, object>),
|
||||
typeof(FlexibleListWrapper<sbyte, bool>),
|
||||
typeof(FlexibleListWrapper<sbyte, byte>),
|
||||
typeof(FlexibleListWrapper<sbyte, sbyte>),
|
||||
typeof(FlexibleListWrapper<sbyte, short>),
|
||||
typeof(FlexibleListWrapper<sbyte, ushort>),
|
||||
typeof(FlexibleListWrapper<sbyte, int>),
|
||||
typeof(FlexibleListWrapper<sbyte, uint>),
|
||||
typeof(FlexibleListWrapper<sbyte, long>),
|
||||
typeof(FlexibleListWrapper<sbyte, ulong>),
|
||||
typeof(FlexibleListWrapper<sbyte, char>),
|
||||
typeof(FlexibleListWrapper<sbyte, double>),
|
||||
typeof(FlexibleListWrapper<sbyte, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<short, object>),
|
||||
typeof(FlexibleListWrapper<short, bool>),
|
||||
typeof(FlexibleListWrapper<short, byte>),
|
||||
typeof(FlexibleListWrapper<short, sbyte>),
|
||||
typeof(FlexibleListWrapper<short, short>),
|
||||
typeof(FlexibleListWrapper<short, ushort>),
|
||||
typeof(FlexibleListWrapper<short, int>),
|
||||
typeof(FlexibleListWrapper<short, uint>),
|
||||
typeof(FlexibleListWrapper<short, long>),
|
||||
typeof(FlexibleListWrapper<short, ulong>),
|
||||
typeof(FlexibleListWrapper<short, char>),
|
||||
typeof(FlexibleListWrapper<short, double>),
|
||||
typeof(FlexibleListWrapper<short, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<ushort, object>),
|
||||
typeof(FlexibleListWrapper<ushort, bool>),
|
||||
typeof(FlexibleListWrapper<ushort, byte>),
|
||||
typeof(FlexibleListWrapper<ushort, sbyte>),
|
||||
typeof(FlexibleListWrapper<ushort, short>),
|
||||
typeof(FlexibleListWrapper<ushort, ushort>),
|
||||
typeof(FlexibleListWrapper<ushort, int>),
|
||||
typeof(FlexibleListWrapper<ushort, uint>),
|
||||
typeof(FlexibleListWrapper<ushort, long>),
|
||||
typeof(FlexibleListWrapper<ushort, ulong>),
|
||||
typeof(FlexibleListWrapper<ushort, char>),
|
||||
typeof(FlexibleListWrapper<ushort, double>),
|
||||
typeof(FlexibleListWrapper<ushort, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<int, object>),
|
||||
typeof(FlexibleListWrapper<int, bool>),
|
||||
typeof(FlexibleListWrapper<int, byte>),
|
||||
typeof(FlexibleListWrapper<int, sbyte>),
|
||||
typeof(FlexibleListWrapper<int, short>),
|
||||
typeof(FlexibleListWrapper<int, ushort>),
|
||||
typeof(FlexibleListWrapper<int, int>),
|
||||
typeof(FlexibleListWrapper<int, uint>),
|
||||
typeof(FlexibleListWrapper<int, long>),
|
||||
typeof(FlexibleListWrapper<int, ulong>),
|
||||
typeof(FlexibleListWrapper<int, char>),
|
||||
typeof(FlexibleListWrapper<int, double>),
|
||||
typeof(FlexibleListWrapper<int, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<uint, object>),
|
||||
typeof(FlexibleListWrapper<uint, bool>),
|
||||
typeof(FlexibleListWrapper<uint, byte>),
|
||||
typeof(FlexibleListWrapper<uint, sbyte>),
|
||||
typeof(FlexibleListWrapper<uint, short>),
|
||||
typeof(FlexibleListWrapper<uint, ushort>),
|
||||
typeof(FlexibleListWrapper<uint, int>),
|
||||
typeof(FlexibleListWrapper<uint, uint>),
|
||||
typeof(FlexibleListWrapper<uint, long>),
|
||||
typeof(FlexibleListWrapper<uint, ulong>),
|
||||
typeof(FlexibleListWrapper<uint, char>),
|
||||
typeof(FlexibleListWrapper<uint, double>),
|
||||
typeof(FlexibleListWrapper<uint, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<long, object>),
|
||||
typeof(FlexibleListWrapper<long, bool>),
|
||||
typeof(FlexibleListWrapper<long, byte>),
|
||||
typeof(FlexibleListWrapper<long, sbyte>),
|
||||
typeof(FlexibleListWrapper<long, short>),
|
||||
typeof(FlexibleListWrapper<long, ushort>),
|
||||
typeof(FlexibleListWrapper<long, int>),
|
||||
typeof(FlexibleListWrapper<long, uint>),
|
||||
typeof(FlexibleListWrapper<long, long>),
|
||||
typeof(FlexibleListWrapper<long, ulong>),
|
||||
typeof(FlexibleListWrapper<long, char>),
|
||||
typeof(FlexibleListWrapper<long, double>),
|
||||
typeof(FlexibleListWrapper<long, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<ulong, object>),
|
||||
typeof(FlexibleListWrapper<ulong, bool>),
|
||||
typeof(FlexibleListWrapper<ulong, byte>),
|
||||
typeof(FlexibleListWrapper<ulong, sbyte>),
|
||||
typeof(FlexibleListWrapper<ulong, short>),
|
||||
typeof(FlexibleListWrapper<ulong, ushort>),
|
||||
typeof(FlexibleListWrapper<ulong, int>),
|
||||
typeof(FlexibleListWrapper<ulong, uint>),
|
||||
typeof(FlexibleListWrapper<ulong, long>),
|
||||
typeof(FlexibleListWrapper<ulong, ulong>),
|
||||
typeof(FlexibleListWrapper<ulong, char>),
|
||||
typeof(FlexibleListWrapper<ulong, double>),
|
||||
typeof(FlexibleListWrapper<ulong, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<char, object>),
|
||||
typeof(FlexibleListWrapper<char, bool>),
|
||||
typeof(FlexibleListWrapper<char, byte>),
|
||||
typeof(FlexibleListWrapper<char, sbyte>),
|
||||
typeof(FlexibleListWrapper<char, short>),
|
||||
typeof(FlexibleListWrapper<char, ushort>),
|
||||
typeof(FlexibleListWrapper<char, int>),
|
||||
typeof(FlexibleListWrapper<char, uint>),
|
||||
typeof(FlexibleListWrapper<char, long>),
|
||||
typeof(FlexibleListWrapper<char, ulong>),
|
||||
typeof(FlexibleListWrapper<char, char>),
|
||||
typeof(FlexibleListWrapper<char, double>),
|
||||
typeof(FlexibleListWrapper<char, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<double, object>),
|
||||
typeof(FlexibleListWrapper<double, bool>),
|
||||
typeof(FlexibleListWrapper<double, byte>),
|
||||
typeof(FlexibleListWrapper<double, sbyte>),
|
||||
typeof(FlexibleListWrapper<double, short>),
|
||||
typeof(FlexibleListWrapper<double, ushort>),
|
||||
typeof(FlexibleListWrapper<double, int>),
|
||||
typeof(FlexibleListWrapper<double, uint>),
|
||||
typeof(FlexibleListWrapper<double, long>),
|
||||
typeof(FlexibleListWrapper<double, ulong>),
|
||||
typeof(FlexibleListWrapper<double, char>),
|
||||
typeof(FlexibleListWrapper<double, double>),
|
||||
typeof(FlexibleListWrapper<double, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<float, object>),
|
||||
typeof(FlexibleListWrapper<float, bool>),
|
||||
typeof(FlexibleListWrapper<float, byte>),
|
||||
typeof(FlexibleListWrapper<float, sbyte>),
|
||||
typeof(FlexibleListWrapper<float, short>),
|
||||
typeof(FlexibleListWrapper<float, ushort>),
|
||||
typeof(FlexibleListWrapper<float, int>),
|
||||
typeof(FlexibleListWrapper<float, uint>),
|
||||
typeof(FlexibleListWrapper<float, long>),
|
||||
typeof(FlexibleListWrapper<float, ulong>),
|
||||
typeof(FlexibleListWrapper<float, char>),
|
||||
typeof(FlexibleListWrapper<float, double>),
|
||||
typeof(FlexibleListWrapper<float, float>),
|
||||
|
||||
typeof(FlexibleListWrapper<object, DateTime>),
|
||||
typeof(FlexibleListWrapper<DateTime, object>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<object, object>),
|
||||
typeof(FlexibleDictionaryWrapper<object, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<object, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<object, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<object, short>),
|
||||
typeof(FlexibleDictionaryWrapper<object, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<object, int>),
|
||||
typeof(FlexibleDictionaryWrapper<object, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<object, long>),
|
||||
typeof(FlexibleDictionaryWrapper<object, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<object, char>),
|
||||
typeof(FlexibleDictionaryWrapper<object, double>),
|
||||
typeof(FlexibleDictionaryWrapper<object, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<bool, object>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, short>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, int>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, long>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, char>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, double>),
|
||||
typeof(FlexibleDictionaryWrapper<bool, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<byte, object>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, short>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, int>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, long>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, char>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, double>),
|
||||
typeof(FlexibleDictionaryWrapper<byte, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, object>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, short>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, int>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, long>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, char>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, double>),
|
||||
typeof(FlexibleDictionaryWrapper<sbyte, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<short, object>),
|
||||
typeof(FlexibleDictionaryWrapper<short, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<short, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<short, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<short, short>),
|
||||
typeof(FlexibleDictionaryWrapper<short, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<short, int>),
|
||||
typeof(FlexibleDictionaryWrapper<short, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<short, long>),
|
||||
typeof(FlexibleDictionaryWrapper<short, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<short, char>),
|
||||
typeof(FlexibleDictionaryWrapper<short, double>),
|
||||
typeof(FlexibleDictionaryWrapper<short, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<ushort, object>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, short>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, int>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, long>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, char>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, double>),
|
||||
typeof(FlexibleDictionaryWrapper<ushort, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<int, object>),
|
||||
typeof(FlexibleDictionaryWrapper<int, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<int, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<int, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<int, short>),
|
||||
typeof(FlexibleDictionaryWrapper<int, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<int, int>),
|
||||
typeof(FlexibleDictionaryWrapper<int, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<int, long>),
|
||||
typeof(FlexibleDictionaryWrapper<int, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<int, char>),
|
||||
typeof(FlexibleDictionaryWrapper<int, double>),
|
||||
typeof(FlexibleDictionaryWrapper<int, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<uint, object>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, short>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, int>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, long>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, char>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, double>),
|
||||
typeof(FlexibleDictionaryWrapper<uint, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<long, object>),
|
||||
typeof(FlexibleDictionaryWrapper<long, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<long, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<long, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<long, short>),
|
||||
typeof(FlexibleDictionaryWrapper<long, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<long, int>),
|
||||
typeof(FlexibleDictionaryWrapper<long, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<long, long>),
|
||||
typeof(FlexibleDictionaryWrapper<long, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<long, char>),
|
||||
typeof(FlexibleDictionaryWrapper<long, double>),
|
||||
typeof(FlexibleDictionaryWrapper<long, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<ulong, object>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, short>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, int>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, long>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, char>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, double>),
|
||||
typeof(FlexibleDictionaryWrapper<ulong, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<char, object>),
|
||||
typeof(FlexibleDictionaryWrapper<char, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<char, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<char, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<char, short>),
|
||||
typeof(FlexibleDictionaryWrapper<char, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<char, int>),
|
||||
typeof(FlexibleDictionaryWrapper<char, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<char, long>),
|
||||
typeof(FlexibleDictionaryWrapper<char, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<char, char>),
|
||||
typeof(FlexibleDictionaryWrapper<char, double>),
|
||||
typeof(FlexibleDictionaryWrapper<char, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<double, object>),
|
||||
typeof(FlexibleDictionaryWrapper<double, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<double, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<double, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<double, short>),
|
||||
typeof(FlexibleDictionaryWrapper<double, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<double, int>),
|
||||
typeof(FlexibleDictionaryWrapper<double, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<double, long>),
|
||||
typeof(FlexibleDictionaryWrapper<double, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<double, char>),
|
||||
typeof(FlexibleDictionaryWrapper<double, double>),
|
||||
typeof(FlexibleDictionaryWrapper<double, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<float, object>),
|
||||
typeof(FlexibleDictionaryWrapper<float, bool>),
|
||||
typeof(FlexibleDictionaryWrapper<float, byte>),
|
||||
typeof(FlexibleDictionaryWrapper<float, sbyte>),
|
||||
typeof(FlexibleDictionaryWrapper<float, short>),
|
||||
typeof(FlexibleDictionaryWrapper<float, ushort>),
|
||||
typeof(FlexibleDictionaryWrapper<float, int>),
|
||||
typeof(FlexibleDictionaryWrapper<float, uint>),
|
||||
typeof(FlexibleDictionaryWrapper<float, long>),
|
||||
typeof(FlexibleDictionaryWrapper<float, ulong>),
|
||||
typeof(FlexibleDictionaryWrapper<float, char>),
|
||||
typeof(FlexibleDictionaryWrapper<float, double>),
|
||||
typeof(FlexibleDictionaryWrapper<float, float>),
|
||||
|
||||
typeof(FlexibleDictionaryWrapper<object, DateTime>),
|
||||
typeof(FlexibleDictionaryWrapper<DateTime, object>),
|
||||
|
||||
(Action)(() => AVCloud.CallFunctionAsync<object>(null, null, null,CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<bool>(null, null,null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<byte>(null, null,null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<sbyte>(null, null,null ,CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<short>(null, null,null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<ushort>(null, null,null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<int>(null, null,null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<uint>(null, null,null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<long>(null, null, null,CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<ulong>(null, null,null ,CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<char>(null, null,null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<double>(null, null,null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<float>(null, null,null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<IDictionary<string, object>>(null, null,null, CancellationToken.None)),
|
||||
(Action)(() => AVCloud.CallFunctionAsync<IList<object>>(null, null,null, CancellationToken.None)),
|
||||
|
||||
typeof(FlexibleListWrapper<object, AVGeoPoint>),
|
||||
typeof(FlexibleListWrapper<AVGeoPoint, object>),
|
||||
typeof(FlexibleDictionaryWrapper<object, AVGeoPoint>),
|
||||
typeof(FlexibleDictionaryWrapper<AVGeoPoint, object>),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,284 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// A AVACL is used to control which users and roles can access or modify a particular object. Each
|
||||
/// <see cref="AVObject"/> can have its own AVACL. You can grant read and write permissions
|
||||
/// separately to specific users, to groups of users that belong to roles, or you can grant permissions
|
||||
/// to "the public" so that, for example, any user could read a particular object but only a particular
|
||||
/// set of users could write to that object.
|
||||
/// </summary>
|
||||
public class AVACL : IJsonConvertible {
|
||||
private enum AccessKind {
|
||||
Read,
|
||||
Write
|
||||
}
|
||||
private const string publicName = "*";
|
||||
private readonly ICollection<string> readers = new HashSet<string>();
|
||||
private readonly ICollection<string> writers = new HashSet<string>();
|
||||
|
||||
internal AVACL(IDictionary<string, object> jsonObject) {
|
||||
readers = new HashSet<string>(from pair in jsonObject
|
||||
where ((IDictionary<string, object>)pair.Value).ContainsKey("read")
|
||||
select pair.Key);
|
||||
writers = new HashSet<string>(from pair in jsonObject
|
||||
where ((IDictionary<string, object>)pair.Value).ContainsKey("write")
|
||||
select pair.Key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an ACL with no permissions granted.
|
||||
/// </summary>
|
||||
public AVACL() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an ACL where only the provided user has access.
|
||||
/// </summary>
|
||||
/// <param name="owner">The only user that can read or write objects governed by this ACL.</param>
|
||||
public AVACL(AVUser owner) {
|
||||
SetReadAccess(owner, true);
|
||||
SetWriteAccess(owner, true);
|
||||
}
|
||||
|
||||
IDictionary<string, object> IJsonConvertible.ToJSON() {
|
||||
var result = new Dictionary<string, object>();
|
||||
foreach (var user in readers.Union(writers)) {
|
||||
var userPermissions = new Dictionary<string, object>();
|
||||
if (readers.Contains(user)) {
|
||||
userPermissions["read"] = true;
|
||||
}
|
||||
if (writers.Contains(user)) {
|
||||
userPermissions["write"] = true;
|
||||
}
|
||||
result[user] = userPermissions;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SetAccess(AccessKind kind, string userId, bool allowed) {
|
||||
if (userId == null) {
|
||||
throw new ArgumentException("Cannot set access for an unsaved user or role.");
|
||||
}
|
||||
ICollection<string> target = null;
|
||||
switch (kind) {
|
||||
case AccessKind.Read:
|
||||
target = readers;
|
||||
break;
|
||||
case AccessKind.Write:
|
||||
target = writers;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("Unknown AccessKind");
|
||||
}
|
||||
if (allowed) {
|
||||
target.Add(userId);
|
||||
} else {
|
||||
target.Remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetAccess(AccessKind kind, string userId) {
|
||||
if (userId == null) {
|
||||
throw new ArgumentException("Cannot get access for an unsaved user or role.");
|
||||
}
|
||||
switch (kind) {
|
||||
case AccessKind.Read:
|
||||
return readers.Contains(userId);
|
||||
case AccessKind.Write:
|
||||
return writers.Contains(userId);
|
||||
default:
|
||||
throw new NotImplementedException("Unknown AccessKind");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the public is allowed to read this object.
|
||||
/// </summary>
|
||||
public bool PublicReadAccess {
|
||||
get {
|
||||
return GetAccess(AccessKind.Read, publicName);
|
||||
}
|
||||
set {
|
||||
SetAccess(AccessKind.Read, publicName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the public is allowed to write this object.
|
||||
/// </summary>
|
||||
public bool PublicWriteAccess {
|
||||
get {
|
||||
return GetAccess(AccessKind.Write, publicName);
|
||||
}
|
||||
set {
|
||||
SetAccess(AccessKind.Write, publicName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the given user id is allowed to read this object.
|
||||
/// </summary>
|
||||
/// <param name="userId">The objectId of the user.</param>
|
||||
/// <param name="allowed">Whether the user has permission.</param>
|
||||
public void SetReadAccess(string userId, bool allowed) {
|
||||
SetAccess(AccessKind.Read, userId, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the given user is allowed to read this object.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="allowed">Whether the user has permission.</param>
|
||||
public void SetReadAccess(AVUser user, bool allowed) {
|
||||
SetReadAccess(user.ObjectId, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the given user id is allowed to write this object.
|
||||
/// </summary>
|
||||
/// <param name="userId">The objectId of the user.</param>
|
||||
/// <param name="allowed">Whether the user has permission.</param>
|
||||
public void SetWriteAccess(string userId, bool allowed) {
|
||||
SetAccess(AccessKind.Write, userId, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the given user is allowed to write this object.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="allowed">Whether the user has permission.</param>
|
||||
public void SetWriteAccess(AVUser user, bool allowed) {
|
||||
SetWriteAccess(user.ObjectId, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the given user id is *explicitly* allowed to read this object.
|
||||
/// Even if this returns false, the user may still be able to read it if
|
||||
/// PublicReadAccess is true or a role that the user belongs to has read access.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user objectId to check.</param>
|
||||
/// <returns>Whether the user has access.</returns>
|
||||
public bool GetReadAccess(string userId) {
|
||||
return GetAccess(AccessKind.Read, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the given user is *explicitly* allowed to read this object.
|
||||
/// Even if this returns false, the user may still be able to read it if
|
||||
/// PublicReadAccess is true or a role that the user belongs to has read access.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to check.</param>
|
||||
/// <returns>Whether the user has access.</returns>
|
||||
public bool GetReadAccess(AVUser user) {
|
||||
return GetReadAccess(user.ObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the given user id is *explicitly* allowed to write this object.
|
||||
/// Even if this returns false, the user may still be able to write it if
|
||||
/// PublicReadAccess is true or a role that the user belongs to has write access.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user objectId to check.</param>
|
||||
/// <returns>Whether the user has access.</returns>
|
||||
public bool GetWriteAccess(string userId) {
|
||||
return GetAccess(AccessKind.Write, userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the given user is *explicitly* allowed to write this object.
|
||||
/// Even if this returns false, the user may still be able to write it if
|
||||
/// PublicReadAccess is true or a role that the user belongs to has write access.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to check.</param>
|
||||
/// <returns>Whether the user has access.</returns>
|
||||
public bool GetWriteAccess(AVUser user) {
|
||||
return GetWriteAccess(user.ObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether users belonging to the role with the given <paramref name="roleName"/>
|
||||
/// are allowed to read this object.
|
||||
/// </summary>
|
||||
/// <param name="roleName">The name of the role.</param>
|
||||
/// <param name="allowed">Whether the role has access.</param>
|
||||
public void SetRoleReadAccess(string roleName, bool allowed) {
|
||||
SetAccess(AccessKind.Read, "role:" + roleName, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether users belonging to the given role are allowed to read this object.
|
||||
/// </summary>
|
||||
/// <param name="role">The role.</param>
|
||||
/// <param name="allowed">Whether the role has access.</param>
|
||||
public void SetRoleReadAccess(AVRole role, bool allowed) {
|
||||
SetRoleReadAccess(role.Name, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether users belonging to the role with the given <paramref name="roleName"/>
|
||||
/// are allowed to read this object. Even if this returns false, the role may still be
|
||||
/// able to read it if a parent role has read access.
|
||||
/// </summary>
|
||||
/// <param name="roleName">The name of the role.</param>
|
||||
/// <returns>Whether the role has access.</returns>
|
||||
public bool GetRoleReadAccess(string roleName) {
|
||||
return GetAccess(AccessKind.Read, "role:" + roleName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether users belonging to the role are allowed to read this object.
|
||||
/// Even if this returns false, the role may still be able to read it if a
|
||||
/// parent role has read access.
|
||||
/// </summary>
|
||||
/// <param name="role">The name of the role.</param>
|
||||
/// <returns>Whether the role has access.</returns>
|
||||
public bool GetRoleReadAccess(AVRole role) {
|
||||
return GetRoleReadAccess(role.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether users belonging to the role with the given <paramref name="roleName"/>
|
||||
/// are allowed to write this object.
|
||||
/// </summary>
|
||||
/// <param name="roleName">The name of the role.</param>
|
||||
/// <param name="allowed">Whether the role has access.</param>
|
||||
public void SetRoleWriteAccess(string roleName, bool allowed) {
|
||||
SetAccess(AccessKind.Write, "role:" + roleName, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether users belonging to the given role are allowed to write this object.
|
||||
/// </summary>
|
||||
/// <param name="role">The role.</param>
|
||||
/// <param name="allowed">Whether the role has access.</param>
|
||||
public void SetRoleWriteAccess(AVRole role, bool allowed) {
|
||||
SetRoleWriteAccess(role.Name, allowed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether users belonging to the role with the given <paramref name="roleName"/>
|
||||
/// are allowed to write this object. Even if this returns false, the role may still be
|
||||
/// able to write it if a parent role has write access.
|
||||
/// </summary>
|
||||
/// <param name="roleName">The name of the role.</param>
|
||||
/// <returns>Whether the role has access.</returns>
|
||||
public bool GetRoleWriteAccess(string roleName) {
|
||||
return GetAccess(AccessKind.Write, "role:" + roleName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether users belonging to the role are allowed to write this object.
|
||||
/// Even if this returns false, the role may still be able to write it if a
|
||||
/// parent role has write access.
|
||||
/// </summary>
|
||||
/// <param name="role">The name of the role.</param>
|
||||
/// <returns>Whether the role has access.</returns>
|
||||
public bool GetRoleWriteAccess(AVRole role) {
|
||||
return GetRoleWriteAccess(role.Name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the class name for a subclass of AVObject.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class AVClassNameAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new AVClassName attribute.
|
||||
/// </summary>
|
||||
/// <param name="className">The class name to associate with the AVObject subclass.</param>
|
||||
public AVClassNameAttribute(string className)
|
||||
{
|
||||
this.ClassName = className;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the class name to associate with the AVObject subclass.
|
||||
/// </summary>
|
||||
public string ClassName { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,506 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// AVClient contains static functions that handle global
|
||||
/// configuration for the LeanCloud library.
|
||||
/// </summary>
|
||||
public static partial class AVClient
|
||||
{
|
||||
public static readonly string[] DateFormatStrings = {
|
||||
// Official ISO format
|
||||
"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'",
|
||||
// It's possible that the string converter server-side may trim trailing zeroes,
|
||||
// so these two formats cover ourselves from that.
|
||||
"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'ff'Z'",
|
||||
"yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'f'Z'",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Represents the configuration of the LeanCloud SDK.
|
||||
/// </summary>
|
||||
public struct Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// 与 SDK 通讯的云端节点
|
||||
/// </summary>
|
||||
public enum AVRegion
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认值,LeanCloud 华北节点,同 Public_North_China
|
||||
/// </summary>
|
||||
[Obsolete("please use Configuration.AVRegion.Public_North_China")]
|
||||
Public_CN = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 默认值,华北公有云节点,同 Public_CN
|
||||
/// </summary>
|
||||
Public_North_China = 0,
|
||||
|
||||
/// <summary>
|
||||
/// LeanCloud 北美区公有云节点,同 Public_North_America
|
||||
/// </summary>
|
||||
[Obsolete("please use Configuration.AVRegion.Public_North_America")]
|
||||
Public_US = 1,
|
||||
/// <summary>
|
||||
/// LeanCloud 北美区公有云节点,同 Public_US
|
||||
/// </summary>
|
||||
Public_North_America = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 华东公有云节点,同 Public_East_China
|
||||
/// </summary>
|
||||
[Obsolete("please use Configuration.AVRegion.Public_East_China")]
|
||||
Vendor_Tencent = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 华东公有云节点,同 Vendor_Tencent
|
||||
/// </summary>
|
||||
Public_East_China = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In the event that you would like to use the LeanCloud SDK
|
||||
/// from a completely portable project, with no platform-specific library required,
|
||||
/// to get full access to all of our features available on LeanCloud.com
|
||||
/// (A/B testing, slow queries, etc.), you must set the values of this struct
|
||||
/// to be appropriate for your platform.
|
||||
///
|
||||
/// Any values set here will overwrite those that are automatically configured by
|
||||
/// any platform-specific migration library your app includes.
|
||||
/// </summary>
|
||||
public struct VersionInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// The build number of your app.
|
||||
/// </summary>
|
||||
public String BuildVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The human friendly version number of your happ.
|
||||
/// </summary>
|
||||
public String DisplayVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The operating system version of the platform the SDK is operating in..
|
||||
/// </summary>
|
||||
public String OSVersion { get; set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The LeanCloud application ID of your app.
|
||||
/// </summary>
|
||||
public string ApplicationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// LeanCloud C# SDK 支持的服务节点,目前支持华北,华东和北美公有云节点和私有节点,以及专属节点
|
||||
/// </summary>
|
||||
public AVRegion Region { get; set; }
|
||||
|
||||
internal int RegionValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)Region;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The LeanCloud application key for your app.
|
||||
/// </summary>
|
||||
public string ApplicationKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The LeanCloud master key for your app.
|
||||
/// </summary>
|
||||
/// <value>The master key.</value>
|
||||
public string MasterKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional HTTP headers to be sent with network requests from the SDK.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> AdditionalHTTPHeaders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The version information of your application environment.
|
||||
/// </summary>
|
||||
public VersionInformation VersionInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 存储服务器地址
|
||||
/// </summary>
|
||||
/// <value>The API server.</value>
|
||||
public string ApiServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 云引擎服务器地址
|
||||
/// </summary>
|
||||
/// <value>The engine server uri.</value>
|
||||
public string EngineServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 即时通信服务器地址
|
||||
/// </summary>
|
||||
/// <value>The RTMR outer.</value>
|
||||
public string RTMServer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 直连即时通信服务器地址
|
||||
/// </summary>
|
||||
/// <value>The realtime server.</value>
|
||||
public string RealtimeServer { get; set; }
|
||||
|
||||
public Uri PushServer { get; set; }
|
||||
|
||||
public Uri StatsServer { get; set; }
|
||||
}
|
||||
|
||||
private static readonly object mutex = new object();
|
||||
|
||||
static AVClient()
|
||||
{
|
||||
versionString = "net-portable-" + Version;
|
||||
|
||||
//AVModuleController.Instance.ScanForModules();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current configuration that LeanCloud has been initialized with.
|
||||
/// </summary>
|
||||
public static Configuration CurrentConfiguration { get; internal set; }
|
||||
|
||||
internal static Version Version
|
||||
{
|
||||
get
|
||||
{
|
||||
var assemblyName = new AssemblyName(typeof(AVClient).GetTypeInfo().Assembly.FullName);
|
||||
return assemblyName.Version;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string versionString;
|
||||
/// <summary>
|
||||
/// 当前 SDK 版本号
|
||||
/// </summary>
|
||||
public static string VersionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return versionString;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates this client as belonging to your application. This must be
|
||||
/// called before your application can use the LeanCloud library. The recommended
|
||||
/// way is to put a call to <c>AVClient.Initialize</c> in your
|
||||
/// Application startup.
|
||||
/// </summary>
|
||||
/// <param name="applicationId">The Application ID provided in the LeanCloud dashboard.
|
||||
/// </param>
|
||||
/// <param name="applicationKey">The .NET API Key provided in the LeanCloud dashboard.
|
||||
/// </param>
|
||||
public static void Initialize(string applicationId, string applicationKey)
|
||||
{
|
||||
Initialize(new Configuration
|
||||
{
|
||||
ApplicationId = applicationId,
|
||||
ApplicationKey = applicationKey
|
||||
});
|
||||
}
|
||||
|
||||
internal static Action<string> LogTracker { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动日志打印
|
||||
/// </summary>
|
||||
/// <param name="trace"></param>
|
||||
public static void HttpLog(Action<string> trace)
|
||||
{
|
||||
LogTracker = trace;
|
||||
}
|
||||
/// <summary>
|
||||
/// 打印 HTTP 访问日志
|
||||
/// </summary>
|
||||
/// <param name="log"></param>
|
||||
public static void PrintLog(string log)
|
||||
{
|
||||
if (AVClient.LogTracker != null)
|
||||
{
|
||||
AVClient.LogTracker(log);
|
||||
}
|
||||
}
|
||||
|
||||
static bool useProduction = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether send the request to production server or staging server.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if use production; otherwise, <c>false</c>.</value>
|
||||
public static bool UseProduction
|
||||
{
|
||||
get
|
||||
{
|
||||
return useProduction;
|
||||
}
|
||||
set
|
||||
{
|
||||
useProduction = value;
|
||||
}
|
||||
}
|
||||
|
||||
static bool useMasterKey = false;
|
||||
public static bool UseMasterKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return useMasterKey;
|
||||
}
|
||||
set
|
||||
{
|
||||
useMasterKey = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates this client as belonging to your application. This must be
|
||||
/// called before your application can use the LeanCloud library. The recommended
|
||||
/// way is to put a call to <c>AVClient.Initialize</c> in your
|
||||
/// Application startup.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration to initialize LeanCloud with.
|
||||
/// </param>
|
||||
public static void Initialize(Configuration configuration)
|
||||
{
|
||||
Config(configuration);
|
||||
|
||||
AVObject.RegisterSubclass<AVUser>();
|
||||
AVObject.RegisterSubclass<AVRole>();
|
||||
AVObject.RegisterSubclass<AVSession>();
|
||||
}
|
||||
|
||||
internal static void Config(Configuration configuration)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
var nodeHash = configuration.ApplicationId.Split('-');
|
||||
if (nodeHash.Length > 1)
|
||||
{
|
||||
if (nodeHash[1].Trim() == "9Nh9j0Va")
|
||||
{
|
||||
configuration.Region = Configuration.AVRegion.Public_East_China;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentConfiguration = configuration;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Clear()
|
||||
{
|
||||
AVPlugins.Instance.AppRouterController.Clear();
|
||||
AVPlugins.Instance.Reset();
|
||||
AVUser.ClearInMemoryUser();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Switch app.
|
||||
/// </summary>
|
||||
/// <param name="configuration">Configuration.</param>
|
||||
public static void Switch(Configuration configuration)
|
||||
{
|
||||
Clear();
|
||||
Initialize(configuration);
|
||||
}
|
||||
|
||||
public static void Switch(string applicationId, string applicationKey, Configuration.AVRegion region = Configuration.AVRegion.Public_North_China)
|
||||
{
|
||||
var configuration = new Configuration
|
||||
{
|
||||
ApplicationId = applicationId,
|
||||
ApplicationKey = applicationKey,
|
||||
Region = region
|
||||
};
|
||||
Switch(configuration);
|
||||
}
|
||||
|
||||
public static string BuildQueryString(IDictionary<string, object> parameters)
|
||||
{
|
||||
return string.Join("&", (from pair in parameters
|
||||
let valueString = pair.Value as string
|
||||
select string.Format("{0}={1}",
|
||||
Uri.EscapeDataString(pair.Key),
|
||||
Uri.EscapeDataString(string.IsNullOrEmpty(valueString) ?
|
||||
Json.Encode(pair.Value) : valueString)))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
internal static IDictionary<string, string> DecodeQueryString(string queryString)
|
||||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
foreach (var pair in queryString.Split('&'))
|
||||
{
|
||||
var parts = pair.Split(new char[] { '=' }, 2);
|
||||
dict[parts[0]] = parts.Length == 2 ? Uri.UnescapeDataString(parts[1].Replace("+", " ")) : null;
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
internal static IDictionary<string, object> DeserializeJsonString(string jsonData)
|
||||
{
|
||||
return Json.Parse(jsonData) as IDictionary<string, object>;
|
||||
}
|
||||
|
||||
internal static string SerializeJsonString(IDictionary<string, object> jsonData)
|
||||
{
|
||||
return Json.Encode(jsonData);
|
||||
}
|
||||
|
||||
public static Task<Tuple<HttpStatusCode, string>> HttpGetAsync(Uri uri)
|
||||
{
|
||||
return RequestAsync(uri, "GET", null, body: null, contentType: null, cancellationToken: CancellationToken.None);
|
||||
}
|
||||
|
||||
public static Task<Tuple<HttpStatusCode, string>> RequestAsync(Uri uri, string method, IList<KeyValuePair<string, string>> headers, IDictionary<string, object> body, string contentType, CancellationToken cancellationToken)
|
||||
{
|
||||
var dataStream = body != null ? new MemoryStream(Encoding.UTF8.GetBytes(Json.Encode(body))) : null;
|
||||
return AVClient.RequestAsync(uri, method, headers, dataStream, contentType, cancellationToken);
|
||||
//return AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task<Tuple<HttpStatusCode, string>> RequestAsync(Uri uri, string method, IList<KeyValuePair<string, string>> headers, Stream data, string contentType, CancellationToken cancellationToken)
|
||||
{
|
||||
HttpRequest request = new HttpRequest()
|
||||
{
|
||||
Data = data != null ? data : null,
|
||||
Headers = headers,
|
||||
Method = method,
|
||||
Uri = uri
|
||||
};
|
||||
return AVPlugins.Instance.HttpClient.ExecuteAsync(request, null, null, cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var response = t.Result;
|
||||
var contentString = response.Item2;
|
||||
int responseCode = (int)response.Item1;
|
||||
var responseLog = responseCode + ";" + contentString;
|
||||
PrintLog(responseLog);
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
internal static Tuple<HttpStatusCode, IDictionary<string, object>> ReponseResolve(Tuple<HttpStatusCode, string> response, CancellationToken cancellationToken)
|
||||
{
|
||||
Tuple<HttpStatusCode, string> result = response;
|
||||
HttpStatusCode code = result.Item1;
|
||||
string item2 = result.Item2;
|
||||
|
||||
if (item2 == null)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new Tuple<HttpStatusCode, IDictionary<string, object>>(code, null);
|
||||
}
|
||||
IDictionary<string, object> strs = null;
|
||||
try
|
||||
{
|
||||
strs = (!item2.StartsWith("[", StringComparison.Ordinal) ? AVClient.DeserializeJsonString(item2) : new Dictionary<string, object>()
|
||||
{
|
||||
{ "results", Json.Parse(item2) }
|
||||
});
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new AVException(AVException.ErrorCode.OtherCause, "Invalid response from server", exception);
|
||||
}
|
||||
var codeValue = (int)code;
|
||||
if (codeValue > 203 || codeValue < 200)
|
||||
{
|
||||
throw new AVException((AVException.ErrorCode)((int)((strs.ContainsKey("code") ? (long)strs["code"] : (long)-1))), (strs.ContainsKey("error") ? strs["error"] as string : item2), null);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new Tuple<HttpStatusCode, IDictionary<string, object>>(code, strs);
|
||||
}
|
||||
internal static Task<Tuple<HttpStatusCode, IDictionary<string, object>>> RequestAsync(string method, Uri relativeUri, string sessionToken, IDictionary<string, object> data, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
var command = new AVCommand(relativeUri.ToString(),
|
||||
method: method,
|
||||
sessionToken: sessionToken,
|
||||
data: data);
|
||||
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
internal static Task<Tuple<HttpStatusCode, IDictionary<string, object>>> RunCommandAsync(AVCommand command)
|
||||
{
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command);
|
||||
}
|
||||
|
||||
internal static bool IsSuccessStatusCode(HttpStatusCode responseStatus)
|
||||
{
|
||||
var codeValue = (int)responseStatus;
|
||||
return (codeValue > 199) && (codeValue < 204);
|
||||
}
|
||||
|
||||
public static string ToLog(this HttpRequest request)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
var start = "===HTTP Request Start===";
|
||||
sb.AppendLine(start);
|
||||
var urlLog = "Url: " + request.Uri;
|
||||
sb.AppendLine(urlLog);
|
||||
|
||||
var methodLog = "Method: " + request.Method;
|
||||
sb.AppendLine(methodLog);
|
||||
|
||||
try
|
||||
{
|
||||
var headers = request.Headers.ToDictionary(x => x.Key, x => x.Value as object);
|
||||
var headersLog = "Headers: " + Json.Encode(headers);
|
||||
sb.AppendLine(headersLog);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (request is AVCommand)
|
||||
{
|
||||
var command = (AVCommand)request;
|
||||
if (command.DataObject != null)
|
||||
{
|
||||
var bodyLog = "Body:" + Json.Encode(command.DataObject);
|
||||
sb.AppendLine(bodyLog);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StreamReader reader = new StreamReader(request.Data);
|
||||
string bodyLog = reader.ReadToEnd();
|
||||
sb.AppendLine(bodyLog);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
var end = "===HTTP Request End===";
|
||||
sb.AppendLine(end);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,599 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// The AVCloud class provides methods for interacting with LeanCloud Cloud Functions.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// For example, this sample code calls the
|
||||
/// "validateGame" Cloud Function and calls processResponse if the call succeeded
|
||||
/// and handleError if it failed.
|
||||
///
|
||||
/// <code>
|
||||
/// var result =
|
||||
/// await AVCloud.CallFunctionAsync<IDictionary<string, object>>("validateGame", parameters);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static class AVCloud
|
||||
{
|
||||
internal static IAVCloudCodeController CloudCodeController
|
||||
{
|
||||
get
|
||||
{
|
||||
return AVPlugins.Instance.CloudCodeController;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls a cloud function.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of data you will receive from the cloud function. This
|
||||
/// can be an IDictionary, string, IList, AVObject, or any other type supported by
|
||||
/// AVObject.</typeparam>
|
||||
/// <param name="name">The cloud function to call.</param>
|
||||
/// <param name="parameters">The parameters to send to the cloud function. This
|
||||
/// dictionary can contain anything that could be passed into a AVObject except for
|
||||
/// AVObjects themselves.</param>
|
||||
/// <param name="sesstionToken"></param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The result of the cloud call.</returns>
|
||||
public static Task<T> CallFunctionAsync<T>(String name, IDictionary<string, object> parameters = null, string sesstionToken = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var sessionTokenTask = AVUser.TakeSessionToken(sesstionToken);
|
||||
|
||||
return sessionTokenTask.OnSuccess(s =>
|
||||
{
|
||||
return CloudCodeController.CallFunctionAsync<T>(name,
|
||||
parameters, s.Result,
|
||||
cancellationToken);
|
||||
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 远程调用云函数,返回结果会反序列化为 <see cref="AVObject"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <param name="sesstionToken"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<T> RPCFunctionAsync<T>(String name, IDictionary<string, object> parameters = null, string sesstionToken = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var sessionTokenTask = AVUser.TakeSessionToken(sesstionToken);
|
||||
|
||||
return sessionTokenTask.OnSuccess(s =>
|
||||
{
|
||||
return CloudCodeController.RPCFunction<T>(name,
|
||||
parameters,
|
||||
s.Result,
|
||||
cancellationToken);
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 LeanCloud 服务器的时间
|
||||
/// <remarks>
|
||||
/// 如果获取失败,将返回 DateTime.MinValue
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <returns>服务器的时间</returns>
|
||||
public static Task<DateTime> GetServerDateTimeAsync()
|
||||
{
|
||||
var command = new AVCommand(relativeUri: "date",
|
||||
method: "GET",
|
||||
sessionToken: null,
|
||||
data: null);
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t =>
|
||||
{
|
||||
DateTime rtn = DateTime.MinValue;
|
||||
if (AVClient.IsSuccessStatusCode(t.Result.Item1))
|
||||
{
|
||||
var date = AVDecoder.Instance.Decode(t.Result.Item2);
|
||||
if (date != null)
|
||||
{
|
||||
if (date is DateTime)
|
||||
{
|
||||
rtn = (DateTime)date;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rtn;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求短信认证。
|
||||
/// </summary>
|
||||
/// <param name="mobilePhoneNumber">手机号。</param>
|
||||
/// <param name="name">应用名称。</param>
|
||||
/// <param name="op">进行的操作名称。</param>
|
||||
/// <param name="ttl">验证码失效时间。</param>
|
||||
/// <returns></returns>
|
||||
public static Task<bool> RequestSMSCodeAsync(string mobilePhoneNumber, string name, string op, int ttl = 10)
|
||||
{
|
||||
return RequestSMSCodeAsync(mobilePhoneNumber, name, op, ttl, CancellationToken.None);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 请求发送验证码。
|
||||
/// </summary>
|
||||
/// <returns>是否发送成功。</returns>
|
||||
/// <param name="mobilePhoneNumber">手机号。</param>
|
||||
/// <param name="name">应用名称。</param>
|
||||
/// <param name="op">进行的操作名称。</param>
|
||||
/// <param name="ttl">验证码失效时间。</param>
|
||||
/// <param name="cancellationToken">Cancellation token。</param>
|
||||
public static Task<bool> RequestSMSCodeAsync(string mobilePhoneNumber, string name, string op, int ttl = 10, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (string.IsNullOrEmpty(mobilePhoneNumber))
|
||||
{
|
||||
throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null);
|
||||
}
|
||||
|
||||
Dictionary<string, object> strs = new Dictionary<string, object>()
|
||||
{
|
||||
{ "mobilePhoneNumber", mobilePhoneNumber },
|
||||
};
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
strs.Add("name", name);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(op))
|
||||
{
|
||||
strs.Add("op", op);
|
||||
}
|
||||
if (ttl > 0)
|
||||
{
|
||||
strs.Add("TTL", ttl);
|
||||
}
|
||||
var command = new AVCommand("requestSmsCode",
|
||||
method: "POST",
|
||||
sessionToken: null,
|
||||
data: strs);
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t =>
|
||||
{
|
||||
return AVClient.IsSuccessStatusCode(t.Result.Item1);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求发送验证码。
|
||||
/// </summary>
|
||||
/// <returns>是否发送成功。</returns>
|
||||
/// <param name="mobilePhoneNumber">手机号。</param>
|
||||
public static Task<bool> RequestSMSCodeAsync(string mobilePhoneNumber)
|
||||
{
|
||||
return AVCloud.RequestSMSCodeAsync(mobilePhoneNumber, CancellationToken.None);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 请求发送验证码。
|
||||
/// </summary>
|
||||
/// <returns>是否发送成功。</returns>
|
||||
/// <param name="mobilePhoneNumber">手机号。</param>
|
||||
/// <param name="cancellationToken">Cancellation Token.</param>
|
||||
public static Task<bool> RequestSMSCodeAsync(string mobilePhoneNumber, CancellationToken cancellationToken)
|
||||
{
|
||||
return AVCloud.RequestSMSCodeAsync(mobilePhoneNumber, null, null, 0, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送手机短信,并指定模板以及传入模板所需的参数。
|
||||
/// Exceptions:
|
||||
/// AVOSCloud.AVException:
|
||||
/// 手机号为空。
|
||||
/// <param name="mobilePhoneNumber"></param>
|
||||
/// <param name="template">Sms's template</param>
|
||||
/// <param name="env">Template variables env.</param>
|
||||
/// <param name="sign">Sms's sign.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns></returns>
|
||||
public static Task<bool> RequestSMSCodeAsync(
|
||||
string mobilePhoneNumber,
|
||||
string template,
|
||||
IDictionary<string, object> env,
|
||||
string sign = "",
|
||||
string validateToken = "",
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
|
||||
if (string.IsNullOrEmpty(mobilePhoneNumber))
|
||||
{
|
||||
throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null);
|
||||
}
|
||||
Dictionary<string, object> strs = new Dictionary<string, object>()
|
||||
{
|
||||
{ "mobilePhoneNumber", mobilePhoneNumber },
|
||||
};
|
||||
strs.Add("template", template);
|
||||
if (String.IsNullOrEmpty(sign))
|
||||
{
|
||||
strs.Add("sign", sign);
|
||||
}
|
||||
if (String.IsNullOrEmpty(validateToken))
|
||||
{
|
||||
strs.Add("validate_token", validateToken);
|
||||
}
|
||||
foreach (var key in env.Keys)
|
||||
{
|
||||
strs.Add(key, env[key]);
|
||||
}
|
||||
var command = new AVCommand("requestSmsCode",
|
||||
method: "POST",
|
||||
sessionToken: null,
|
||||
data: strs);
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t =>
|
||||
{
|
||||
return AVClient.IsSuccessStatusCode(t.Result.Item1);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="mobilePhoneNumber"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<bool> RequestVoiceCodeAsync(string mobilePhoneNumber)
|
||||
{
|
||||
if (string.IsNullOrEmpty(mobilePhoneNumber))
|
||||
{
|
||||
throw new AVException(AVException.ErrorCode.MobilePhoneInvalid, "Moblie Phone number is invalid.", null);
|
||||
}
|
||||
Dictionary<string, object> strs = new Dictionary<string, object>()
|
||||
{
|
||||
{ "mobilePhoneNumber", mobilePhoneNumber },
|
||||
{ "smsType", "voice" },
|
||||
{ "IDD","+86" }
|
||||
};
|
||||
|
||||
var command = new AVCommand("requestSmsCode",
|
||||
method: "POST",
|
||||
sessionToken: null,
|
||||
data: strs);
|
||||
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command).ContinueWith(t =>
|
||||
{
|
||||
return AVClient.IsSuccessStatusCode(t.Result.Item1);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证是否是有效短信验证码。
|
||||
/// </summary>
|
||||
/// <returns>是否验证通过。</returns>
|
||||
/// <param name="mobilePhoneNumber">手机号</param>
|
||||
/// <param name="code">验证码。</param>
|
||||
public static Task<bool> VerifySmsCodeAsync(string code, string mobilePhoneNumber)
|
||||
{
|
||||
return AVCloud.VerifySmsCodeAsync(code, mobilePhoneNumber, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证是否是有效短信验证码。
|
||||
/// </summary>
|
||||
/// <returns>是否验证通过。</returns>
|
||||
/// <param name="code">验证码。</param>
|
||||
/// <param name="mobilePhoneNumber">手机号</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public static Task<bool> VerifySmsCodeAsync(string code, string mobilePhoneNumber, CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand("verifySmsCode/" + code.Trim() + "?mobilePhoneNumber=" + mobilePhoneNumber.Trim(),
|
||||
method: "POST",
|
||||
sessionToken: null,
|
||||
data: null);
|
||||
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t =>
|
||||
{
|
||||
return AVClient.IsSuccessStatusCode(t.Result.Item1);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stands for a captcha result.
|
||||
/// </summary>
|
||||
public class Captcha
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for captcha verify.
|
||||
/// </summary>
|
||||
public string Token { internal set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// Captcha image URL.
|
||||
/// </summary>
|
||||
public string Url { internal set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// Verify the user's input of catpcha.
|
||||
/// </summary>
|
||||
/// <param name="code">User's input of this captcha.</param>
|
||||
/// <param name="cancellationToken">CancellationToken.</param>
|
||||
/// <returns></returns>
|
||||
public Task VerifyAsync(string code, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return AVCloud.VerifyCaptchaAsync(code, Token);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a captcha image.
|
||||
/// </summary>
|
||||
/// <param name="width">captcha image width.</param>
|
||||
/// <param name="height">captcha image height.</param>
|
||||
/// <param name="cancellationToken">CancellationToken.</param>
|
||||
/// <returns>an instance of Captcha.</returns>
|
||||
public static Task<Captcha> RequestCaptchaAsync(int width = 85, int height = 30, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var path = String.Format("requestCaptcha?width={0}&height={1}", width, height);
|
||||
var command = new AVCommand(path, method: "GET", sessionToken: null, data: null);
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary<string, object>;
|
||||
return new Captcha()
|
||||
{
|
||||
Token = decoded["captcha_token"] as string,
|
||||
Url = decoded["captcha_url"] as string,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify the user's input of catpcha.
|
||||
/// </summary>
|
||||
/// <param name="token">The captcha's token, from server.</param>
|
||||
/// <param name="code">User's input of this captcha.</param>
|
||||
/// <param name="cancellationToken">CancellationToken.</param>
|
||||
/// <returns></returns>
|
||||
public static Task<string> VerifyCaptchaAsync(string code, string token, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var data = new Dictionary<string, object>
|
||||
{
|
||||
{ "captcha_token", token },
|
||||
{ "captcha_code", code },
|
||||
};
|
||||
var command = new AVCommand("verifyCaptcha", method: "POST", sessionToken: null, data: data);
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t =>
|
||||
{
|
||||
if (!t.Result.Item2.ContainsKey("validate_token"))
|
||||
throw new KeyNotFoundException("validate_token");
|
||||
return t.Result.Item2["validate_token"] as string;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the custom cloud parameters, you can set them at console https://leancloud.cn/dashboard/devcomponent.html?appid={your_app_Id}#/component/custom_param
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<IDictionary<string, object>> GetCustomParametersAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var command = new AVCommand(string.Format("statistics/apps/{0}/sendPolicy", AVClient.CurrentConfiguration.ApplicationId),
|
||||
method: "GET",
|
||||
sessionToken: null,
|
||||
data: null);
|
||||
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var settings = t.Result.Item2;
|
||||
var CloudParameters = settings["parameters"] as IDictionary<string, object>;
|
||||
return CloudParameters;
|
||||
});
|
||||
}
|
||||
|
||||
public class RealtimeSignature
|
||||
{
|
||||
public string Nonce { internal set; get; }
|
||||
public long Timestamp { internal set; get; }
|
||||
public string ClientId { internal set; get; }
|
||||
public string Signature { internal set; get; }
|
||||
}
|
||||
|
||||
public static Task<RealtimeSignature> RequestRealtimeSignatureAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return AVUser.GetCurrentUserAsync(cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
return RequestRealtimeSignatureAsync(t.Result, cancellationToken);
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
public static Task<RealtimeSignature> RequestRealtimeSignatureAsync(AVUser user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var command = new AVCommand(string.Format("rtm/sign"),
|
||||
method: "POST",
|
||||
sessionToken: null,
|
||||
data: new Dictionary<string, object>
|
||||
{
|
||||
{ "session_token", user.SessionToken },
|
||||
}
|
||||
);
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t =>
|
||||
{
|
||||
var body = t.Result.Item2;
|
||||
return new RealtimeSignature()
|
||||
{
|
||||
Nonce = body["nonce"] as string,
|
||||
Timestamp = (long)body["timestamp"],
|
||||
ClientId = body["client_id"] as string,
|
||||
Signature = body["signature"] as string,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the LeanEngine hosting URL for current app async.
|
||||
/// </summary>
|
||||
/// <returns>The lean engine hosting URL async.</returns>
|
||||
public static Task<string> GetLeanEngineHostingUrlAsync()
|
||||
{
|
||||
return CallFunctionAsync<string>("_internal_extensions_get_domain");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// AVRPCC loud function base.
|
||||
/// </summary>
|
||||
public class AVRPCCloudFunctionBase<P, R>
|
||||
{
|
||||
/// <summary>
|
||||
/// AVRPCD eserialize.
|
||||
/// </summary>
|
||||
public delegate R AVRPCDeserialize<R>(IDictionary<string, object> result);
|
||||
/// <summary>
|
||||
/// AVRPCS erialize.
|
||||
/// </summary>
|
||||
public delegate IDictionary<string, object> AVRPCSerialize<P>(P parameters);
|
||||
|
||||
public AVRPCCloudFunctionBase()
|
||||
: this(true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AVRPCCloudFunctionBase(bool noneParameters)
|
||||
{
|
||||
if (noneParameters)
|
||||
{
|
||||
this.Encode = n =>
|
||||
{
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private AVRPCDeserialize<R> _decode;
|
||||
public AVRPCDeserialize<R> Decode
|
||||
{
|
||||
get
|
||||
{
|
||||
return _decode;
|
||||
}
|
||||
set
|
||||
{
|
||||
_decode = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private AVRPCSerialize<P> _encode;
|
||||
public AVRPCSerialize<P> Encode
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_encode == null)
|
||||
{
|
||||
_encode = n =>
|
||||
{
|
||||
if (n != null)
|
||||
return Json.Parse(n.ToString()) as IDictionary<string, object>;
|
||||
return null;
|
||||
};
|
||||
}
|
||||
return _encode;
|
||||
}
|
||||
set
|
||||
{
|
||||
_encode = value;
|
||||
}
|
||||
|
||||
}
|
||||
public string FunctionName { get; set; }
|
||||
|
||||
public Task<R> ExecuteAsync(P parameters)
|
||||
{
|
||||
return AVUser.GetCurrentAsync().OnSuccess(t =>
|
||||
{
|
||||
var user = t.Result;
|
||||
var encodedParameters = Encode(parameters);
|
||||
var command = new AVCommand(
|
||||
string.Format("call/{0}", Uri.EscapeUriString(this.FunctionName)),
|
||||
method: "POST",
|
||||
sessionToken: user != null ? user.SessionToken : null,
|
||||
data: encodedParameters);
|
||||
|
||||
return AVClient.RunCommandAsync(command);
|
||||
|
||||
}).Unwrap().OnSuccess(s =>
|
||||
{
|
||||
var responseBody = s.Result.Item2;
|
||||
if (!responseBody.ContainsKey("result"))
|
||||
{
|
||||
return default(R);
|
||||
}
|
||||
|
||||
return Decode(responseBody);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class AVObjectRPCCloudFunction : AVObjectRPCCloudFunction<AVObject>
|
||||
{
|
||||
|
||||
}
|
||||
public class AVObjectListRPCCloudFunction : AVObjectListRPCCloudFunction<AVObject>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class AVObjectListRPCCloudFunction<R> : AVRPCCloudFunctionBase<object, IList<R>> where R : AVObject
|
||||
{
|
||||
public AVObjectListRPCCloudFunction()
|
||||
: base(true)
|
||||
{
|
||||
this.Decode = this.AVObjectListDeserializer();
|
||||
}
|
||||
|
||||
public AVRPCDeserialize<IList<R>> AVObjectListDeserializer()
|
||||
{
|
||||
AVRPCDeserialize<IList<R>> del = data =>
|
||||
{
|
||||
var items = data["result"] as IList<object>;
|
||||
|
||||
return items.Select(item =>
|
||||
{
|
||||
var state = AVObjectCoder.Instance.Decode(item as IDictionary<string, object>, AVDecoder.Instance);
|
||||
return AVObject.FromState<AVObject>(state, state.ClassName);
|
||||
}).ToList() as IList<R>;
|
||||
|
||||
};
|
||||
return del;
|
||||
}
|
||||
}
|
||||
|
||||
public class AVObjectRPCCloudFunction<R> : AVRPCCloudFunctionBase<object, R> where R : AVObject
|
||||
{
|
||||
public AVObjectRPCCloudFunction()
|
||||
: base(true)
|
||||
{
|
||||
this.Decode = this.AVObjectDeserializer();
|
||||
}
|
||||
|
||||
|
||||
public AVRPCDeserialize<R> AVObjectDeserializer()
|
||||
{
|
||||
AVRPCDeserialize<R> del = data =>
|
||||
{
|
||||
var item = data["result"] as object;
|
||||
var state = AVObjectCoder.Instance.Decode(item as IDictionary<string, object>, AVDecoder.Instance);
|
||||
|
||||
return AVObject.FromState<R>(state, state.ClassName);
|
||||
};
|
||||
return del;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// The AVConfig is a representation of the remote configuration object,
|
||||
/// that enables you to add things like feature gating, a/b testing or simple "Message of the day".
|
||||
/// </summary>
|
||||
public class AVConfig : IJsonConvertible
|
||||
{
|
||||
private IDictionary<string, object> properties = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest fetched AVConfig.
|
||||
/// </summary>
|
||||
/// <returns>AVConfig object</returns>
|
||||
public static AVConfig CurrentConfig
|
||||
{
|
||||
get
|
||||
{
|
||||
Task<AVConfig> task = ConfigController.CurrentConfigController.GetCurrentConfigAsync();
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ClearCurrentConfig()
|
||||
{
|
||||
ConfigController.CurrentConfigController.ClearCurrentConfigAsync().Wait();
|
||||
}
|
||||
|
||||
internal static void ClearCurrentConfigInMemory()
|
||||
{
|
||||
ConfigController.CurrentConfigController.ClearCurrentConfigInMemoryAsync().Wait();
|
||||
}
|
||||
|
||||
private static IAVConfigController ConfigController
|
||||
{
|
||||
get { return AVPlugins.Instance.ConfigController; }
|
||||
}
|
||||
|
||||
internal AVConfig()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
internal AVConfig(IDictionary<string, object> fetchedConfig)
|
||||
{
|
||||
var props = AVDecoder.Instance.Decode(fetchedConfig["params"]) as IDictionary<string, object>;
|
||||
properties = props;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the AVConfig asynchronously from the server.
|
||||
/// </summary>
|
||||
/// <returns>AVConfig object that was fetched</returns>
|
||||
public static Task<AVConfig> GetAsync()
|
||||
{
|
||||
return GetAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the AVConfig asynchronously from the server.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>AVConfig object that was fetched</returns>
|
||||
public static Task<AVConfig> GetAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return ConfigController.FetchConfigAsync(AVUser.CurrentSessionToken, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value for the key of a particular type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to convert the value to. Supported types are
|
||||
/// AVObject and its descendents, LeanCloud types such as AVRelation and AVGeopoint,
|
||||
/// primitive types,IList<T>, IDictionary<string, T> and strings.</typeparam>
|
||||
/// <param name="key">The key of the element to get.</param>
|
||||
/// <exception cref="System.Collections.Generic.KeyNotFoundException">The property is retrieved
|
||||
/// and <paramref name="key"/> is not found.</exception>
|
||||
/// <exception cref="System.FormatException">The property under this <paramref name="key"/>
|
||||
/// key was found, but of a different type.</exception>
|
||||
public T Get<T>(string key)
|
||||
{
|
||||
return Conversion.To<T>(this.properties[key]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates result with the value for the key, if possible.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The desired type for the value.</typeparam>
|
||||
/// <param name="key">The key to retrieve a value for.</param>
|
||||
/// <param name="result">The value for the given key, converted to the
|
||||
/// requested type, or null if unsuccessful.</param>
|
||||
/// <returns>true if the lookup and conversion succeeded, otherwise false.</returns>
|
||||
public bool TryGetValue<T>(string key, out T result)
|
||||
{
|
||||
if (this.properties.ContainsKey(key))
|
||||
{
|
||||
try
|
||||
{
|
||||
var temp = Conversion.To<T>(this.properties[key]);
|
||||
result = temp;
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Could not convert, do nothing
|
||||
}
|
||||
}
|
||||
result = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value on the config.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for the parameter.</param>
|
||||
/// <exception cref="System.Collections.Generic.KeyNotFoundException">The property is
|
||||
/// retrieved and <paramref name="key"/> is not found.</exception>
|
||||
/// <returns>The value for the key.</returns>
|
||||
virtual public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.properties[key];
|
||||
}
|
||||
}
|
||||
|
||||
IDictionary<string, object> IJsonConvertible.ToJSON()
|
||||
{
|
||||
return new Dictionary<string, object> {
|
||||
{ "params", NoObjectsEncoder.Instance.Encode(properties) }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud {
|
||||
/// <summary>
|
||||
/// Represents download progress.
|
||||
/// </summary>
|
||||
public class AVDownloadProgressEventArgs : EventArgs {
|
||||
public AVDownloadProgressEventArgs() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the progress (a number between 0.0 and 1.0) of a download.
|
||||
/// </summary>
|
||||
public double Progress { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,275 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Exceptions that may occur when sending requests to LeanCloud.
|
||||
/// </summary>
|
||||
public class AVException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Error codes that may be delivered in response to requests to LeanCloud.
|
||||
/// </summary>
|
||||
public enum ErrorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Error code indicating that an unknown error or an error unrelated to LeanCloud
|
||||
/// occurred.
|
||||
/// </summary>
|
||||
OtherCause = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that something has gone wrong with the server.
|
||||
/// If you get this error code, it is LeanCloud's fault.
|
||||
/// </summary>
|
||||
InternalServerError = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating the connection to the LeanCloud servers failed.
|
||||
/// </summary>
|
||||
ConnectionFailed = 100,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating the specified object doesn't exist.
|
||||
/// </summary>
|
||||
ObjectNotFound = 101,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating you tried to query with a datatype that doesn't
|
||||
/// support it, like exact matching an array or object.
|
||||
/// </summary>
|
||||
InvalidQuery = 102,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating a missing or invalid classname. Classnames are
|
||||
/// case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the
|
||||
/// only valid characters.
|
||||
/// </summary>
|
||||
InvalidClassName = 103,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating an unspecified object id.
|
||||
/// </summary>
|
||||
MissingObjectId = 104,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating an invalid key name. Keys are case-sensitive. They
|
||||
/// must start with a letter, and a-zA-Z0-9_ are the only valid characters.
|
||||
/// </summary>
|
||||
InvalidKeyName = 105,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating a malformed pointer. You should not see this unless
|
||||
/// you have been mucking about changing internal LeanCloud code.
|
||||
/// </summary>
|
||||
InvalidPointer = 106,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that badly formed JSON was received upstream. This
|
||||
/// either indicates you have done something unusual with modifying how
|
||||
/// things encode to JSON, or the network is failing badly.
|
||||
/// </summary>
|
||||
InvalidJSON = 107,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the feature you tried to access is only
|
||||
/// available internally for testing purposes.
|
||||
/// </summary>
|
||||
CommandUnavailable = 108,
|
||||
|
||||
/// <summary>
|
||||
/// You must call LeanCloud.initialize before using the LeanCloud library.
|
||||
/// </summary>
|
||||
NotInitialized = 109,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a field was set to an inconsistent type.
|
||||
/// </summary>
|
||||
IncorrectType = 111,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating an invalid channel name. A channel name is either
|
||||
/// an empty string (the broadcast channel) or contains only a-zA-Z0-9_
|
||||
/// characters and starts with a letter.
|
||||
/// </summary>
|
||||
InvalidChannelName = 112,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that push is misconfigured.
|
||||
/// </summary>
|
||||
PushMisconfigured = 115,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the object is too large.
|
||||
/// </summary>
|
||||
ObjectTooLarge = 116,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the operation isn't allowed for clients.
|
||||
/// </summary>
|
||||
OperationForbidden = 119,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating the result was not found in the cache.
|
||||
/// </summary>
|
||||
CacheMiss = 120,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that an invalid key was used in a nested
|
||||
/// JSONObject.
|
||||
/// </summary>
|
||||
InvalidNestedKey = 121,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that an invalid filename was used for AVFile.
|
||||
/// A valid file name contains only a-zA-Z0-9_. characters and is between 1
|
||||
/// and 128 characters.
|
||||
/// </summary>
|
||||
InvalidFileName = 122,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating an invalid ACL was provided.
|
||||
/// </summary>
|
||||
InvalidACL = 123,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the request timed out on the server. Typically
|
||||
/// this indicates that the request is too expensive to run.
|
||||
/// </summary>
|
||||
Timeout = 124,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the email address was invalid.
|
||||
/// </summary>
|
||||
InvalidEmailAddress = 125,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a unique field was given a value that is
|
||||
/// already taken.
|
||||
/// </summary>
|
||||
DuplicateValue = 137,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a role's name is invalid.
|
||||
/// </summary>
|
||||
InvalidRoleName = 139,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that an application quota was exceeded. Upgrade to
|
||||
/// resolve.
|
||||
/// </summary>
|
||||
ExceededQuota = 140,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a Cloud Code script failed.
|
||||
/// </summary>
|
||||
ScriptFailed = 141,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a Cloud Code validation failed.
|
||||
/// </summary>
|
||||
ValidationFailed = 142,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that deleting a file failed.
|
||||
/// </summary>
|
||||
FileDeleteFailed = 153,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the application has exceeded its request limit.
|
||||
/// </summary>
|
||||
RequestLimitExceeded = 155,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the provided event name is invalid.
|
||||
/// </summary>
|
||||
InvalidEventName = 160,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the username is missing or empty.
|
||||
/// </summary>
|
||||
UsernameMissing = 200,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the password is missing or empty.
|
||||
/// </summary>
|
||||
PasswordMissing = 201,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the username has already been taken.
|
||||
/// </summary>
|
||||
UsernameTaken = 202,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the email has already been taken.
|
||||
/// </summary>
|
||||
EmailTaken = 203,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the email is missing, but must be specified.
|
||||
/// </summary>
|
||||
EmailMissing = 204,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a user with the specified email was not found.
|
||||
/// </summary>
|
||||
EmailNotFound = 205,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a user object without a valid session could
|
||||
/// not be altered.
|
||||
/// </summary>
|
||||
SessionMissing = 206,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a user can only be created through signup.
|
||||
/// </summary>
|
||||
MustCreateUserThroughSignup = 207,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that an an account being linked is already linked
|
||||
/// to another user.
|
||||
/// </summary>
|
||||
AccountAlreadyLinked = 208,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that the current session token is invalid.
|
||||
/// </summary>
|
||||
InvalidSessionToken = 209,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a user cannot be linked to an account because
|
||||
/// that account's id could not be found.
|
||||
/// </summary>
|
||||
LinkedIdMissing = 250,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a user with a linked (e.g. Facebook) account
|
||||
/// has an invalid session.
|
||||
/// </summary>
|
||||
InvalidLinkedSession = 251,
|
||||
|
||||
/// <summary>
|
||||
/// Error code indicating that a service being linked (e.g. Facebook or
|
||||
/// Twitter) is unsupported.
|
||||
/// </summary>
|
||||
UnsupportedService = 252,
|
||||
|
||||
/// <summary>
|
||||
/// 手机号不合法
|
||||
/// </summary>
|
||||
MobilePhoneInvalid = 253
|
||||
}
|
||||
|
||||
internal AVException(ErrorCode code, string message, Exception cause = null)
|
||||
: base(message, cause)
|
||||
{
|
||||
this.Code = code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The LeanCloud error code associated with the exception.
|
||||
/// </summary>
|
||||
public ErrorCode Code { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides convenience extension methods for working with collections
|
||||
/// of AVObjects so that you can easily save and fetch them in batches.
|
||||
/// </summary>
|
||||
public static class AVExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves all of the AVObjects in the enumeration. Equivalent to
|
||||
/// calling <see cref="AVObject.SaveAllAsync{T}(IEnumerable{T})"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to save.</param>
|
||||
public static Task SaveAllAsync<T>(this IEnumerable<T> objects) where T : AVObject
|
||||
{
|
||||
return AVObject.SaveAllAsync(objects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves all of the AVObjects in the enumeration. Equivalent to
|
||||
/// calling
|
||||
/// <see cref="AVObject.SaveAllAsync{T}(IEnumerable{T}, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to save.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public static Task SaveAllAsync<T>(
|
||||
this IEnumerable<T> objects, CancellationToken cancellationToken) where T : AVObject
|
||||
{
|
||||
return AVObject.SaveAllAsync(objects, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all of the objects in the enumeration. Equivalent to
|
||||
/// calling <see cref="AVObject.FetchAllAsync{T}(IEnumerable{T})"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to save.</param>
|
||||
public static Task<IEnumerable<T>> FetchAllAsync<T>(this IEnumerable<T> objects)
|
||||
where T : AVObject
|
||||
{
|
||||
return AVObject.FetchAllAsync(objects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all of the objects in the enumeration. Equivalent to
|
||||
/// calling
|
||||
/// <see cref="AVObject.FetchAllAsync{T}(IEnumerable{T}, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to fetch.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public static Task<IEnumerable<T>> FetchAllAsync<T>(
|
||||
this IEnumerable<T> objects, CancellationToken cancellationToken)
|
||||
where T : AVObject
|
||||
{
|
||||
return AVObject.FetchAllAsync(objects, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all of the objects in the enumeration that don't already have
|
||||
/// data. Equivalent to calling
|
||||
/// <see cref="AVObject.FetchAllIfNeededAsync{T}(IEnumerable{T})"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to fetch.</param>
|
||||
public static Task<IEnumerable<T>> FetchAllIfNeededAsync<T>(
|
||||
this IEnumerable<T> objects)
|
||||
where T : AVObject
|
||||
{
|
||||
return AVObject.FetchAllIfNeededAsync(objects);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all of the objects in the enumeration that don't already have
|
||||
/// data. Equivalent to calling
|
||||
/// <see cref="AVObject.FetchAllIfNeededAsync{T}(IEnumerable{T}, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <param name="objects">The objects to fetch.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public static Task<IEnumerable<T>> FetchAllIfNeededAsync<T>(
|
||||
this IEnumerable<T> objects, CancellationToken cancellationToken)
|
||||
where T : AVObject
|
||||
{
|
||||
return AVObject.FetchAllIfNeededAsync(objects, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a query that is the or of the given queries.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of AVObject being queried.</typeparam>
|
||||
/// <param name="source">An initial query to 'or' with additional queries.</param>
|
||||
/// <param name="queries">The list of AVQueries to 'or' together.</param>
|
||||
/// <returns>A query that is the or of the given queries.</returns>
|
||||
public static AVQuery<T> Or<T>(this AVQuery<T> source, params AVQuery<T>[] queries)
|
||||
where T : AVObject
|
||||
{
|
||||
return AVQuery<T>.Or(queries.Concat(new[] { source }));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches this object with the data from the server.
|
||||
/// </summary>
|
||||
public static Task<T> FetchAsync<T>(this T obj) where T : AVObject
|
||||
{
|
||||
return obj.FetchAsyncInternal(CancellationToken.None).OnSuccess(t => (T)t.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches this object with the data from the server.
|
||||
/// </summary>
|
||||
/// <param name="obj">The AVObject to fetch.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public static Task<T> FetchAsync<T>(this T obj, CancellationToken cancellationToken)
|
||||
where T : AVObject
|
||||
{
|
||||
return FetchAsync<T>(obj, null, cancellationToken);
|
||||
}
|
||||
public static Task<T> FetchAsync<T>(this T obj, IEnumerable<string> includeKeys) where T : AVObject
|
||||
{
|
||||
return FetchAsync<T>(obj, includeKeys, CancellationToken.None).OnSuccess(t => (T)t.Result);
|
||||
}
|
||||
public static Task<T> FetchAsync<T>(this T obj, IEnumerable<string> includeKeys, CancellationToken cancellationToken)
|
||||
where T : AVObject
|
||||
{
|
||||
var queryString = new Dictionary<string, object>();
|
||||
if (includeKeys != null)
|
||||
{
|
||||
|
||||
var encode = string.Join(",", includeKeys.ToArray());
|
||||
queryString.Add("include", encode);
|
||||
}
|
||||
return obj.FetchAsyncInternal(queryString, cancellationToken).OnSuccess(t => (T)t.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this AVObject has not been fetched (i.e. <see cref="AVObject.IsDataAvailable"/> returns
|
||||
/// false), fetches this object with the data from the server.
|
||||
/// </summary>
|
||||
/// <param name="obj">The AVObject to fetch.</param>
|
||||
public static Task<T> FetchIfNeededAsync<T>(this T obj) where T : AVObject
|
||||
{
|
||||
return obj.FetchIfNeededAsyncInternal(CancellationToken.None).OnSuccess(t => (T)t.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If this AVObject has not been fetched (i.e. <see cref="AVObject.IsDataAvailable"/> returns
|
||||
/// false), fetches this object with the data from the server.
|
||||
/// </summary>
|
||||
/// <param name="obj">The AVObject to fetch.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public static Task<T> FetchIfNeededAsync<T>(this T obj, CancellationToken cancellationToken)
|
||||
where T : AVObject
|
||||
{
|
||||
return obj.FetchIfNeededAsyncInternal(cancellationToken).OnSuccess(t => (T)t.Result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies a field name for a property on a AVObject subclass.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class AVFieldNameAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new AVFieldName attribute.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">The name of the field on the AVObject that the
|
||||
/// property represents.</param>
|
||||
public AVFieldNameAttribute(string fieldName)
|
||||
{
|
||||
FieldName = fieldName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the field represented by this property.
|
||||
/// </summary>
|
||||
public string FieldName { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,728 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// AVFile is a local representation of a file that is saved to the LeanCloud.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The workflow is to construct a <see cref="AVFile"/> with data and a filename,
|
||||
/// then save it and set it as a field on a AVObject:
|
||||
///
|
||||
/// <code>
|
||||
/// var file = new AVFile("hello.txt",
|
||||
/// new MemoryStream(Encoding.UTF8.GetBytes("hello")));
|
||||
/// await file.SaveAsync();
|
||||
/// var obj = new AVObject("TestObject");
|
||||
/// obj["file"] = file;
|
||||
/// await obj.SaveAsync();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public partial class AVFile : IJsonConvertible
|
||||
{
|
||||
internal static int objectCounter = 0;
|
||||
internal static readonly object Mutex = new object();
|
||||
private FileState state;
|
||||
private readonly Stream dataStream;
|
||||
private readonly TaskQueue taskQueue = new TaskQueue();
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// 通过文件名,数据流,文件类型,文件源信息构建一个 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">数据流</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
/// <param name="metaData">文件源信息</param>
|
||||
public AVFile(string name, Stream data, string mimeType = null, IDictionary<string, object> metaData = null)
|
||||
{
|
||||
mimeType = mimeType == null ? GetMIMEType(name) : mimeType;
|
||||
state = new FileState
|
||||
{
|
||||
Name = name,
|
||||
MimeType = mimeType,
|
||||
MetaData = metaData
|
||||
};
|
||||
this.dataStream = data;
|
||||
lock (Mutex)
|
||||
{
|
||||
objectCounter++;
|
||||
state.counter = objectCounter;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,文件 Byte 数组,以及文件类型构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件 Byte 数组</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
public AVFile(string name, byte[] data, string mimeType = null)
|
||||
: this(name, new MemoryStream(data), mimeType) { }
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,文件流数据,文件类型构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件流数据</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
public AVFile(string name, Stream data, string mimeType = null)
|
||||
: this(name, data, mimeType, new Dictionary<string, object>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 byte 数组以及文件名创建文件
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件的 byte[] 数据</param>
|
||||
public AVFile(string name, byte[] data)
|
||||
: this(name, new MemoryStream(data), new Dictionary<string, object>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,数据 byte[] 数组以及元数据创建文件
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件的 byte[] 数据</param>
|
||||
/// <param name="metaData">元数据</param>
|
||||
public AVFile(string name, byte[] data, IDictionary<string, object> metaData)
|
||||
: this(name, new MemoryStream(data), metaData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,数据流以及元数据创建文件
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件的数据流</param>
|
||||
/// <param name="metaData">元数据</param>
|
||||
public AVFile(string name, Stream data, IDictionary<string, object> metaData)
|
||||
: this(name, data, GetMIMEType(name), metaData)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,数据流以及元数据创建文件
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="data">文件的数据流</param>
|
||||
public AVFile(string name, Stream data)
|
||||
: this(name, data, new Dictionary<string, object>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#region created by url or uri
|
||||
/// <summary>
|
||||
/// 根据文件名,Uri,文件类型以及文件源信息
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="uri">文件Uri</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
/// <param name="metaData">文件源信息</param>
|
||||
public AVFile(string name, Uri uri, string mimeType = null, IDictionary<string, object> metaData = null)
|
||||
{
|
||||
mimeType = mimeType == null ? GetMIMEType(name) : mimeType;
|
||||
state = new FileState
|
||||
{
|
||||
Name = name,
|
||||
Url = uri,
|
||||
MetaData = metaData,
|
||||
MimeType = mimeType
|
||||
};
|
||||
lock (Mutex)
|
||||
{
|
||||
objectCounter++;
|
||||
state.counter = objectCounter;
|
||||
}
|
||||
this.isExternal = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,文件 Url,文件类型,文件源信息构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="url">文件 Url</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
/// <param name="metaData">文件源信息</param>
|
||||
public AVFile(string name, string url, string mimeType = null, IDictionary<string, object> metaData = null)
|
||||
: this(name, new Uri(url), mimeType, metaData)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,文件 Url以及文件的源信息构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="url">文件 Url</param>
|
||||
/// <param name="metaData">文件源信息</param>
|
||||
public AVFile(string name, string url, IDictionary<string, object> metaData)
|
||||
: this(name, url, null, metaData)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名,文件 Uri,以及文件类型构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="uri">文件 Uri</param>
|
||||
/// <param name="mimeType">文件类型</param>
|
||||
public AVFile(string name, Uri uri, string mimeType = null)
|
||||
: this(name, uri, mimeType, new Dictionary<string, object>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名以及文件 Uri 构建 AVFile
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="uri">文件 Uri</param>
|
||||
public AVFile(string name, Uri uri)
|
||||
: this(name, uri, null, new Dictionary<string, object>())
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据文件名和 Url 创建文件
|
||||
/// </summary>
|
||||
/// <param name="name">文件名</param>
|
||||
/// <param name="url">文件的 Url</param>
|
||||
public AVFile(string name, string url)
|
||||
: this(name, new Uri(url))
|
||||
{
|
||||
}
|
||||
|
||||
internal AVFile(FileState filestate)
|
||||
{
|
||||
this.state = filestate;
|
||||
}
|
||||
internal AVFile(string objectId)
|
||||
: this(new FileState()
|
||||
{
|
||||
ObjectId = objectId
|
||||
})
|
||||
{
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the file still needs to be saved.
|
||||
/// </summary>
|
||||
public bool IsDirty
|
||||
{
|
||||
get
|
||||
{
|
||||
return state.Url == null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the file. Before save is called, this is the filename given by
|
||||
/// the user. After save is called, that name gets prefixed with a unique identifier.
|
||||
/// </summary>
|
||||
[AVFieldName("name")]
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return state.Name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MIME type of the file. This is either passed in to the constructor or
|
||||
/// inferred from the file extension. "unknown/unknown" will be used if neither is
|
||||
/// available.
|
||||
/// </summary>
|
||||
public string MimeType
|
||||
{
|
||||
get
|
||||
{
|
||||
return state.MimeType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url of the file. It is only available after you save the file or after
|
||||
/// you get the file from a <see cref="AVObject"/>.
|
||||
/// </summary>
|
||||
[AVFieldName("url")]
|
||||
public Uri Url
|
||||
{
|
||||
get
|
||||
{
|
||||
return state.Url;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IAVFileController FileController
|
||||
{
|
||||
get
|
||||
{
|
||||
return AVPlugins.Instance.FileController;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
IDictionary<string, object> IJsonConvertible.ToJSON()
|
||||
{
|
||||
if (this.IsDirty)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"AVFile must be saved before it can be serialized.");
|
||||
}
|
||||
return new Dictionary<string, object> {
|
||||
{"__type", "File"},
|
||||
{ "id", ObjectId },
|
||||
{"name", Name},
|
||||
{"url", Url.AbsoluteUri}
|
||||
};
|
||||
}
|
||||
|
||||
#region Save
|
||||
|
||||
/// <summary>
|
||||
/// Saves the file to the LeanCloud cloud.
|
||||
/// </summary>
|
||||
public Task SaveAsync()
|
||||
{
|
||||
return SaveAsync(null, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the file to the LeanCloud cloud.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public Task SaveAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return SaveAsync(null, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the file to the LeanCloud cloud.
|
||||
/// </summary>
|
||||
/// <param name="progress">The progress callback.</param>
|
||||
public Task SaveAsync(IProgress<AVUploadProgressEventArgs> progress)
|
||||
{
|
||||
return SaveAsync(progress, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the file to the LeanCloud cloud.
|
||||
/// </summary>
|
||||
/// <param name="progress">The progress callback.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public Task SaveAsync(IProgress<AVUploadProgressEventArgs> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (this.isExternal)
|
||||
return this.SaveExternal();
|
||||
|
||||
return taskQueue.Enqueue(
|
||||
toAwait => FileController.SaveAsync(state, dataStream, AVUser.CurrentSessionToken, progress, cancellationToken), cancellationToken)
|
||||
.OnSuccess(t =>
|
||||
{
|
||||
state = t.Result;
|
||||
});
|
||||
}
|
||||
|
||||
internal Task SaveExternal()
|
||||
{
|
||||
Dictionary<string, object> strs = new Dictionary<string, object>()
|
||||
{
|
||||
{ "url", this.Url.ToString() },
|
||||
{ "name",this.Name },
|
||||
{ "mime_type",this.MimeType},
|
||||
{ "metaData",this.MetaData}
|
||||
};
|
||||
AVCommand cmd = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(this.ObjectId))
|
||||
{
|
||||
cmd = new AVCommand("files/" + this.ObjectId,
|
||||
method: "PUT", sessionToken: AVUser.CurrentSessionToken, data: strs);
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd = new AVCommand("files", method: "POST", sessionToken: AVUser.CurrentSessionToken, data: strs);
|
||||
}
|
||||
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(cmd).ContinueWith(t =>
|
||||
{
|
||||
var result = t.Result.Item2;
|
||||
this.state.ObjectId = result["objectId"].ToString();
|
||||
return AVClient.IsSuccessStatusCode(t.Result.Item1);
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Compatible
|
||||
private readonly static Dictionary<string, string> MIMETypesDictionary;
|
||||
private object mutex = new object();
|
||||
private bool isExternal;
|
||||
/// <summary>
|
||||
/// 文件在 LeanCloud 的唯一Id 标识
|
||||
/// </summary>
|
||||
public string ObjectId
|
||||
{
|
||||
get
|
||||
{
|
||||
String str;
|
||||
lock (this.mutex)
|
||||
{
|
||||
str = state.ObjectId;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件的元数据。
|
||||
/// </summary>
|
||||
public IDictionary<string, object> MetaData
|
||||
{
|
||||
get
|
||||
{
|
||||
return state.MetaData;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文件是否为外链文件。
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// </value>
|
||||
public bool IsExternal
|
||||
{
|
||||
get
|
||||
{
|
||||
return isExternal;
|
||||
}
|
||||
}
|
||||
|
||||
static AVFile()
|
||||
{
|
||||
Dictionary<string, string> strs = 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" },
|
||||
};
|
||||
AVFile.MIMETypesDictionary = strs;
|
||||
}
|
||||
internal static string GetMIMEType(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string str = Path.GetExtension(fileName).Remove(0, 1);
|
||||
if (!AVFile.MIMETypesDictionary.ContainsKey(str))
|
||||
{
|
||||
return "unknown/unknown";
|
||||
}
|
||||
return AVFile.MIMETypesDictionary[str];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "unknown/unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 ObjectId 获取文件
|
||||
/// </summary>
|
||||
/// <remarks>获取之后并没有实际执行下载,只是加载了文件的元信息以及物理地址(Url)
|
||||
/// </remarks>
|
||||
public static Task<AVFile> GetFileWithObjectIdAsync(string objectId, CancellationToken cancellationToken)
|
||||
{
|
||||
string currentSessionToken = AVUser.CurrentSessionToken;
|
||||
return FileController.GetAsync(objectId, currentSessionToken, cancellationToken).OnSuccess(_ =>
|
||||
{
|
||||
var filestate = _.Result;
|
||||
return new AVFile(filestate);
|
||||
});
|
||||
}
|
||||
|
||||
public static AVFile CreateWithoutData(string objectId)
|
||||
{
|
||||
return new AVFile(objectId);
|
||||
}
|
||||
|
||||
public static AVFile CreateWithState(FileState state)
|
||||
{
|
||||
return new AVFile(state);
|
||||
}
|
||||
|
||||
public static AVFile CreateWithData(string objectId,string name, string url,IDictionary<string,object> metaData)
|
||||
{
|
||||
var fileState = new FileState();
|
||||
fileState.Name = name;
|
||||
fileState.ObjectId = objectId;
|
||||
fileState.Url = new Uri(url);
|
||||
fileState.MetaData = metaData;
|
||||
return CreateWithState(fileState);
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据 ObjectId 获取文件
|
||||
/// </summary>
|
||||
/// <remarks>获取之后并没有实际执行下载,只是加载了文件的元信息以及物理地址(Url)
|
||||
/// </remarks>
|
||||
public static Task<AVFile> GetFileWithObjectIdAsync(string objectId)
|
||||
{
|
||||
return GetFileWithObjectIdAsync(objectId, CancellationToken.None);
|
||||
}
|
||||
|
||||
internal void MergeFromJSON(IDictionary<string, object> jsonData)
|
||||
{
|
||||
lock (this.mutex)
|
||||
{
|
||||
state.ObjectId = jsonData["objectId"] as string;
|
||||
state.Url = new Uri(jsonData["url"] as string, UriKind.Absolute);
|
||||
if (jsonData.ContainsKey("name"))
|
||||
{
|
||||
state.Name = jsonData["name"] as string;
|
||||
}
|
||||
if (jsonData.ContainsKey("metaData"))
|
||||
{
|
||||
state.MetaData = jsonData["metaData"] as Dictionary<string, object>;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除文件
|
||||
/// </summary>
|
||||
/// <returns>Task</returns>
|
||||
public Task DeleteAsync()
|
||||
{
|
||||
return DeleteAsync(CancellationToken.None);
|
||||
}
|
||||
internal Task DeleteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return taskQueue.Enqueue(toAwait => DeleteAsync(toAwait, cancellationToken),
|
||||
cancellationToken);
|
||||
|
||||
}
|
||||
internal Task DeleteAsync(Task toAwait, CancellationToken cancellationToken)
|
||||
{
|
||||
if (ObjectId == null)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
string sessionToken = AVUser.CurrentSessionToken;
|
||||
|
||||
return toAwait.OnSuccess(_ =>
|
||||
{
|
||||
return FileController.DeleteAsync(state, sessionToken, cancellationToken);
|
||||
}).Unwrap().OnSuccess(_ => { });
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a distance between two AVGeoPoints.
|
||||
/// </summary>
|
||||
public struct AVGeoDistance
|
||||
{
|
||||
private const double EarthMeanRadiusKilometers = 6371.0;
|
||||
private const double EarthMeanRadiusMiles = 3958.8;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a AVGeoDistance.
|
||||
/// </summary>
|
||||
/// <param name="radians">The distance in radians.</param>
|
||||
public AVGeoDistance(double radians)
|
||||
: this()
|
||||
{
|
||||
Radians = radians;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance in radians.
|
||||
/// </summary>
|
||||
public double Radians { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance in miles.
|
||||
/// </summary>
|
||||
public double Miles
|
||||
{
|
||||
get
|
||||
{
|
||||
return Radians * EarthMeanRadiusMiles;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance in kilometers.
|
||||
/// </summary>
|
||||
public double Kilometers
|
||||
{
|
||||
get
|
||||
{
|
||||
return Radians * EarthMeanRadiusKilometers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a AVGeoDistance from a number of miles.
|
||||
/// </summary>
|
||||
/// <param name="miles">The number of miles.</param>
|
||||
/// <returns>A AVGeoDistance for the given number of miles.</returns>
|
||||
public static AVGeoDistance FromMiles(double miles)
|
||||
{
|
||||
return new AVGeoDistance(miles / EarthMeanRadiusMiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a AVGeoDistance from a number of kilometers.
|
||||
/// </summary>
|
||||
/// <param name="kilometers">The number of kilometers.</param>
|
||||
/// <returns>A AVGeoDistance for the given number of kilometers.</returns>
|
||||
public static AVGeoDistance FromKilometers(double kilometers)
|
||||
{
|
||||
return new AVGeoDistance(kilometers / EarthMeanRadiusKilometers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a AVGeoDistance from a number of radians.
|
||||
/// </summary>
|
||||
/// <param name="radians">The number of radians.</param>
|
||||
/// <returns>A AVGeoDistance for the given number of radians.</returns>
|
||||
public static AVGeoDistance FromRadians(double radians)
|
||||
{
|
||||
return new AVGeoDistance(radians);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// AVGeoPoint represents a latitude / longitude point that may be associated
|
||||
/// with a key in a AVObject or used as a reference point for geo queries.
|
||||
/// This allows proximity-based queries on the key.
|
||||
///
|
||||
/// Only one key in a class may contain a GeoPoint.
|
||||
/// </summary>
|
||||
public struct AVGeoPoint : IJsonConvertible
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a AVGeoPoint with the specified latitude and longitude.
|
||||
/// </summary>
|
||||
/// <param name="latitude">The point's latitude.</param>
|
||||
/// <param name="longitude">The point's longitude.</param>
|
||||
public AVGeoPoint(double latitude, double longitude)
|
||||
: this()
|
||||
{
|
||||
Latitude = latitude;
|
||||
Longitude = longitude;
|
||||
}
|
||||
|
||||
private double latitude;
|
||||
/// <summary>
|
||||
/// Gets or sets the latitude of the GeoPoint. Valid range is [-90, 90].
|
||||
/// Extremes should not be used.
|
||||
/// </summary>
|
||||
public double Latitude
|
||||
{
|
||||
get
|
||||
{
|
||||
return latitude;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value > 90 || value < -90)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("value",
|
||||
"Latitude must be within the range [-90, 90]");
|
||||
}
|
||||
latitude = value;
|
||||
}
|
||||
}
|
||||
|
||||
private double longitude;
|
||||
/// <summary>
|
||||
/// Gets or sets the longitude. Valid range is [-180, 180].
|
||||
/// Extremes should not be used.
|
||||
/// </summary>
|
||||
public double Longitude
|
||||
{
|
||||
get
|
||||
{
|
||||
return longitude;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value > 180 || value < -180)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("value",
|
||||
"Longitude must be within the range [-180, 180]");
|
||||
}
|
||||
longitude = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the distance in radians between this point and another GeoPoint. This is the smallest angular
|
||||
/// distance between the two points.
|
||||
/// </summary>
|
||||
/// <param name="point">GeoPoint describing the other point being measured against.</param>
|
||||
/// <returns>The distance in between the two points.</returns>
|
||||
public AVGeoDistance DistanceTo(AVGeoPoint point)
|
||||
{
|
||||
double d2r = Math.PI / 180; // radian conversion factor
|
||||
double lat1rad = Latitude * d2r;
|
||||
double long1rad = longitude * d2r;
|
||||
double lat2rad = point.Latitude * d2r;
|
||||
double long2rad = point.Longitude * d2r;
|
||||
double deltaLat = lat1rad - lat2rad;
|
||||
double deltaLong = long1rad - long2rad;
|
||||
double sinDeltaLatDiv2 = Math.Sin(deltaLat / 2);
|
||||
double sinDeltaLongDiv2 = Math.Sin(deltaLong / 2);
|
||||
// Square of half the straight line chord distance between both points.
|
||||
// [0.0, 1.0]
|
||||
double a = sinDeltaLatDiv2 * sinDeltaLatDiv2 +
|
||||
Math.Cos(lat1rad) * Math.Cos(lat2rad) * sinDeltaLongDiv2 * sinDeltaLongDiv2;
|
||||
a = Math.Min(1.0, a);
|
||||
return new AVGeoDistance(2 * Math.Asin(Math.Sqrt(a)));
|
||||
}
|
||||
|
||||
IDictionary<string, object> IJsonConvertible.ToJSON()
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "__type", "GeoPoint" },
|
||||
{ "latitude", Latitude },
|
||||
{ "longitude", Longitude }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,360 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The AVQuery class defines a query that is used to fetch AVObjects. The
|
||||
/// most common use case is finding all objects that match a query through the
|
||||
/// <see cref="FindAsync()"/> method.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// This sample code fetches all objects of
|
||||
/// class <c>"MyClass"</c>:
|
||||
///
|
||||
/// <code>
|
||||
/// AVQuery query = new AVQuery("MyClass");
|
||||
/// IEnumerable<AVObject> result = await query.FindAsync();
|
||||
/// </code>
|
||||
///
|
||||
/// A AVQuery can also be used to retrieve a single object whose id is known,
|
||||
/// through the <see cref="GetAsync(string)"/> method. For example, this sample code
|
||||
/// fetches an object of class <c>"MyClass"</c> and id <c>myId</c>.
|
||||
///
|
||||
/// <code>
|
||||
/// AVQuery query = new AVQuery("MyClass");
|
||||
/// AVObject result = await query.GetAsync(myId);
|
||||
/// </code>
|
||||
///
|
||||
/// A AVQuery can also be used to count the number of objects that match the
|
||||
/// query without retrieving all of those objects. For example, this sample code
|
||||
/// counts the number of objects of the class <c>"MyClass"</c>.
|
||||
///
|
||||
/// <code>
|
||||
/// AVQuery query = new AVQuery("MyClass");
|
||||
/// int count = await query.CountAsync();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class AVQuery<T> : AVQueryPair<AVQuery<T>, T>, IAVQuery
|
||||
where T : AVObject
|
||||
{
|
||||
internal static IAVQueryController QueryController
|
||||
{
|
||||
get
|
||||
{
|
||||
return AVPlugins.Instance.QueryController;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IObjectSubclassingController SubclassingController
|
||||
{
|
||||
get
|
||||
{
|
||||
return AVPlugins.Instance.SubclassingController;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调试时可以用来查看最终的发送的查询语句
|
||||
/// </summary>
|
||||
private string JsonString
|
||||
{
|
||||
get
|
||||
{
|
||||
return AVClient.SerializeJsonString(this.BuildParameters(true));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private constructor for composition of queries. A Source query is required,
|
||||
/// but the remaining values can be null if they won't be changed in this
|
||||
/// composition.
|
||||
/// </summary>
|
||||
private AVQuery(AVQuery<T> source,
|
||||
IDictionary<string, object> where = null,
|
||||
IEnumerable<string> replacementOrderBy = null,
|
||||
IEnumerable<string> thenBy = null,
|
||||
int? skip = null,
|
||||
int? limit = null,
|
||||
IEnumerable<string> includes = null,
|
||||
IEnumerable<string> selectedKeys = null,
|
||||
String redirectClassNameForKey = null)
|
||||
: base(source, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//internal override AVQuery<T> CreateInstance(
|
||||
// AVQuery<T> source,
|
||||
// IDictionary<string, object> where = null,
|
||||
// IEnumerable<string> replacementOrderBy = null,
|
||||
// IEnumerable<string> thenBy = null,
|
||||
// int? skip = null,
|
||||
// int? limit = null,
|
||||
// IEnumerable<string> includes = null,
|
||||
// IEnumerable<string> selectedKeys = null,
|
||||
// String redirectClassNameForKey = null)
|
||||
//{
|
||||
// return new AVQuery<T>(this, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey);
|
||||
//}
|
||||
|
||||
public override AVQuery<T> CreateInstance(
|
||||
IDictionary<string, object> where = null,
|
||||
IEnumerable<string> replacementOrderBy = null,
|
||||
IEnumerable<string> thenBy = null,
|
||||
int? skip = null,
|
||||
int? limit = null,
|
||||
IEnumerable<string> includes = null,
|
||||
IEnumerable<string> selectedKeys = null,
|
||||
String redirectClassNameForKey = null)
|
||||
{
|
||||
return new AVQuery<T>(this, where, replacementOrderBy, thenBy, skip, limit, includes, selectedKeys, redirectClassNameForKey);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a query based upon the AVObject subclass used as the generic parameter for the AVQuery.
|
||||
/// </summary>
|
||||
public AVQuery()
|
||||
: this(SubclassingController.GetClassName(typeof(T)))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a query. A default query with no further parameters will retrieve
|
||||
/// all <see cref="AVObject"/>s of the provided class.
|
||||
/// </summary>
|
||||
/// <param name="className">The name of the class to retrieve AVObjects for.</param>
|
||||
public AVQuery(string className)
|
||||
: base(className)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a query that is the or of the given queries.
|
||||
/// </summary>
|
||||
/// <param name="queries">The list of AVQueries to 'or' together.</param>
|
||||
/// <returns>A AVQquery that is the 'or' of the passed in queries.</returns>
|
||||
public static AVQuery<T> Or(IEnumerable<AVQuery<T>> queries)
|
||||
{
|
||||
string className = null;
|
||||
var orValue = new List<IDictionary<string, object>>();
|
||||
// We need to cast it to non-generic IEnumerable because of AOT-limitation
|
||||
var nonGenericQueries = (IEnumerable)queries;
|
||||
foreach (var obj in nonGenericQueries)
|
||||
{
|
||||
var q = (AVQuery<T>)obj;
|
||||
if (className != null && q.className != className)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"All of the queries in an or query must be on the same class.");
|
||||
}
|
||||
className = q.className;
|
||||
var parameters = q.BuildParameters();
|
||||
if (parameters.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
object where;
|
||||
if (!parameters.TryGetValue("where", out where) || parameters.Count > 1)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"None of the queries in an or query can have non-filtering clauses");
|
||||
}
|
||||
orValue.Add(where as IDictionary<string, object>);
|
||||
}
|
||||
return new AVQuery<T>(new AVQuery<T>(className),
|
||||
where: new Dictionary<string, object> {
|
||||
{ "$or", orValue}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a list of AVObjects that satisfy this query from LeanCloud.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The list of AVObjects that match this query.</returns>
|
||||
public override Task<IEnumerable<T>> FindAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return AVUser.GetCurrentUserAsync().OnSuccess(t =>
|
||||
{
|
||||
return QueryController.FindAsync<T>(this, t.Result, cancellationToken);
|
||||
}).Unwrap().OnSuccess(t =>
|
||||
{
|
||||
IEnumerable<IObjectState> states = t.Result;
|
||||
return (from state in states
|
||||
select AVObject.FromState<T>(state, ClassName));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves at most one AVObject that satisfies this query.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A single AVObject that satisfies this query, or else null.</returns>
|
||||
public override Task<T> FirstOrDefaultAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return AVUser.GetCurrentUserAsync().OnSuccess(t =>
|
||||
{
|
||||
return QueryController.FirstAsync<T>(this, t.Result, cancellationToken);
|
||||
}).Unwrap().OnSuccess(t =>
|
||||
{
|
||||
IObjectState state = t.Result;
|
||||
return state == null ? default(T) : AVObject.FromState<T>(state, ClassName);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves at most one AVObject that satisfies this query.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A single AVObject that satisfies this query.</returns>
|
||||
/// <exception cref="AVException">If no results match the query.</exception>
|
||||
public override Task<T> FirstAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return FirstOrDefaultAsync(cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
if (t.Result == null)
|
||||
{
|
||||
throw new AVException(AVException.ErrorCode.ObjectNotFound,
|
||||
"No results matched the query.");
|
||||
}
|
||||
return t.Result;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of objects that match this query.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The number of objects that match this query.</returns>
|
||||
public override Task<int> CountAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return AVUser.GetCurrentUserAsync().OnSuccess(t =>
|
||||
{
|
||||
return QueryController.CountAsync<T>(this, t.Result, cancellationToken);
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a AVObject whose id is already known by fetching data
|
||||
/// from the server.
|
||||
/// </summary>
|
||||
/// <param name="objectId">ObjectId of the AVObject to fetch.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The AVObject for the given objectId.</returns>
|
||||
public override Task<T> GetAsync(string objectId, CancellationToken cancellationToken)
|
||||
{
|
||||
AVQuery<T> singleItemQuery = new AVQuery<T>(className)
|
||||
.WhereEqualTo("objectId", objectId);
|
||||
singleItemQuery = new AVQuery<T>(singleItemQuery, includes: this.includes, selectedKeys: this.selectedKeys, limit: 1);
|
||||
return singleItemQuery.FindAsync(cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var result = t.Result.FirstOrDefault();
|
||||
if (result == null)
|
||||
{
|
||||
throw new AVException(AVException.ErrorCode.ObjectNotFound,
|
||||
"Object with the given objectId not found.");
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
#region CQL
|
||||
/// <summary>
|
||||
/// 执行 CQL 查询
|
||||
/// </summary>
|
||||
/// <param name="cql">CQL 语句</param>
|
||||
/// <param name="cancellationToken">CancellationToken</param>
|
||||
/// <returns>返回符合条件的对象集合</returns>
|
||||
public static Task<IEnumerable<T>> DoCloudQueryAsync(string cql, CancellationToken cancellationToken)
|
||||
{
|
||||
var queryString = string.Format("cloudQuery?cql={0}", Uri.EscapeDataString(cql));
|
||||
|
||||
return rebuildObjectFromCloudQueryResult(queryString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行 CQL 查询
|
||||
/// </summary>
|
||||
/// <param name="cql"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<IEnumerable<T>> DoCloudQueryAsync(string cql)
|
||||
{
|
||||
return DoCloudQueryAsync(cql, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行 CQL 查询
|
||||
/// </summary>
|
||||
/// <param name="cqlTeamplate">带有占位符的模板 cql 语句</param>
|
||||
/// <param name="pvalues">占位符对应的参数数组</param>
|
||||
/// <returns></returns>
|
||||
public static Task<IEnumerable<T>> DoCloudQueryAsync(string cqlTeamplate, params object[] pvalues)
|
||||
{
|
||||
string queryStringTemplate = "cloudQuery?cql={0}&pvalues={1}";
|
||||
string pSrting = Json.Encode(pvalues);
|
||||
string queryString = string.Format(queryStringTemplate, Uri.EscapeDataString(cqlTeamplate), Uri.EscapeDataString(pSrting));
|
||||
|
||||
return rebuildObjectFromCloudQueryResult(queryString);
|
||||
}
|
||||
|
||||
internal static Task<IEnumerable<T>> rebuildObjectFromCloudQueryResult(string queryString)
|
||||
{
|
||||
var command = new AVCommand(queryString,
|
||||
method: "GET",
|
||||
sessionToken: AVUser.CurrentSessionToken,
|
||||
data: null);
|
||||
|
||||
return AVPlugins.Instance.CommandRunner.RunCommandAsync(command, cancellationToken: CancellationToken.None).OnSuccess(t =>
|
||||
{
|
||||
var items = t.Result.Item2["results"] as IList<object>;
|
||||
var className = t.Result.Item2["className"].ToString();
|
||||
|
||||
IEnumerable<IObjectState> states = (from item in items
|
||||
select AVObjectCoder.Instance.Decode(item as IDictionary<string, object>, AVDecoder.Instance));
|
||||
|
||||
return (from state in states
|
||||
select AVObject.FromState<T>(state, className));
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to compare with the current object.</param>
|
||||
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c></returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null || !(obj is AVQuery<T>))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var other = obj as AVQuery<T>;
|
||||
return Object.Equals(this.className, other.ClassName) &&
|
||||
this.where.CollectionsEqual(other.where) &&
|
||||
this.orderBy.CollectionsEqual(other.orderBy) &&
|
||||
this.includes.CollectionsEqual(other.includes) &&
|
||||
this.selectedKeys.CollectionsEqual(other.selectedKeys) &&
|
||||
Object.Equals(this.skip, other.skip) &&
|
||||
Object.Equals(this.limit, other.limit);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,818 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="AVQuery{T}"/> to support
|
||||
/// Linq-style queries.
|
||||
/// </summary>
|
||||
public static class AVQueryExtensions
|
||||
{
|
||||
private static readonly MethodInfo getMethod;
|
||||
private static readonly MethodInfo stringContains;
|
||||
private static readonly MethodInfo stringStartsWith;
|
||||
private static readonly MethodInfo stringEndsWith;
|
||||
private static readonly MethodInfo containsMethod;
|
||||
private static readonly MethodInfo notContainsMethod;
|
||||
private static readonly MethodInfo containsKeyMethod;
|
||||
private static readonly MethodInfo notContainsKeyMethod;
|
||||
private static readonly Dictionary<MethodInfo, MethodInfo> functionMappings;
|
||||
static AVQueryExtensions()
|
||||
{
|
||||
getMethod = GetMethod<AVObject>(obj => obj.Get<int>(null)).GetGenericMethodDefinition();
|
||||
stringContains = GetMethod<string>(str => str.Contains(null));
|
||||
stringStartsWith = GetMethod<string>(str => str.StartsWith(null));
|
||||
stringEndsWith = GetMethod<string>(str => str.EndsWith(null));
|
||||
functionMappings = new Dictionary<MethodInfo, MethodInfo> {
|
||||
{
|
||||
stringContains,
|
||||
GetMethod<AVQuery<AVObject>>(q => q.WhereContains(null, null))
|
||||
},
|
||||
{
|
||||
stringStartsWith,
|
||||
GetMethod<AVQuery<AVObject>>(q => q.WhereStartsWith(null, null))
|
||||
},
|
||||
{
|
||||
stringEndsWith,
|
||||
GetMethod<AVQuery<AVObject>>(q => q.WhereEndsWith(null,null))
|
||||
},
|
||||
};
|
||||
containsMethod = GetMethod<object>(
|
||||
o => AVQueryExtensions.ContainsStub<object>(null, null)).GetGenericMethodDefinition();
|
||||
notContainsMethod = GetMethod<object>(
|
||||
o => AVQueryExtensions.NotContainsStub<object>(null, null))
|
||||
.GetGenericMethodDefinition();
|
||||
|
||||
containsKeyMethod = GetMethod<object>(o => AVQueryExtensions.ContainsKeyStub(null, null));
|
||||
notContainsKeyMethod = GetMethod<object>(
|
||||
o => AVQueryExtensions.NotContainsKeyStub(null, null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a MethodInfo for a top-level method call.
|
||||
/// </summary>
|
||||
private static MethodInfo GetMethod<T>(Expression<Action<T>> expression)
|
||||
{
|
||||
return (expression.Body as MethodCallExpression).Method;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a query is normalized, this is a placeholder to indicate we should
|
||||
/// add a WhereContainedIn() clause.
|
||||
/// </summary>
|
||||
private static bool ContainsStub<T>(object collection, T value)
|
||||
{
|
||||
throw new NotImplementedException(
|
||||
"Exists only for expression translation as a placeholder.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a query is normalized, this is a placeholder to indicate we should
|
||||
/// add a WhereNotContainedIn() clause.
|
||||
/// </summary>
|
||||
private static bool NotContainsStub<T>(object collection, T value)
|
||||
{
|
||||
throw new NotImplementedException(
|
||||
"Exists only for expression translation as a placeholder.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a query is normalized, this is a placeholder to indicate that we should
|
||||
/// add a WhereExists() clause.
|
||||
/// </summary>
|
||||
private static bool ContainsKeyStub(AVObject obj, string key)
|
||||
{
|
||||
throw new NotImplementedException(
|
||||
"Exists only for expression translation as a placeholder.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a query is normalized, this is a placeholder to indicate that we should
|
||||
/// add a WhereDoesNotExist() clause.
|
||||
/// </summary>
|
||||
private static bool NotContainsKeyStub(AVObject obj, string key)
|
||||
{
|
||||
throw new NotImplementedException(
|
||||
"Exists only for expression translation as a placeholder.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates an expression and throws if the expression has components that can't be
|
||||
/// evaluated (e.g. uses the parameter that's only represented by an object on the server).
|
||||
/// </summary>
|
||||
private static object GetValue(Expression exp)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Expression.Lambda(
|
||||
typeof(Func<>).MakeGenericType(exp.Type), exp).Compile().DynamicInvoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to evaluate expression: " + exp, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the MethodCallExpression is a call to AVObject.Get(),
|
||||
/// which is the call we normalize all indexing into the AVObject to.
|
||||
/// </summary>
|
||||
private static bool IsAVObjectGet(MethodCallExpression node)
|
||||
{
|
||||
if (node == null || node.Object == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!typeof(AVObject).GetTypeInfo().IsAssignableFrom(node.Object.Type.GetTypeInfo()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == getMethod;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Visits an Expression, converting AVObject.Get/AVObject[]/AVObject.Property,
|
||||
/// and nested indices into a single call to AVObject.Get() with a "field path" like
|
||||
/// "foo.bar.baz"
|
||||
/// </summary>
|
||||
private class ObjectNormalizer : ExpressionVisitor
|
||||
{
|
||||
protected override Expression VisitIndex(IndexExpression node)
|
||||
{
|
||||
var visitedObject = Visit(node.Object);
|
||||
var indexer = visitedObject as MethodCallExpression;
|
||||
if (IsAVObjectGet(indexer))
|
||||
{
|
||||
var indexValue = GetValue(node.Arguments[0]) as string;
|
||||
if (indexValue == null)
|
||||
{
|
||||
throw new InvalidOperationException("Index must be a string");
|
||||
}
|
||||
var newPath = GetValue(indexer.Arguments[0]) + "." + indexValue;
|
||||
return Expression.Call(indexer.Object,
|
||||
getMethod.MakeGenericMethod(node.Type),
|
||||
Expression.Constant(newPath, typeof(string)));
|
||||
}
|
||||
return base.VisitIndex(node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for a AVFieldName attribute and use that as the path component, turning
|
||||
/// properties like foo.ObjectId into foo.Get("objectId")
|
||||
/// </summary>
|
||||
protected override Expression VisitMember(MemberExpression node)
|
||||
{
|
||||
var fieldName = node.Member.GetCustomAttribute<AVFieldNameAttribute>();
|
||||
if (fieldName != null &&
|
||||
typeof(AVObject).GetTypeInfo().IsAssignableFrom(node.Expression.Type.GetTypeInfo()))
|
||||
{
|
||||
var newPath = fieldName.FieldName;
|
||||
return Expression.Call(node.Expression,
|
||||
getMethod.MakeGenericMethod(node.Type),
|
||||
Expression.Constant(newPath, typeof(string)));
|
||||
}
|
||||
return base.VisitMember(node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a AVObject.Get() call has been cast, just change the generic parameter.
|
||||
/// </summary>
|
||||
protected override Expression VisitUnary(UnaryExpression node)
|
||||
{
|
||||
var methodCall = Visit(node.Operand) as MethodCallExpression;
|
||||
if ((node.NodeType == ExpressionType.Convert ||
|
||||
node.NodeType == ExpressionType.ConvertChecked) &&
|
||||
IsAVObjectGet(methodCall))
|
||||
{
|
||||
return Expression.Call(methodCall.Object,
|
||||
getMethod.MakeGenericMethod(node.Type),
|
||||
methodCall.Arguments);
|
||||
}
|
||||
return base.VisitUnary(node);
|
||||
}
|
||||
|
||||
protected override Expression VisitMethodCall(MethodCallExpression node)
|
||||
{
|
||||
if (node.Method.Name == "get_Item" && node.Object is ParameterExpression)
|
||||
{
|
||||
var indexPath = GetValue(node.Arguments[0]) as string;
|
||||
return Expression.Call(node.Object,
|
||||
getMethod.MakeGenericMethod(typeof(object)),
|
||||
Expression.Constant(indexPath, typeof(string)));
|
||||
}
|
||||
|
||||
if (node.Method.Name == "get_Item" || IsAVObjectGet(node))
|
||||
{
|
||||
var visitedObject = Visit(node.Object);
|
||||
var indexer = visitedObject as MethodCallExpression;
|
||||
if (IsAVObjectGet(indexer))
|
||||
{
|
||||
var indexValue = GetValue(node.Arguments[0]) as string;
|
||||
if (indexValue == null)
|
||||
{
|
||||
throw new InvalidOperationException("Index must be a string");
|
||||
}
|
||||
var newPath = GetValue(indexer.Arguments[0]) + "." + indexValue;
|
||||
return Expression.Call(indexer.Object,
|
||||
getMethod.MakeGenericMethod(node.Type),
|
||||
Expression.Constant(newPath, typeof(string)));
|
||||
}
|
||||
}
|
||||
return base.VisitMethodCall(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes Where expressions.
|
||||
/// </summary>
|
||||
private class WhereNormalizer : ExpressionVisitor
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes binary operators. <, >, <=, >= !=, and ==
|
||||
/// This puts the AVObject.Get() on the left side of the operation
|
||||
/// (reversing it if necessary), and normalizes the AVObject.Get()
|
||||
/// </summary>
|
||||
protected override Expression VisitBinary(BinaryExpression node)
|
||||
{
|
||||
var leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression;
|
||||
var rightTransformed = new ObjectNormalizer().Visit(node.Right) as MethodCallExpression;
|
||||
|
||||
MethodCallExpression objectExpression;
|
||||
Expression filterExpression;
|
||||
bool inverted;
|
||||
if (leftTransformed != null)
|
||||
{
|
||||
objectExpression = leftTransformed;
|
||||
filterExpression = node.Right;
|
||||
inverted = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
objectExpression = rightTransformed;
|
||||
filterExpression = node.Left;
|
||||
inverted = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (node.NodeType)
|
||||
{
|
||||
case ExpressionType.GreaterThan:
|
||||
if (inverted)
|
||||
{
|
||||
return Expression.LessThan(objectExpression, filterExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Expression.GreaterThan(objectExpression, filterExpression);
|
||||
}
|
||||
case ExpressionType.GreaterThanOrEqual:
|
||||
if (inverted)
|
||||
{
|
||||
return Expression.LessThanOrEqual(objectExpression, filterExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Expression.GreaterThanOrEqual(objectExpression, filterExpression);
|
||||
}
|
||||
case ExpressionType.LessThan:
|
||||
if (inverted)
|
||||
{
|
||||
return Expression.GreaterThan(objectExpression, filterExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Expression.LessThan(objectExpression, filterExpression);
|
||||
}
|
||||
case ExpressionType.LessThanOrEqual:
|
||||
if (inverted)
|
||||
{
|
||||
return Expression.GreaterThanOrEqual(objectExpression, filterExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Expression.LessThanOrEqual(objectExpression, filterExpression);
|
||||
}
|
||||
case ExpressionType.Equal:
|
||||
return Expression.Equal(objectExpression, filterExpression);
|
||||
case ExpressionType.NotEqual:
|
||||
return Expression.NotEqual(objectExpression, filterExpression);
|
||||
}
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
throw new InvalidOperationException("Operation not supported: " + node);
|
||||
}
|
||||
return base.VisitBinary(node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a ! operator is used, this removes the ! and instead calls the equivalent
|
||||
/// function (so e.g. == becomes !=, < becomes >=, Contains becomes NotContains)
|
||||
/// </summary>
|
||||
protected override Expression VisitUnary(UnaryExpression node)
|
||||
{
|
||||
// Normalizes inversion
|
||||
if (node.NodeType == ExpressionType.Not)
|
||||
{
|
||||
var visitedOperand = Visit(node.Operand);
|
||||
var binaryOperand = visitedOperand as BinaryExpression;
|
||||
if (binaryOperand != null)
|
||||
{
|
||||
switch (binaryOperand.NodeType)
|
||||
{
|
||||
case ExpressionType.GreaterThan:
|
||||
return Expression.LessThanOrEqual(binaryOperand.Left, binaryOperand.Right);
|
||||
case ExpressionType.GreaterThanOrEqual:
|
||||
return Expression.LessThan(binaryOperand.Left, binaryOperand.Right);
|
||||
case ExpressionType.LessThan:
|
||||
return Expression.GreaterThanOrEqual(binaryOperand.Left, binaryOperand.Right);
|
||||
case ExpressionType.LessThanOrEqual:
|
||||
return Expression.GreaterThan(binaryOperand.Left, binaryOperand.Right);
|
||||
case ExpressionType.Equal:
|
||||
return Expression.NotEqual(binaryOperand.Left, binaryOperand.Right);
|
||||
case ExpressionType.NotEqual:
|
||||
return Expression.Equal(binaryOperand.Left, binaryOperand.Right);
|
||||
}
|
||||
}
|
||||
|
||||
var methodCallOperand = visitedOperand as MethodCallExpression;
|
||||
if (methodCallOperand != null)
|
||||
{
|
||||
if (methodCallOperand.Method.IsGenericMethod)
|
||||
{
|
||||
if (methodCallOperand.Method.GetGenericMethodDefinition() == containsMethod)
|
||||
{
|
||||
var genericNotContains = notContainsMethod.MakeGenericMethod(
|
||||
methodCallOperand.Method.GetGenericArguments());
|
||||
return Expression.Call(genericNotContains, methodCallOperand.Arguments.ToArray());
|
||||
}
|
||||
if (methodCallOperand.Method.GetGenericMethodDefinition() == notContainsMethod)
|
||||
{
|
||||
var genericContains = containsMethod.MakeGenericMethod(
|
||||
methodCallOperand.Method.GetGenericArguments());
|
||||
return Expression.Call(genericContains, methodCallOperand.Arguments.ToArray());
|
||||
}
|
||||
}
|
||||
if (methodCallOperand.Method == containsKeyMethod)
|
||||
{
|
||||
return Expression.Call(notContainsKeyMethod, methodCallOperand.Arguments.ToArray());
|
||||
}
|
||||
if (methodCallOperand.Method == notContainsKeyMethod)
|
||||
{
|
||||
return Expression.Call(containsKeyMethod, methodCallOperand.Arguments.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
return base.VisitUnary(node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes .Equals into == and Contains() into the appropriate stub.
|
||||
/// </summary>
|
||||
protected override Expression VisitMethodCall(MethodCallExpression node)
|
||||
{
|
||||
// Convert .Equals() into ==
|
||||
if (node.Method.Name == "Equals" &&
|
||||
node.Method.ReturnType == typeof(bool) &&
|
||||
node.Method.GetParameters().Length == 1)
|
||||
{
|
||||
var obj = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression;
|
||||
var parameter = new ObjectNormalizer().Visit(node.Arguments[0]) as MethodCallExpression;
|
||||
if ((IsAVObjectGet(obj) && (obj.Object is ParameterExpression)) ||
|
||||
(IsAVObjectGet(parameter) && (parameter.Object is ParameterExpression)))
|
||||
{
|
||||
return Expression.Equal(node.Object, node.Arguments[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the .Contains() into a ContainsStub
|
||||
if (node.Method != stringContains &&
|
||||
node.Method.Name == "Contains" &&
|
||||
node.Method.ReturnType == typeof(bool) &&
|
||||
node.Method.GetParameters().Length <= 2)
|
||||
{
|
||||
var collection = node.Method.GetParameters().Length == 1 ?
|
||||
node.Object :
|
||||
node.Arguments[0];
|
||||
var parameterIndex = node.Method.GetParameters().Length - 1;
|
||||
var parameter = new ObjectNormalizer().Visit(node.Arguments[parameterIndex])
|
||||
as MethodCallExpression;
|
||||
if (IsAVObjectGet(parameter) && (parameter.Object is ParameterExpression))
|
||||
{
|
||||
var genericContains = containsMethod.MakeGenericMethod(parameter.Type);
|
||||
return Expression.Call(genericContains, collection, parameter);
|
||||
}
|
||||
var target = new ObjectNormalizer().Visit(collection) as MethodCallExpression;
|
||||
var element = node.Arguments[parameterIndex];
|
||||
if (IsAVObjectGet(target) && (target.Object is ParameterExpression))
|
||||
{
|
||||
var genericContains = containsMethod.MakeGenericMethod(element.Type);
|
||||
return Expression.Call(genericContains, target, element);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert obj["foo.bar"].ContainsKey("baz") into obj.ContainsKey("foo.bar.baz")
|
||||
if (node.Method.Name == "ContainsKey" &&
|
||||
node.Method.ReturnType == typeof(bool) &&
|
||||
node.Method.GetParameters().Length == 1)
|
||||
{
|
||||
var getter = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression;
|
||||
Expression target = null;
|
||||
string path = null;
|
||||
if (IsAVObjectGet(getter) && getter.Object is ParameterExpression)
|
||||
{
|
||||
target = getter.Object;
|
||||
path = GetValue(getter.Arguments[0]) + "." + GetValue(node.Arguments[0]);
|
||||
return Expression.Call(containsKeyMethod, target, Expression.Constant(path));
|
||||
}
|
||||
else if (node.Object is ParameterExpression)
|
||||
{
|
||||
target = node.Object;
|
||||
path = GetValue(node.Arguments[0]) as string;
|
||||
}
|
||||
if (target != null && path != null)
|
||||
{
|
||||
return Expression.Call(containsKeyMethod, target, Expression.Constant(path));
|
||||
}
|
||||
}
|
||||
return base.VisitMethodCall(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a normalized method call expression into the appropriate AVQuery clause.
|
||||
/// </summary>
|
||||
private static AVQuery<T> WhereMethodCall<T>(
|
||||
this AVQuery<T> source, Expression<Func<T, bool>> expression, MethodCallExpression node)
|
||||
where T : AVObject
|
||||
{
|
||||
if (IsAVObjectGet(node) && (node.Type == typeof(bool) || node.Type == typeof(bool?)))
|
||||
{
|
||||
// This is a raw boolean field access like 'where obj.Get<bool>("foo")'
|
||||
return source.WhereEqualTo(GetValue(node.Arguments[0]) as string, true);
|
||||
}
|
||||
|
||||
MethodInfo translatedMethod;
|
||||
if (functionMappings.TryGetValue(node.Method, out translatedMethod))
|
||||
{
|
||||
var objTransformed = new ObjectNormalizer().Visit(node.Object) as MethodCallExpression;
|
||||
if (!(IsAVObjectGet(objTransformed) &&
|
||||
objTransformed.Object == expression.Parameters[0]))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The left-hand side of a supported function call must be a AVObject field access.");
|
||||
}
|
||||
var fieldPath = GetValue(objTransformed.Arguments[0]);
|
||||
var containedIn = GetValue(node.Arguments[0]);
|
||||
var queryType = translatedMethod.DeclaringType.GetGenericTypeDefinition()
|
||||
.MakeGenericType(typeof(T));
|
||||
translatedMethod = ReflectionHelpers.GetMethod(queryType,
|
||||
translatedMethod.Name,
|
||||
translatedMethod.GetParameters().Select(p => p.ParameterType).ToArray());
|
||||
return translatedMethod.Invoke(source, new[] { fieldPath, containedIn }) as AVQuery<T>;
|
||||
}
|
||||
|
||||
if (node.Arguments[0] == expression.Parameters[0])
|
||||
{
|
||||
// obj.ContainsKey("foo") --> query.WhereExists("foo")
|
||||
if (node.Method == containsKeyMethod)
|
||||
{
|
||||
return source.WhereExists(GetValue(node.Arguments[1]) as string);
|
||||
}
|
||||
// !obj.ContainsKey("foo") --> query.WhereDoesNotExist("foo")
|
||||
if (node.Method == notContainsKeyMethod)
|
||||
{
|
||||
return source.WhereDoesNotExist(GetValue(node.Arguments[1]) as string);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.Method.IsGenericMethod)
|
||||
{
|
||||
if (node.Method.GetGenericMethodDefinition() == containsMethod)
|
||||
{
|
||||
// obj.Get<IList<T>>("path").Contains(someValue)
|
||||
if (IsAVObjectGet(node.Arguments[0] as MethodCallExpression))
|
||||
{
|
||||
return source.WhereEqualTo(
|
||||
GetValue(((MethodCallExpression)node.Arguments[0]).Arguments[0]) as string,
|
||||
GetValue(node.Arguments[1]));
|
||||
}
|
||||
// someList.Contains(obj.Get<T>("path"))
|
||||
if (IsAVObjectGet(node.Arguments[1] as MethodCallExpression))
|
||||
{
|
||||
var collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable;
|
||||
return source.WhereContainedIn(
|
||||
GetValue(((MethodCallExpression)node.Arguments[1]).Arguments[0]) as string,
|
||||
collection.Cast<object>());
|
||||
}
|
||||
}
|
||||
|
||||
if (node.Method.GetGenericMethodDefinition() == notContainsMethod)
|
||||
{
|
||||
// !obj.Get<IList<T>>("path").Contains(someValue)
|
||||
if (IsAVObjectGet(node.Arguments[0] as MethodCallExpression))
|
||||
{
|
||||
return source.WhereNotEqualTo(
|
||||
GetValue(((MethodCallExpression)node.Arguments[0]).Arguments[0]) as string,
|
||||
GetValue(node.Arguments[1]));
|
||||
}
|
||||
// !someList.Contains(obj.Get<T>("path"))
|
||||
if (IsAVObjectGet(node.Arguments[1] as MethodCallExpression))
|
||||
{
|
||||
var collection = GetValue(node.Arguments[0]) as System.Collections.IEnumerable;
|
||||
return source.WhereNotContainedIn(
|
||||
GetValue(((MethodCallExpression)node.Arguments[1]).Arguments[0]) as string,
|
||||
collection.Cast<object>());
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException(node.Method + " is not a supported method call in a where expression.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a normalized binary expression into the appropriate AVQuery clause.
|
||||
/// </summary>
|
||||
private static AVQuery<T> WhereBinaryExpression<T>(
|
||||
this AVQuery<T> source, Expression<Func<T, bool>> expression, BinaryExpression node)
|
||||
where T : AVObject
|
||||
{
|
||||
var leftTransformed = new ObjectNormalizer().Visit(node.Left) as MethodCallExpression;
|
||||
|
||||
if (!(IsAVObjectGet(leftTransformed) &&
|
||||
leftTransformed.Object == expression.Parameters[0]))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Where expressions must have one side be a field operation on a AVObject.");
|
||||
}
|
||||
|
||||
var fieldPath = GetValue(leftTransformed.Arguments[0]) as string;
|
||||
var filterValue = GetValue(node.Right);
|
||||
|
||||
if (filterValue != null && !AVEncoder.IsValidType(filterValue))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Where clauses must use types compatible with AVObjects.");
|
||||
}
|
||||
|
||||
switch (node.NodeType)
|
||||
{
|
||||
case ExpressionType.GreaterThan:
|
||||
return source.WhereGreaterThan(fieldPath, filterValue);
|
||||
case ExpressionType.GreaterThanOrEqual:
|
||||
return source.WhereGreaterThanOrEqualTo(fieldPath, filterValue);
|
||||
case ExpressionType.LessThan:
|
||||
return source.WhereLessThan(fieldPath, filterValue);
|
||||
case ExpressionType.LessThanOrEqual:
|
||||
return source.WhereLessThanOrEqualTo(fieldPath, filterValue);
|
||||
case ExpressionType.Equal:
|
||||
return source.WhereEqualTo(fieldPath, filterValue);
|
||||
case ExpressionType.NotEqual:
|
||||
return source.WhereNotEqualTo(fieldPath, filterValue);
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
"Where expressions do not support this operator.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters a query based upon the predicate provided.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of AVObject being queried for.</typeparam>
|
||||
/// <param name="source">The base <see cref="AVQuery{TSource}"/> to which
|
||||
/// the predicate will be added.</param>
|
||||
/// <param name="predicate">A function to test each AVObject for a condition.
|
||||
/// The predicate must be able to be represented by one of the standard Where
|
||||
/// functions on AVQuery</param>
|
||||
/// <returns>A new AVQuery whose results will match the given predicate as
|
||||
/// well as the Source's filters.</returns>
|
||||
public static AVQuery<TSource> Where<TSource>(
|
||||
this AVQuery<TSource> source, Expression<Func<TSource, bool>> predicate)
|
||||
where TSource : AVObject
|
||||
{
|
||||
// Handle top-level logic operators && and ||
|
||||
var binaryExpression = predicate.Body as BinaryExpression;
|
||||
if (binaryExpression != null)
|
||||
{
|
||||
if (binaryExpression.NodeType == ExpressionType.AndAlso)
|
||||
{
|
||||
return source
|
||||
.Where(Expression.Lambda<Func<TSource, bool>>(
|
||||
binaryExpression.Left, predicate.Parameters))
|
||||
.Where(Expression.Lambda<Func<TSource, bool>>(
|
||||
binaryExpression.Right, predicate.Parameters));
|
||||
}
|
||||
if (binaryExpression.NodeType == ExpressionType.OrElse)
|
||||
{
|
||||
var left = source.Where(Expression.Lambda<Func<TSource, bool>>(
|
||||
binaryExpression.Left, predicate.Parameters));
|
||||
var right = source.Where(Expression.Lambda<Func<TSource, bool>>(
|
||||
binaryExpression.Right, predicate.Parameters));
|
||||
return left.Or(right);
|
||||
}
|
||||
}
|
||||
|
||||
var normalized = new WhereNormalizer().Visit(predicate.Body);
|
||||
|
||||
var methodCallExpr = normalized as MethodCallExpression;
|
||||
if (methodCallExpr != null)
|
||||
{
|
||||
return source.WhereMethodCall(predicate, methodCallExpr);
|
||||
}
|
||||
|
||||
var binaryExpr = normalized as BinaryExpression;
|
||||
if (binaryExpr != null)
|
||||
{
|
||||
return source.WhereBinaryExpression(predicate, binaryExpr);
|
||||
}
|
||||
|
||||
var unaryExpr = normalized as UnaryExpression;
|
||||
if (unaryExpr != null && unaryExpr.NodeType == ExpressionType.Not)
|
||||
{
|
||||
var node = unaryExpr.Operand as MethodCallExpression;
|
||||
if (IsAVObjectGet(node) && (node.Type == typeof(bool) || node.Type == typeof(bool?)))
|
||||
{
|
||||
// This is a raw boolean field access like 'where !obj.Get<bool>("foo")'
|
||||
return source.WhereNotEqualTo(GetValue(node.Arguments[0]) as string, true);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"Encountered an unsupported expression for AVQueries.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes an OrderBy's keySelector expression and then extracts the path
|
||||
/// from the AVObject.Get() call.
|
||||
/// </summary>
|
||||
private static string GetOrderByPath<TSource, TSelector>(
|
||||
Expression<Func<TSource, TSelector>> keySelector)
|
||||
{
|
||||
string result = null;
|
||||
var normalized = new ObjectNormalizer().Visit(keySelector.Body);
|
||||
var callExpr = normalized as MethodCallExpression;
|
||||
if (IsAVObjectGet(callExpr) && callExpr.Object == keySelector.Parameters[0])
|
||||
{
|
||||
// We're operating on the parameter
|
||||
result = GetValue(callExpr.Arguments[0]) as string;
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"OrderBy expression must be a field access on a AVObject.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orders a query based upon the key selector provided.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of AVObject being queried for.</typeparam>
|
||||
/// <typeparam name="TSelector">The type of key returned by keySelector.</typeparam>
|
||||
/// <param name="source">The query to order.</param>
|
||||
/// <param name="keySelector">A function to extract a key from the AVObject.</param>
|
||||
/// <returns>A new AVQuery based on Source whose results will be ordered by
|
||||
/// the key specified in the keySelector.</returns>
|
||||
public static AVQuery<TSource> OrderBy<TSource, TSelector>(
|
||||
this AVQuery<TSource> source, Expression<Func<TSource, TSelector>> keySelector)
|
||||
where TSource : AVObject
|
||||
{
|
||||
return source.OrderBy(GetOrderByPath(keySelector));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Orders a query based upon the key selector provided.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of AVObject being queried for.</typeparam>
|
||||
/// <typeparam name="TSelector">The type of key returned by keySelector.</typeparam>
|
||||
/// <param name="source">The query to order.</param>
|
||||
/// <param name="keySelector">A function to extract a key from the AVObject.</param>
|
||||
/// <returns>A new AVQuery based on Source whose results will be ordered by
|
||||
/// the key specified in the keySelector.</returns>
|
||||
public static AVQuery<TSource> OrderByDescending<TSource, TSelector>(
|
||||
this AVQuery<TSource> source, Expression<Func<TSource, TSelector>> keySelector)
|
||||
where TSource : AVObject
|
||||
{
|
||||
return source.OrderByDescending(GetOrderByPath(keySelector));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a subsequent ordering of a query based upon the key selector provided.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of AVObject being queried for.</typeparam>
|
||||
/// <typeparam name="TSelector">The type of key returned by keySelector.</typeparam>
|
||||
/// <param name="source">The query to order.</param>
|
||||
/// <param name="keySelector">A function to extract a key from the AVObject.</param>
|
||||
/// <returns>A new AVQuery based on Source whose results will be ordered by
|
||||
/// the key specified in the keySelector.</returns>
|
||||
public static AVQuery<TSource> ThenBy<TSource, TSelector>(
|
||||
this AVQuery<TSource> source, Expression<Func<TSource, TSelector>> keySelector)
|
||||
where TSource : AVObject
|
||||
{
|
||||
return source.ThenBy(GetOrderByPath(keySelector));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a subsequent ordering of a query based upon the key selector provided.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of AVObject being queried for.</typeparam>
|
||||
/// <typeparam name="TSelector">The type of key returned by keySelector.</typeparam>
|
||||
/// <param name="source">The query to order.</param>
|
||||
/// <param name="keySelector">A function to extract a key from the AVObject.</param>
|
||||
/// <returns>A new AVQuery based on Source whose results will be ordered by
|
||||
/// the key specified in the keySelector.</returns>
|
||||
public static AVQuery<TSource> ThenByDescending<TSource, TSelector>(
|
||||
this AVQuery<TSource> source, Expression<Func<TSource, TSelector>> keySelector)
|
||||
where TSource : AVObject
|
||||
{
|
||||
return source.ThenByDescending(GetOrderByPath(keySelector));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Correlates the elements of two queries based on matching keys.
|
||||
/// </summary>
|
||||
/// <typeparam name="TOuter">The type of AVObjects of the first query.</typeparam>
|
||||
/// <typeparam name="TInner">The type of AVObjects of the second query.</typeparam>
|
||||
/// <typeparam name="TKey">The type of the keys returned by the key selector
|
||||
/// functions.</typeparam>
|
||||
/// <typeparam name="TResult">The type of the result. This must match either
|
||||
/// TOuter or TInner</typeparam>
|
||||
/// <param name="outer">The first query to join.</param>
|
||||
/// <param name="inner">The query to join to the first query.</param>
|
||||
/// <param name="outerKeySelector">A function to extract a join key from the results of
|
||||
/// the first query.</param>
|
||||
/// <param name="innerKeySelector">A function to extract a join key from the results of
|
||||
/// the second query.</param>
|
||||
/// <param name="resultSelector">A function to select either the outer or inner query
|
||||
/// result to determine which query is the base query.</param>
|
||||
/// <returns>A new AVQuery with a WhereMatchesQuery or WhereMatchesKeyInQuery
|
||||
/// clause based upon the query indicated in the <paramref name="resultSelector"/>.</returns>
|
||||
public static AVQuery<TResult> Join<TOuter, TInner, TKey, TResult>(
|
||||
this AVQuery<TOuter> outer,
|
||||
AVQuery<TInner> inner,
|
||||
Expression<Func<TOuter, TKey>> outerKeySelector,
|
||||
Expression<Func<TInner, TKey>> innerKeySelector,
|
||||
Expression<Func<TOuter, TInner, TResult>> resultSelector)
|
||||
where TOuter : AVObject
|
||||
where TInner : AVObject
|
||||
where TResult : AVObject
|
||||
{
|
||||
// resultSelector must select either the inner object or the outer object. If it's the inner
|
||||
// object, reverse the query.
|
||||
if (resultSelector.Body == resultSelector.Parameters[1])
|
||||
{
|
||||
// The inner object was selected.
|
||||
return inner.Join<TInner, TOuter, TKey, TInner>(
|
||||
outer,
|
||||
innerKeySelector,
|
||||
outerKeySelector,
|
||||
(i, o) => i) as AVQuery<TResult>;
|
||||
}
|
||||
if (resultSelector.Body != resultSelector.Parameters[0])
|
||||
{
|
||||
throw new InvalidOperationException("Joins must select either the outer or inner object.");
|
||||
}
|
||||
|
||||
// Normalize both selectors
|
||||
Expression outerNormalized = new ObjectNormalizer().Visit(outerKeySelector.Body);
|
||||
Expression innerNormalized = new ObjectNormalizer().Visit(innerKeySelector.Body);
|
||||
MethodCallExpression outerAsGet = outerNormalized as MethodCallExpression;
|
||||
MethodCallExpression innerAsGet = innerNormalized as MethodCallExpression;
|
||||
if (IsAVObjectGet(outerAsGet) && outerAsGet.Object == outerKeySelector.Parameters[0])
|
||||
{
|
||||
var outerKey = GetValue(outerAsGet.Arguments[0]) as string;
|
||||
|
||||
if (IsAVObjectGet(innerAsGet) && innerAsGet.Object == innerKeySelector.Parameters[0])
|
||||
{
|
||||
// Both are key accesses, so treat this as a WhereMatchesKeyInQuery
|
||||
var innerKey = GetValue(innerAsGet.Arguments[0]) as string;
|
||||
return outer.WhereMatchesKeyInQuery(outerKey, innerKey, inner) as AVQuery<TResult>;
|
||||
}
|
||||
|
||||
if (innerKeySelector.Body == innerKeySelector.Parameters[0])
|
||||
{
|
||||
// The inner selector is on the result of the query itself, so treat this as a
|
||||
// WhereMatchesQuery
|
||||
return outer.WhereMatchesQuery(outerKey, inner) as AVQuery<TResult>;
|
||||
}
|
||||
throw new InvalidOperationException(
|
||||
"The key for the joined object must be a AVObject or a field access " +
|
||||
"on the AVObject.");
|
||||
}
|
||||
|
||||
// TODO (hallucinogen): If we ever support "and" queries fully and/or support a "where this object
|
||||
// matches some key in some other query" (as opposed to requiring a key on this query), we
|
||||
// can add support for even more types of joins.
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"The key for the selected object must be a field access on the AVObject.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// A common base class for AVRelations.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class AVRelationBase : IJsonConvertible
|
||||
{
|
||||
private AVObject parent;
|
||||
private string key;
|
||||
private string targetClassName;
|
||||
|
||||
internal AVRelationBase(AVObject parent, string key)
|
||||
{
|
||||
EnsureParentAndKey(parent, key);
|
||||
}
|
||||
|
||||
internal AVRelationBase(AVObject parent, string key, string targetClassName)
|
||||
: this(parent, key)
|
||||
{
|
||||
this.targetClassName = targetClassName;
|
||||
}
|
||||
|
||||
internal static IObjectSubclassingController SubclassingController
|
||||
{
|
||||
get
|
||||
{
|
||||
return AVPlugins.Instance.SubclassingController;
|
||||
}
|
||||
}
|
||||
|
||||
internal void EnsureParentAndKey(AVObject parent, string key)
|
||||
{
|
||||
this.parent = this.parent ?? parent;
|
||||
this.key = this.key ?? key;
|
||||
Debug.Assert(this.parent == parent, "Relation retrieved from two different objects");
|
||||
Debug.Assert(this.key == key, "Relation retrieved from two different keys");
|
||||
}
|
||||
|
||||
internal void Add(AVObject obj)
|
||||
{
|
||||
var change = new AVRelationOperation(new[] { obj }, null);
|
||||
parent.PerformOperation(key, change);
|
||||
targetClassName = change.TargetClassName;
|
||||
}
|
||||
|
||||
internal void Remove(AVObject obj)
|
||||
{
|
||||
var change = new AVRelationOperation(null, new[] { obj });
|
||||
parent.PerformOperation(key, change);
|
||||
targetClassName = change.TargetClassName;
|
||||
}
|
||||
|
||||
IDictionary<string, object> IJsonConvertible.ToJSON()
|
||||
{
|
||||
return new Dictionary<string, object> {
|
||||
{ "__type", "Relation"},
|
||||
{ "className", targetClassName}
|
||||
};
|
||||
}
|
||||
|
||||
internal AVQuery<T> GetQuery<T>() where T : AVObject
|
||||
{
|
||||
if (targetClassName != null)
|
||||
{
|
||||
return new AVQuery<T>(targetClassName)
|
||||
.WhereRelatedTo(parent, key);
|
||||
}
|
||||
|
||||
return new AVQuery<T>(parent.ClassName)
|
||||
.RedirectClassName(key)
|
||||
.WhereRelatedTo(parent, key);
|
||||
}
|
||||
|
||||
internal AVQuery<T> GetReverseQuery<T>(T target) where T : AVObject
|
||||
{
|
||||
if (target.ObjectId == null)
|
||||
{
|
||||
throw new ArgumentNullException("target.ObjectId", "can not query a relation without target ObjectId.");
|
||||
}
|
||||
|
||||
return new AVQuery<T>(parent.ClassName).WhereEqualTo(key, target);
|
||||
}
|
||||
|
||||
internal string TargetClassName
|
||||
{
|
||||
get
|
||||
{
|
||||
return targetClassName;
|
||||
}
|
||||
set
|
||||
{
|
||||
targetClassName = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces the proper AVRelation<T> instance for the given classname.
|
||||
/// </summary>
|
||||
internal static AVRelationBase CreateRelation(AVObject parent,
|
||||
string key,
|
||||
string targetClassName)
|
||||
{
|
||||
var targetType = SubclassingController.GetType(targetClassName) ?? typeof(AVObject);
|
||||
|
||||
Expression<Func<AVRelation<AVObject>>> createRelationExpr =
|
||||
() => CreateRelation<AVObject>(parent, key, targetClassName);
|
||||
var createRelationMethod =
|
||||
((MethodCallExpression)createRelationExpr.Body)
|
||||
.Method
|
||||
.GetGenericMethodDefinition()
|
||||
.MakeGenericMethod(targetType);
|
||||
return (AVRelationBase)createRelationMethod.Invoke(null, new object[] { parent, key, targetClassName });
|
||||
}
|
||||
|
||||
private static AVRelation<T> CreateRelation<T>(AVObject parent, string key, string targetClassName)
|
||||
where T : AVObject
|
||||
{
|
||||
return new AVRelation<T>(parent, key, targetClassName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to all of the children of a many-to-many relationship. Each instance of
|
||||
/// AVRelation is associated with a particular parent and key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the child objects.</typeparam>
|
||||
public sealed class AVRelation<T> : AVRelationBase where T : AVObject
|
||||
{
|
||||
|
||||
internal AVRelation(AVObject parent, string key) : base(parent, key) { }
|
||||
|
||||
internal AVRelation(AVObject parent, string key, string targetClassName)
|
||||
: base(parent, key, targetClassName) { }
|
||||
|
||||
/// <summary>
|
||||
/// Adds an object to this relation. The object must already have been saved.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to add.</param>
|
||||
public void Add(T obj)
|
||||
{
|
||||
base.Add(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an object from this relation. The object must already have been saved.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to remove.</param>
|
||||
public void Remove(T obj)
|
||||
{
|
||||
base.Remove(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a query that can be used to query the objects in this relation.
|
||||
/// </summary>
|
||||
public AVQuery<T> Query
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.GetQuery<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Role on the LeanCloud server. AVRoles represent groupings
|
||||
/// of <see cref="AVUser"/>s for the purposes of granting permissions (e.g.
|
||||
/// specifying a <see cref="AVACL"/> for a <see cref="AVObject"/>. Roles
|
||||
/// are specified by their sets of child users and child roles, all of which are granted
|
||||
/// any permissions that the parent role has.
|
||||
///
|
||||
/// Roles must have a name (that cannot be changed after creation of the role),
|
||||
/// and must specify an ACL.
|
||||
/// </summary>
|
||||
[AVClassName("_Role")]
|
||||
public class AVRole : AVObject
|
||||
{
|
||||
private static readonly Regex namePattern = new Regex("^[0-9a-zA-Z_\\- ]+$");
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new AVRole. You must assign a name and ACL to the role.
|
||||
/// </summary>
|
||||
public AVRole() : base() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new AVRole with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the role to create.</param>
|
||||
/// <param name="acl">The ACL for this role. Roles must have an ACL.</param>
|
||||
public AVRole(string name, AVACL acl)
|
||||
: this()
|
||||
{
|
||||
Name = name;
|
||||
ACL = acl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the role.
|
||||
/// </summary>
|
||||
[AVFieldName("name")]
|
||||
public string Name
|
||||
{
|
||||
get { return GetProperty<string>("Name"); }
|
||||
set { SetProperty(value, "Name"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="AVRelation{AVUser}"/> for the <see cref="AVUser"/>s that are
|
||||
/// direct children of this role. These users are granted any privileges that
|
||||
/// this role has been granted (e.g. read or write access through ACLs). You can
|
||||
/// add or remove child users from the role through this relation.
|
||||
/// </summary>
|
||||
[AVFieldName("users")]
|
||||
public AVRelation<AVUser> Users
|
||||
{
|
||||
get { return GetRelationProperty<AVUser>("Users"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="AVRelation{AVRole}"/> for the <see cref="AVRole"/>s that are
|
||||
/// direct children of this role. These roles' users are granted any privileges that
|
||||
/// this role has been granted (e.g. read or write access through ACLs). You can
|
||||
/// add or remove child roles from the role through this relation.
|
||||
/// </summary>
|
||||
[AVFieldName("roles")]
|
||||
public AVRelation<AVRole> Roles
|
||||
{
|
||||
get { return GetRelationProperty<AVRole>("Roles"); }
|
||||
}
|
||||
|
||||
internal override void OnSettingValue(ref string key, ref object value)
|
||||
{
|
||||
base.OnSettingValue(ref key, ref value);
|
||||
if (key == "name")
|
||||
{
|
||||
if (ObjectId != null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A role's name can only be set before it has been saved.");
|
||||
}
|
||||
if (!(value is string))
|
||||
{
|
||||
throw new ArgumentException("A role's name must be a string.", "value");
|
||||
}
|
||||
if (!namePattern.IsMatch((string)value))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"A role's name can only contain alphanumeric characters, _, -, and spaces.",
|
||||
"value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="AVQuery{AVRole}"/> over the Role collection.
|
||||
/// </summary>
|
||||
public static AVQuery<AVRole> Query
|
||||
{
|
||||
get
|
||||
{
|
||||
return new AVQuery<AVRole>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue