507 lines
18 KiB
C#
507 lines
18 KiB
C#
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();
|
||
}
|
||
}
|
||
}
|