chore: 拷贝 pcl 工程;重构 http 请求逻辑

oneRain 2019-07-30 16:47:10 +08:00
parent 8a7994a33d
commit 19c1975880
113 changed files with 19228 additions and 74 deletions

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit" version="3.12.0" targetFramework="net47" />
</packages>

View File

@ -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;
}
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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");
}
}
}
}

View File

@ -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);
}
}

View File

@ -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; }
}
}

View File

@ -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"]);
});
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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));
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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.");
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
});
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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>();
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}

View File

@ -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; }
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}

View File

@ -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;
// });
//}
}
}

View File

@ -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;
}
}

View File

@ -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:");
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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();
});
}
}
}

View File

@ -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;
});
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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
{
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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&lt;string, object&gt; or an IList&lt;object&gt; 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&lt;string, object&gt;, IList&lt;object&gt;, 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&lt;string, object&gt;, IList&lt;object&gt;, 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);
}
}
}

View File

@ -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++);
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; } }
}
}

View File

@ -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>),
};
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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();
}
}
}

View File

@ -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&lt;IDictionary&lt;string, object&gt;&gt;("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;
}
}
}

View File

@ -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&lt;T&gt;, IDictionary&lt;string, T&gt; 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) }
};
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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
}
}

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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&lt;AVObject&gt; 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();
}
}
}

View File

@ -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. &lt;, &gt;, &lt;=, &gt;= !=, 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 !=, &lt; becomes &gt;=, 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.");
}
}
}

View File

@ -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&lt;T&gt; 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>();
}
}
}
}

View File

@ -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