Merge pull request #2 from onerain88/init
init: 合并工程
commit
609b88d05b
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{EA1C601E-D853-41F7-B9EB-276CBF7D1FA5}</ProjectGuid>
|
||||
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>LiveQuery.PCL</RootNamespace>
|
||||
<AssemblyName>LiveQuery.PCL</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile>Profile111</TargetFrameworkProfile>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="..\Source\AVLiveQuery.cs">
|
||||
<Link>Source\AVLiveQuery.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\AVLiveQueryEventArgs.cs">
|
||||
<Link>Source\AVLiveQueryEventArgs.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\AVLiveQueryExtensions.cs">
|
||||
<Link>Source\AVLiveQueryExtensions.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RTM\RTM.PCL\RTM.PCL.csproj">
|
||||
<Project>{92B2B40E-A3CD-4672-AC84-2E894E1A6CE5}</Project>
|
||||
<Name>RTM.PCL</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Storage\Storage.PCL\Storage.PCL.csproj">
|
||||
<Project>{659D19F0-9A40-42C0-886C-555E64F16848}</Project>
|
||||
<Name>Storage.PCL</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
|
||||
[assembly: AssemblyTitle("LiveQuery.PCL")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("${AuthorCopyright}")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{F907012C-74DF-4575-AFE6-E8DAACC26D24}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>LiveQuery.Test</RootNamespace>
|
||||
<AssemblyName>LiveQuery.Test</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="nunit.framework">
|
||||
<HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Test.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,10 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
namespace LiveQuery.Test {
|
||||
[TestFixture()]
|
||||
public class Test {
|
||||
[Test()]
|
||||
public void TestCase() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NUnit" version="2.6.4" targetFramework="net47" />
|
||||
</packages>
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{3251B4D8-D11A-4D90-8626-27FEE266B066}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>LiveQuery.Unity</RootNamespace>
|
||||
<AssemblyName>LiveQuery.Unity</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="..\Source\AVLiveQuery.cs">
|
||||
<Link>Source\AVLiveQuery.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\AVLiveQueryEventArgs.cs">
|
||||
<Link>Source\AVLiveQueryEventArgs.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\AVLiveQueryExtensions.cs">
|
||||
<Link>Source\AVLiveQueryExtensions.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\RTM\RTM.Unity\RTM.Unity.csproj">
|
||||
<Project>{1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C}</Project>
|
||||
<Name>RTM.Unity</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Storage\Storage.Unity\Storage.Unity.csproj">
|
||||
<Project>{A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC}</Project>
|
||||
<Name>Storage.Unity</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
|
||||
[assembly: AssemblyTitle("LiveQuery.Unity")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("${AuthorCopyright}")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
|
@ -0,0 +1,225 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using LeanCloud.Realtime;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace LeanCloud.LiveQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// AVLiveQuery 类
|
||||
/// </summary>
|
||||
public static class AVLiveQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// LiveQuery 传输数据的 AVRealtime 实例
|
||||
/// </summary>
|
||||
public static AVRealtime Channel {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal static long ClientTs {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal static bool Inited {
|
||||
get; set;
|
||||
}
|
||||
|
||||
internal static string InstallationId {
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AVLiveQuery 对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class AVLiveQuery<T> where T : AVObject
|
||||
{
|
||||
internal static Dictionary<string, WeakReference<AVLiveQuery<T>>> liveQueryDict = new Dictionary<string, WeakReference<AVLiveQuery<T>>>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 当前 AVLiveQuery 对象的 Id
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 根据 AVQuery 创建 AVLiveQuery 对象
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
public AVLiveQuery(AVQuery<T> query) {
|
||||
this.Query = query;
|
||||
}
|
||||
/// <summary>
|
||||
/// AVLiveQuery 对应的 AVQuery 对象
|
||||
/// </summary>
|
||||
public AVQuery<T> Query { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据推送的触发的事件通知
|
||||
/// </summary>
|
||||
public event EventHandler<AVLiveQueryEventArgs<T>> OnLiveQueryReceived;
|
||||
|
||||
/// <summary>
|
||||
/// 推送抵达时触发事件通知
|
||||
/// </summary>
|
||||
/// <param name="scope">产生这条推送的原因。
|
||||
/// <remarks>
|
||||
/// create:符合查询条件的对象创建;
|
||||
/// update:符合查询条件的对象属性修改。
|
||||
/// enter:对象修改事件,从不符合查询条件变成符合。
|
||||
/// leave:对象修改时间,从符合查询条件变成不符合。
|
||||
/// delete:对象删除
|
||||
/// login:只对 _User 对象有效,表示用户登录。
|
||||
/// </remarks>
|
||||
/// </param>
|
||||
/// <param name="onRecevived"></param>
|
||||
public void On(string scope, Action<T> onRecevived)
|
||||
{
|
||||
this.OnLiveQueryReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Scope == scope)
|
||||
{
|
||||
onRecevived.Invoke(e.Payload);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订阅操作
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<AVLiveQuery<T>> SubscribeAsync(CancellationToken cancellationToken = default(CancellationToken)) {
|
||||
if (Query == null) {
|
||||
throw new Exception("Query can not be null when subcribe.");
|
||||
}
|
||||
if (!AVLiveQuery.Inited) {
|
||||
await Login();
|
||||
AVLiveQuery.Channel.OnReconnected += OnChannelReconnected;
|
||||
AVLiveQuery.Channel.NoticeReceived += OnChannelNoticeReceived;
|
||||
AVLiveQuery.Inited = true;
|
||||
}
|
||||
await InternalSubscribe();
|
||||
var liveQueryRef = new WeakReference<AVLiveQuery<T>>(this);
|
||||
liveQueryDict.Add(Id, liveQueryRef);
|
||||
return this;
|
||||
}
|
||||
|
||||
static async void OnChannelReconnected(object sender, AVIMReconnectedEventArgs e) {
|
||||
await Login();
|
||||
lock (liveQueryDict) {
|
||||
foreach (var kv in liveQueryDict) {
|
||||
if (kv.Value.TryGetTarget(out var liveQuery)) {
|
||||
liveQuery.InternalSubscribe().ContinueWith(_ => { });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async Task Login() {
|
||||
var installation = await AVPlugins.Instance.InstallationIdController.GetAsync();
|
||||
AVLiveQuery.InstallationId = installation.ToString();
|
||||
AVLiveQuery.Channel.ToggleNotification(true);
|
||||
await AVLiveQuery.Channel.OpenAsync();
|
||||
AVLiveQuery.ClientTs = (long) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
var liveQueryLogInCmd = new AVIMCommand().Command("login")
|
||||
.Argument("installationId", AVLiveQuery.InstallationId)
|
||||
.Argument("clientTs", AVLiveQuery.ClientTs)
|
||||
.Argument("service", 1).AppId(AVClient.CurrentConfiguration.ApplicationId);
|
||||
// open the session for LiveQuery.
|
||||
try {
|
||||
await AVLiveQuery.Channel.AVIMCommandRunner.RunCommandAsync(liveQueryLogInCmd);
|
||||
} catch (Exception e) {
|
||||
AVRealtime.PrintLog(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnChannelNoticeReceived(object sender, AVIMNotice e) {
|
||||
if (e.CommandName == "data") {
|
||||
var ids = AVDecoder.Instance.DecodeList<string>(e.RawData["ids"]);
|
||||
if (e.RawData["msg"] is IEnumerable<object> msg) {
|
||||
var receivedPayloads = from item in msg
|
||||
select item as Dictionary<string, object>;
|
||||
if (receivedPayloads != null) {
|
||||
foreach (var payload in receivedPayloads) {
|
||||
var liveQueryId = payload["query_id"] as string;
|
||||
if (liveQueryDict.TryGetValue(liveQueryId, out var liveQueryRef) &&
|
||||
liveQueryRef.TryGetTarget(out var liveQuery)) {
|
||||
var scope = payload["op"] as string;
|
||||
var objectPayload = payload["object"] as Dictionary<string, object>;
|
||||
string[] keys = null;
|
||||
if (payload.TryGetValue("updatedKeys", out object updatedKeys)) {
|
||||
// enter, leave, update
|
||||
keys = (updatedKeys as List<object>).Select(x => x.ToString()).ToArray();
|
||||
}
|
||||
liveQuery.Emit(scope, objectPayload, keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task InternalSubscribe() {
|
||||
var queryMap = new Dictionary<string, object> {
|
||||
{ "where", Query.Condition},
|
||||
{ "className", Query.GetClassName()}
|
||||
};
|
||||
|
||||
Dictionary<string, object> data = new Dictionary<string, object> {
|
||||
{ "query", queryMap },
|
||||
{ "id", AVLiveQuery.InstallationId },
|
||||
{ "clientTimestamp", AVLiveQuery.ClientTs }
|
||||
};
|
||||
string sessionToken = AVUser.CurrentUser != null ? AVUser.CurrentUser.SessionToken : string.Empty;
|
||||
if (!string.IsNullOrEmpty(sessionToken)) {
|
||||
data.Add("sessionToken", sessionToken);
|
||||
}
|
||||
var command = new AVCommand("LiveQuery/subscribe",
|
||||
"POST",
|
||||
sessionToken,
|
||||
data: data);
|
||||
var res = await AVPlugins.Instance.CommandRunner.RunCommandAsync(command);
|
||||
Id = res.Item2["query_id"] as string;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消对当前 LiveQuery 对象的订阅
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task UnsubscribeAsync() {
|
||||
Dictionary<string, object> strs = new Dictionary<string, object> {
|
||||
{ "id", AVLiveQuery.InstallationId },
|
||||
{ "query_id", Id },
|
||||
};
|
||||
string sessionToken = AVUser.CurrentUser != null ? AVUser.CurrentUser.SessionToken : string.Empty;
|
||||
var command = new AVCommand("LiveQuery/unsubscribe",
|
||||
"POST",
|
||||
sessionToken,
|
||||
data: strs);
|
||||
await AVPlugins.Instance.CommandRunner.RunCommandAsync(command);
|
||||
lock (liveQueryDict) {
|
||||
liveQueryDict.Remove(Id);
|
||||
}
|
||||
}
|
||||
|
||||
void Emit(string scope, IDictionary<string, object> payloadMap, string[] keys) {
|
||||
var objectState = AVObjectCoder.Instance.Decode(payloadMap, AVDecoder.Instance);
|
||||
var payloadObject = AVObject.FromState<T>(objectState, Query.GetClassName<T>());
|
||||
var args = new AVLiveQueryEventArgs<T> {
|
||||
Scope = scope,
|
||||
Keys = keys,
|
||||
Payload = payloadObject
|
||||
};
|
||||
OnLiveQueryReceived?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud;
|
||||
using System.Collections;
|
||||
|
||||
namespace LeanCloud.LiveQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// AVLiveQuery 回调参数
|
||||
/// </summary>
|
||||
public class AVLiveQueryEventArgs<T> : EventArgs
|
||||
where T : AVObject
|
||||
{
|
||||
internal AVLiveQueryEventArgs()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新事件
|
||||
/// </summary>
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新字段
|
||||
/// </summary>
|
||||
public IEnumerable<string> Keys { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新数据
|
||||
/// </summary>
|
||||
public T Payload { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.LiveQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// AVLiveQuery 扩展类
|
||||
/// </summary>
|
||||
public static class AVLiveQueryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// AVQuery 订阅 AVLiveQuery 的扩展方法
|
||||
/// </summary>
|
||||
/// <returns>AVLiveQuery 对象</returns>
|
||||
/// <param name="query">Query.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <typeparam name="T">The 1st type parameter.</typeparam>
|
||||
public static async Task<AVLiveQuery<T>> SubscribeAsync<T>(this AVQuery<T> query, CancellationToken cancellationToken = default(CancellationToken)) where T : AVObject {
|
||||
var liveQuery = new AVLiveQuery<T>(query);
|
||||
liveQuery = await liveQuery.SubscribeAsync();
|
||||
return liveQuery;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
|
||||
[assembly: AssemblyTitle("RTM.PCL")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("${AuthorCopyright}")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
|
@ -0,0 +1,216 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{92B2B40E-A3CD-4672-AC84-2E894E1A6CE5}</ProjectGuid>
|
||||
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>RTM.PCL</RootNamespace>
|
||||
<AssemblyName>RTM.PCL</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile>Profile111</TargetFrameworkProfile>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="..\Source\Internal\AVIMCorePlugins.cs">
|
||||
<Link>Internal\AVIMCorePlugins.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\IAVIMPlatformHooks.cs">
|
||||
<Link>Internal\IAVIMPlatformHooks.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\AVIMCommand.cs">
|
||||
<Link>Internal\Command\AVIMCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\AVIMCommandRunner.cs">
|
||||
<Link>Internal\Command\AVIMCommandRunner.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\AckCommand.cs">
|
||||
<Link>Internal\Command\AckCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\ConversationCommand.cs">
|
||||
<Link>Internal\Command\ConversationCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\IAVIMCommandRunner.cs">
|
||||
<Link>Internal\Command\IAVIMCommandRunner.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\MessageCommand.cs">
|
||||
<Link>Internal\Command\MessageCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\PatchCommand.cs">
|
||||
<Link>Internal\Command\PatchCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\ReadCommand.cs">
|
||||
<Link>Internal\Command\ReadCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\SessionCommand.cs">
|
||||
<Link>Internal\Command\SessionCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\DataEngine\Controller\DateTimeEngine.cs">
|
||||
<Link>Internal\DataEngine\Controller\DateTimeEngine.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\DataEngine\Controller\DictionaryEngine.cs">
|
||||
<Link>Internal\DataEngine\Controller\DictionaryEngine.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\DataEngine\Controller\StringEngine.cs">
|
||||
<Link>Internal\DataEngine\Controller\StringEngine.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Message\Subclassing\FreeStyleMessageClassInfo.cs">
|
||||
<Link>Internal\Message\Subclassing\FreeStyleMessageClassInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Message\Subclassing\FreeStyleMessageClassingController.cs">
|
||||
<Link>Internal\Message\Subclassing\FreeStyleMessageClassingController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Message\Subclassing\IFreeStyleMessageClassingController.cs">
|
||||
<Link>Internal\Message\Subclassing\IFreeStyleMessageClassingController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Protocol\AVIMProtocol.cs">
|
||||
<Link>Internal\Protocol\AVIMProtocol.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Router\AVRouterController.cs">
|
||||
<Link>Internal\Router\AVRouterController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Router\IAVRouterController.cs">
|
||||
<Link>Internal\Router\IAVRouterController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Router\State\RouterState.cs">
|
||||
<Link>Internal\Router\State\RouterState.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Timer\IAVTimer.cs">
|
||||
<Link>Internal\Timer\IAVTimer.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Timer\Portable\AVTimer.Portable.cs">
|
||||
<Link>Internal\Timer\Portable\AVTimer.Portable.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\WebSocket\IWebSocketClient.cs">
|
||||
<Link>Internal\WebSocket\IWebSocketClient.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\WebSocket\Portable\DefaultWebSocketClient.Portable.cs">
|
||||
<Link>Internal\WebSocket\Portable\DefaultWebSocketClient.Portable.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMAudioMessage.cs">
|
||||
<Link>Public\AVIMAudioMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMBinaryMessage.cs">
|
||||
<Link>Public\AVIMBinaryMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMClient.cs">
|
||||
<Link>Public\AVIMClient.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMConversation.cs">
|
||||
<Link>Public\AVIMConversation.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMConversationQuery.cs">
|
||||
<Link>Public\AVIMConversationQuery.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMEnumerator.cs">
|
||||
<Link>Public\AVIMEnumerator.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMEventArgs.cs">
|
||||
<Link>Public\AVIMEventArgs.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMException.cs">
|
||||
<Link>Public\AVIMException.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMImageMessage.cs">
|
||||
<Link>Public\AVIMImageMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMMessage.cs">
|
||||
<Link>Public\AVIMMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMMessageClassNameAttribute.cs">
|
||||
<Link>Public\AVIMMessageClassNameAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMMessageFieldNameAttribute.cs">
|
||||
<Link>Public\AVIMMessageFieldNameAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMMessageListener.cs">
|
||||
<Link>Public\AVIMMessageListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMNotice.cs">
|
||||
<Link>Public\AVIMNotice.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMRecalledMessage.cs">
|
||||
<Link>Public\AVIMRecalledMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMSignature.cs">
|
||||
<Link>Public\AVIMSignature.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMTemporaryConversation.cs">
|
||||
<Link>Public\AVIMTemporaryConversation.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMTextMessage.cs">
|
||||
<Link>Public\AVIMTextMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMTypedMessage.cs">
|
||||
<Link>Public\AVIMTypedMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMTypedMessageTypeIntAttribute.cs">
|
||||
<Link>Public\AVIMTypedMessageTypeIntAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVRealtime.cs">
|
||||
<Link>Public\AVRealtime.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\IAVIMListener.cs">
|
||||
<Link>Public\IAVIMListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\IAVIMMessage.cs">
|
||||
<Link>Public\IAVIMMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\ICacheEngine.cs">
|
||||
<Link>Public\ICacheEngine.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\ISignatureFactory.cs">
|
||||
<Link>Public\ISignatureFactory.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\AVIMConversationListener.cs">
|
||||
<Link>Public\Listener\AVIMConversationListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\ConversationUnreadListener.cs">
|
||||
<Link>Public\Listener\ConversationUnreadListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\GoAwayListener.cs">
|
||||
<Link>Public\Listener\GoAwayListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\MessagePatchListener.cs">
|
||||
<Link>Public\Listener\MessagePatchListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\OfflineMessageListener.cs">
|
||||
<Link>Public\Listener\OfflineMessageListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\SessionListener.cs">
|
||||
<Link>Public\Listener\SessionListener.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="WebSockets.PCL">
|
||||
<HintPath>..\..\packages\Websockets.Pcl.1.1.9\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10\WebSockets.PCL.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Storage\Storage.PCL\Storage.PCL.csproj">
|
||||
<Project>{659D19F0-9A40-42C0-886C-555E64F16848}</Project>
|
||||
<Name>Storage.PCL</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Websockets.Pcl" version="1.1.9" targetFramework="portable45-net45+win8+wpa81" developmentDependency="true" />
|
||||
</packages>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{A1BBD0B5-41C6-4579-B9A3-5EF778BE7F95}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>RTM.Test</RootNamespace>
|
||||
<AssemblyName>RTM.Test</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="nunit.framework">
|
||||
<HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Test.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,10 @@
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
namespace RTM.Test {
|
||||
[TestFixture()]
|
||||
public class Test {
|
||||
[Test()]
|
||||
public void TestCase() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NUnit" version="2.6.4" targetFramework="net47" />
|
||||
</packages>
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
// Information about this assembly is defined by the following attributes.
|
||||
// Change them to the values specific to your project.
|
||||
|
||||
[assembly: AssemblyTitle("RTM.Unity")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("${AuthorCopyright}")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
|
||||
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
|
||||
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
// The following attributes are used to specify the signing key for the assembly,
|
||||
// if desired. See the Mono documentation for more information about signing.
|
||||
|
||||
//[assembly: AssemblyDelaySign(false)]
|
||||
//[assembly: AssemblyKeyFile("")]
|
|
@ -0,0 +1,225 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{1E608FCD-9039-4FF7-8EE7-BA8B00E15D1C}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<RootNamespace>RTM.Unity</RootNamespace>
|
||||
<AssemblyName>RTM.Unity</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
|
||||
<ReleaseVersion>0.1.0</ReleaseVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug</OutputPath>
|
||||
<DefineConstants>DEBUG;</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<ConsolePause>false</ConsolePause>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="UnityEngine">
|
||||
<HintPath>..\..\Libs\UnityEngine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="websocket-sharp">
|
||||
<HintPath>..\..\Libs\websocket-sharp.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="..\Source\Internal\AVIMCorePlugins.cs">
|
||||
<Link>Internal\AVIMCorePlugins.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\IAVIMPlatformHooks.cs">
|
||||
<Link>Internal\IAVIMPlatformHooks.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\AVIMCommand.cs">
|
||||
<Link>Internal\Command\AVIMCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\AVIMCommandRunner.cs">
|
||||
<Link>Internal\Command\AVIMCommandRunner.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\AckCommand.cs">
|
||||
<Link>Internal\Command\AckCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\ConversationCommand.cs">
|
||||
<Link>Internal\Command\ConversationCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\IAVIMCommandRunner.cs">
|
||||
<Link>Internal\Command\IAVIMCommandRunner.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\MessageCommand.cs">
|
||||
<Link>Internal\Command\MessageCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\PatchCommand.cs">
|
||||
<Link>Internal\Command\PatchCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\ReadCommand.cs">
|
||||
<Link>Internal\Command\ReadCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Command\SessionCommand.cs">
|
||||
<Link>Internal\Command\SessionCommand.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\DataEngine\Controller\DateTimeEngine.cs">
|
||||
<Link>Internal\DataEngine\Controller\DateTimeEngine.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\DataEngine\Controller\DictionaryEngine.cs">
|
||||
<Link>Internal\DataEngine\Controller\DictionaryEngine.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\DataEngine\Controller\StringEngine.cs">
|
||||
<Link>Internal\DataEngine\Controller\StringEngine.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Message\Subclassing\FreeStyleMessageClassInfo.cs">
|
||||
<Link>Internal\Message\Subclassing\FreeStyleMessageClassInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Message\Subclassing\FreeStyleMessageClassingController.cs">
|
||||
<Link>Internal\Message\Subclassing\FreeStyleMessageClassingController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Message\Subclassing\IFreeStyleMessageClassingController.cs">
|
||||
<Link>Internal\Message\Subclassing\IFreeStyleMessageClassingController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Protocol\AVIMProtocol.cs">
|
||||
<Link>Internal\Protocol\AVIMProtocol.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Router\AVRouterController.cs">
|
||||
<Link>Internal\Router\AVRouterController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Router\IAVRouterController.cs">
|
||||
<Link>Internal\Router\IAVRouterController.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Router\State\RouterState.cs">
|
||||
<Link>Internal\Router\State\RouterState.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Timer\IAVTimer.cs">
|
||||
<Link>Internal\Timer\IAVTimer.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\Timer\Unity\AVTimer.Unity.cs">
|
||||
<Link>Internal\Timer\Unity\AVTimer.Unity.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\WebSocket\IWebSocketClient.cs">
|
||||
<Link>Internal\WebSocket\IWebSocketClient.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Internal\WebSocket\Unity\DefaultWebSocketClient.Unity.cs">
|
||||
<Link>Internal\WebSocket\Unity\DefaultWebSocketClient.Unity.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMAudioMessage.cs">
|
||||
<Link>Public\AVIMAudioMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMBinaryMessage.cs">
|
||||
<Link>Public\AVIMBinaryMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMClient.cs">
|
||||
<Link>Public\AVIMClient.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMConversation.cs">
|
||||
<Link>Public\AVIMConversation.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMConversationQuery.cs">
|
||||
<Link>Public\AVIMConversationQuery.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMEnumerator.cs">
|
||||
<Link>Public\AVIMEnumerator.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMEventArgs.cs">
|
||||
<Link>Public\AVIMEventArgs.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMException.cs">
|
||||
<Link>Public\AVIMException.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMImageMessage.cs">
|
||||
<Link>Public\AVIMImageMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMMessage.cs">
|
||||
<Link>Public\AVIMMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMMessageClassNameAttribute.cs">
|
||||
<Link>Public\AVIMMessageClassNameAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMMessageFieldNameAttribute.cs">
|
||||
<Link>Public\AVIMMessageFieldNameAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMMessageListener.cs">
|
||||
<Link>Public\AVIMMessageListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMNotice.cs">
|
||||
<Link>Public\AVIMNotice.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMRecalledMessage.cs">
|
||||
<Link>Public\AVIMRecalledMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMSignature.cs">
|
||||
<Link>Public\AVIMSignature.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMTemporaryConversation.cs">
|
||||
<Link>Public\AVIMTemporaryConversation.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMTextMessage.cs">
|
||||
<Link>Public\AVIMTextMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMTypedMessage.cs">
|
||||
<Link>Public\AVIMTypedMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVIMTypedMessageTypeIntAttribute.cs">
|
||||
<Link>Public\AVIMTypedMessageTypeIntAttribute.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\AVRealtime.cs">
|
||||
<Link>Public\AVRealtime.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\IAVIMListener.cs">
|
||||
<Link>Public\IAVIMListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\IAVIMMessage.cs">
|
||||
<Link>Public\IAVIMMessage.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\ICacheEngine.cs">
|
||||
<Link>Public\ICacheEngine.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\ISignatureFactory.cs">
|
||||
<Link>Public\ISignatureFactory.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\AVIMConversationListener.cs">
|
||||
<Link>Public\Listener\AVIMConversationListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\ConversationUnreadListener.cs">
|
||||
<Link>Public\Listener\ConversationUnreadListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\GoAwayListener.cs">
|
||||
<Link>Public\Listener\GoAwayListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\MessagePatchListener.cs">
|
||||
<Link>Public\Listener\MessagePatchListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\OfflineMessageListener.cs">
|
||||
<Link>Public\Listener\OfflineMessageListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Listener\SessionListener.cs">
|
||||
<Link>Public\Listener\SessionListener.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\Source\Public\Unity\AVRealtimeBehavior.cs">
|
||||
<Link>Public\Unity\AVRealtimeBehavior.cs</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\Source\Internal\WebSocket\Unity\websocket-sharp.dll">
|
||||
<Link>Internal\WebSocket\Unity\websocket-sharp.dll</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Storage\Storage.Unity\Storage.Unity.csproj">
|
||||
<Project>{A0D50BCB-E50E-4AAE-8E7D-24BF5AE33DAC}</Project>
|
||||
<Name>Storage.Unity</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class AVIMCorePlugins
|
||||
{
|
||||
private static readonly AVIMCorePlugins instance = new AVIMCorePlugins();
|
||||
public static AVIMCorePlugins Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object mutex = new object();
|
||||
|
||||
private IAVRouterController routerController;
|
||||
public IAVRouterController RouterController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
routerController = routerController ?? new AVRouterController();
|
||||
return routerController;
|
||||
}
|
||||
}
|
||||
internal set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
routerController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private IFreeStyleMessageClassingController freeStyleClassingController;
|
||||
public IFreeStyleMessageClassingController FreeStyleClassingController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
freeStyleClassingController = freeStyleClassingController ?? new FreeStyleMessageClassingController();
|
||||
return freeStyleClassingController;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
using LeanCloud;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Command.
|
||||
/// </summary>
|
||||
public class AVIMCommand
|
||||
{
|
||||
protected readonly string cmd;
|
||||
protected readonly string op;
|
||||
protected string appId;
|
||||
protected string peerId;
|
||||
protected AVIMSignature signature;
|
||||
protected readonly IDictionary<string, object> arguments;
|
||||
|
||||
public int TimeoutInSeconds { get; set; }
|
||||
|
||||
protected readonly IDictionary<string, object> estimatedData = new Dictionary<string, object>();
|
||||
internal readonly object mutex = new object();
|
||||
internal static readonly object Mutex = new object();
|
||||
|
||||
public AVIMCommand() :
|
||||
this(arguments: new Dictionary<string, object>())
|
||||
{
|
||||
|
||||
}
|
||||
protected AVIMCommand(string cmd = null,
|
||||
string op = null,
|
||||
string appId = null,
|
||||
string peerId = null,
|
||||
AVIMSignature signature = null,
|
||||
IDictionary<string, object> arguments = null)
|
||||
{
|
||||
this.cmd = cmd;
|
||||
this.op = op;
|
||||
this.arguments = arguments == null ? new Dictionary<string, object>() : arguments;
|
||||
this.peerId = peerId;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
protected AVIMCommand(AVIMCommand source,
|
||||
string cmd = null,
|
||||
string op = null,
|
||||
string appId = null,
|
||||
string peerId = null,
|
||||
IDictionary<string, object> arguments = null,
|
||||
AVIMSignature signature = null)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException("source", "Source can not be null");
|
||||
}
|
||||
this.cmd = source.cmd;
|
||||
this.op = source.op;
|
||||
this.arguments = source.arguments;
|
||||
this.peerId = source.peerId;
|
||||
this.appId = source.appId;
|
||||
this.signature = source.signature;
|
||||
|
||||
if (cmd != null)
|
||||
{
|
||||
this.cmd = cmd;
|
||||
}
|
||||
if (op != null)
|
||||
{
|
||||
this.op = op;
|
||||
}
|
||||
if (arguments != null)
|
||||
{
|
||||
this.arguments = arguments;
|
||||
}
|
||||
if (peerId != null)
|
||||
{
|
||||
this.peerId = peerId;
|
||||
}
|
||||
if (appId != null)
|
||||
{
|
||||
this.appId = appId;
|
||||
}
|
||||
if (signature != null)
|
||||
{
|
||||
this.signature = signature;
|
||||
}
|
||||
}
|
||||
|
||||
public AVIMCommand Command(string cmd)
|
||||
{
|
||||
return new AVIMCommand(this, cmd: cmd);
|
||||
}
|
||||
public AVIMCommand Option(string op)
|
||||
{
|
||||
return new AVIMCommand(this, op: op);
|
||||
}
|
||||
public AVIMCommand Argument(string key, object value)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
this.arguments[key] = value;
|
||||
return new AVIMCommand(this);
|
||||
}
|
||||
}
|
||||
public AVIMCommand AppId(string appId)
|
||||
{
|
||||
this.appId = appId;
|
||||
return new AVIMCommand(this, appId: appId);
|
||||
}
|
||||
|
||||
public AVIMCommand PeerId(string peerId)
|
||||
{
|
||||
this.peerId = peerId;
|
||||
return new AVIMCommand(this, peerId: peerId);
|
||||
}
|
||||
|
||||
public AVIMCommand IDlize()
|
||||
{
|
||||
this.Argument("i", AVIMCommand.NextCmdId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IDictionary<string, object> Encode()
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
estimatedData.Clear();
|
||||
estimatedData.Merge(arguments);
|
||||
estimatedData.Add("cmd", cmd);
|
||||
estimatedData.Add("appId", this.appId);
|
||||
if (!string.IsNullOrEmpty(op))
|
||||
estimatedData.Add("op", op);
|
||||
if (!string.IsNullOrEmpty(peerId))
|
||||
estimatedData.Add("peerId", peerId);
|
||||
|
||||
return estimatedData;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string EncodeJsonString()
|
||||
{
|
||||
var json = this.Encode();
|
||||
return Json.Encode(json);
|
||||
}
|
||||
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
return !string.IsNullOrEmpty(this.cmd);
|
||||
}
|
||||
}
|
||||
|
||||
private static Int32 lastCmdId = -65536;
|
||||
internal static Int32 NextCmdId
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (Mutex)
|
||||
{
|
||||
lastCmdId++;
|
||||
|
||||
if (lastCmdId > ushort.MaxValue)
|
||||
{
|
||||
lastCmdId = -65536;
|
||||
}
|
||||
return lastCmdId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
using LeanCloud;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
public class AVIMCommandRunner : IAVIMCommandRunner
|
||||
{
|
||||
private readonly IWebSocketClient webSocketClient;
|
||||
public AVIMCommandRunner(IWebSocketClient webSocketClient)
|
||||
{
|
||||
this.webSocketClient = webSocketClient;
|
||||
}
|
||||
|
||||
public void RunCommand(AVIMCommand command)
|
||||
{
|
||||
command.IDlize();
|
||||
var requestString = command.EncodeJsonString();
|
||||
AVRealtime.PrintLog("websocket=>" + requestString);
|
||||
webSocketClient.Send(requestString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public Task<Tuple<int, IDictionary<string, object>>> RunCommandAsync(AVIMCommand command, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var tcs = new TaskCompletionSource<Tuple<int, IDictionary<string, object>>>();
|
||||
|
||||
command.IDlize();
|
||||
|
||||
var requestString = command.EncodeJsonString();
|
||||
if (!command.IsValid)
|
||||
{
|
||||
requestString = "{}";
|
||||
}
|
||||
AVRealtime.PrintLog("websocket=>" + requestString);
|
||||
webSocketClient.Send(requestString);
|
||||
var requestJson = command.Encode();
|
||||
|
||||
|
||||
Action<string> onMessage = null;
|
||||
onMessage = (response) =>
|
||||
{
|
||||
//AVRealtime.PrintLog("response<=" + response);
|
||||
var responseJson = Json.Parse(response) as IDictionary<string, object>;
|
||||
if (responseJson.Keys.Contains("i"))
|
||||
{
|
||||
if (requestJson["i"].ToString() == responseJson["i"].ToString())
|
||||
{
|
||||
var result = new Tuple<int, IDictionary<string, object>>(-1, responseJson);
|
||||
if (responseJson.Keys.Contains("code"))
|
||||
{
|
||||
var errorCode = int.Parse(responseJson["code"].ToString());
|
||||
var reason = string.Empty;
|
||||
int appCode = 0;
|
||||
|
||||
if (responseJson.Keys.Contains("reason"))
|
||||
{
|
||||
reason = responseJson["reason"].ToString();
|
||||
}
|
||||
if (responseJson.Keys.Contains("appCode"))
|
||||
{
|
||||
appCode = int.Parse(responseJson["appCode"].ToString());
|
||||
}
|
||||
tcs.SetException(new AVIMException(errorCode, appCode, reason, null));
|
||||
}
|
||||
if (tcs.Task.Exception == null)
|
||||
{
|
||||
tcs.SetResult(result);
|
||||
}
|
||||
webSocketClient.OnMessage -= onMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
webSocketClient.OnMessage += onMessage;
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class AckCommand : AVIMCommand
|
||||
{
|
||||
public AckCommand()
|
||||
: base(cmd: "ack")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AckCommand(AVIMCommand source)
|
||||
: base(source)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AckCommand Message(IAVIMMessage message)
|
||||
{
|
||||
return new AckCommand()
|
||||
.ConversationId(message.ConversationId)
|
||||
.MessageId(message.Id);
|
||||
}
|
||||
|
||||
public AckCommand MessageId(string messageId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(messageId))
|
||||
{
|
||||
messageId = "";
|
||||
}
|
||||
return new AckCommand(this.Argument("mid", messageId));
|
||||
}
|
||||
|
||||
public AckCommand ConversationId(string conversationId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conversationId))
|
||||
{
|
||||
conversationId = "";
|
||||
}
|
||||
return new AckCommand(this.Argument("cid", conversationId));
|
||||
}
|
||||
|
||||
public AckCommand FromTimeStamp(long startTimeStamp)
|
||||
{
|
||||
return new AckCommand(this.Argument("fromts", startTimeStamp));
|
||||
}
|
||||
|
||||
public AckCommand ToTimeStamp(long endTimeStamp)
|
||||
{
|
||||
return new AckCommand(this.Argument("tots", endTimeStamp));
|
||||
}
|
||||
|
||||
public AckCommand ReadAck()
|
||||
{
|
||||
return new AckCommand(this.Argument("read", true));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class ConversationCommand : AVIMCommand
|
||||
{
|
||||
protected IList<string> members;
|
||||
public ConversationCommand()
|
||||
: base(cmd: "conv")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ConversationCommand(AVIMCommand source)
|
||||
: base(source: source)
|
||||
{
|
||||
}
|
||||
|
||||
public ConversationCommand Member(string clientId)
|
||||
{
|
||||
if (members == null)
|
||||
{
|
||||
members = new List<string>();
|
||||
}
|
||||
members.Add(clientId);
|
||||
return Members(members);
|
||||
}
|
||||
|
||||
public ConversationCommand Members(IEnumerable<string> members)
|
||||
{
|
||||
this.members = members.ToList();
|
||||
return new ConversationCommand(this.Argument("m", members));
|
||||
}
|
||||
|
||||
public ConversationCommand Transient(bool isTransient)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("transient", isTransient));
|
||||
}
|
||||
|
||||
public ConversationCommand Unique(bool isUnique)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("unique", isUnique));
|
||||
}
|
||||
|
||||
public ConversationCommand Temporary(bool isTemporary)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("tempConv", isTemporary));
|
||||
}
|
||||
|
||||
public ConversationCommand TempConvTTL(double tempConvTTL)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("tempConvTTL", tempConvTTL));
|
||||
}
|
||||
|
||||
public ConversationCommand Attr(IDictionary<string, object> attr)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("attr", attr));
|
||||
}
|
||||
|
||||
public ConversationCommand Set(string key, object value)
|
||||
{
|
||||
return new ConversationCommand(this.Argument(key, value));
|
||||
}
|
||||
|
||||
public ConversationCommand ConversationId(string conversationId)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("cid", conversationId));
|
||||
}
|
||||
|
||||
public ConversationCommand Generate(AVIMConversation conversation)
|
||||
{
|
||||
var attr = conversation.EncodeAttributes();
|
||||
var cmd = new ConversationCommand()
|
||||
.ConversationId(conversation.ConversationId)
|
||||
.Attr(attr)
|
||||
.Members(conversation.MemberIds)
|
||||
.Transient(conversation.IsTransient)
|
||||
.Temporary(conversation.IsTemporary);
|
||||
|
||||
if (conversation.IsTemporary)
|
||||
{
|
||||
var ttl = (conversation.expiredAt.Value - DateTime.Now).TotalSeconds;
|
||||
cmd = cmd.TempConvTTL(ttl);
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public ConversationCommand Where(object encodedQueryString)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("where", encodedQueryString));
|
||||
}
|
||||
|
||||
public ConversationCommand Limit(int limit)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("limit", limit));
|
||||
}
|
||||
|
||||
public ConversationCommand Skip(int skip)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("skip", skip));
|
||||
}
|
||||
|
||||
public ConversationCommand Count()
|
||||
{
|
||||
return new ConversationCommand(this.Argument("count", 1));
|
||||
}
|
||||
|
||||
public ConversationCommand Sort(string sort)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("sort", sort));
|
||||
}
|
||||
|
||||
public ConversationCommand TargetClientId(string targetClientId)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("targetClientId", targetClientId));
|
||||
}
|
||||
|
||||
public ConversationCommand QueryAllMembers(bool queryAllMembers)
|
||||
{
|
||||
return new ConversationCommand(this.Argument("queryAllMembers", queryAllMembers));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
public interface IAVIMCommandRunner
|
||||
{
|
||||
Task<Tuple<int, IDictionary<string, object>>> RunCommandAsync(AVIMCommand command,
|
||||
CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
void RunCommand(AVIMCommand command);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class MessageCommand : AVIMCommand
|
||||
{
|
||||
public MessageCommand()
|
||||
: base(cmd: "direct")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public MessageCommand(AVIMCommand source)
|
||||
: base(source: source)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public MessageCommand ConvId(string convId)
|
||||
{
|
||||
return new MessageCommand(this.Argument("cid", convId));
|
||||
}
|
||||
|
||||
public MessageCommand Receipt(bool receipt)
|
||||
{
|
||||
return new MessageCommand(this.Argument("r", receipt));
|
||||
}
|
||||
|
||||
public MessageCommand Transient(bool transient)
|
||||
{
|
||||
if (transient) return new MessageCommand(this.Argument("transient", transient));
|
||||
return new MessageCommand(this);
|
||||
}
|
||||
public MessageCommand Priority(int priority)
|
||||
{
|
||||
if (priority > 1) return new MessageCommand(this.Argument("level", priority));
|
||||
return new MessageCommand(this);
|
||||
}
|
||||
public MessageCommand Will(bool will)
|
||||
{
|
||||
if (will) return new MessageCommand(this.Argument("will", will));
|
||||
return new MessageCommand(this);
|
||||
}
|
||||
public MessageCommand Distinct(string token)
|
||||
{
|
||||
return new MessageCommand(this.Argument("dt", token));
|
||||
}
|
||||
public MessageCommand Message(string msg)
|
||||
{
|
||||
return new MessageCommand(this.Argument("msg", msg));
|
||||
}
|
||||
public MessageCommand BinaryEncode(bool binaryEncode)
|
||||
{
|
||||
return new MessageCommand(this.Argument("bin", binaryEncode));
|
||||
}
|
||||
|
||||
public MessageCommand PushData(IDictionary<string, object> pushData)
|
||||
{
|
||||
return new MessageCommand(this.Argument("pushData", Json.Encode(pushData)));
|
||||
}
|
||||
|
||||
public MessageCommand Mention(IEnumerable<string> clientIds)
|
||||
{
|
||||
var mentionedMembers = clientIds.ToList();
|
||||
return new MessageCommand(this.Argument("mentionPids", mentionedMembers));
|
||||
}
|
||||
|
||||
public MessageCommand MentionAll(bool mentionAll)
|
||||
{
|
||||
return new MessageCommand(this.Argument("mentionAll", mentionAll));
|
||||
}
|
||||
|
||||
public MessageCommand Binary(byte[] data)
|
||||
{
|
||||
return new MessageCommand(this.Argument("binaryMsg", data));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class PatchCommand : AVIMCommand
|
||||
{
|
||||
|
||||
internal struct Patch
|
||||
{
|
||||
public string MessageId { get; set; }
|
||||
public string ConvId { get; set; }
|
||||
public string From { get; set; }
|
||||
public long MetaTimestamp { get; set; }
|
||||
public long PatchTimestamp { get; set; }
|
||||
public string PatchData { get; set; }
|
||||
public bool Recall { get; set; }
|
||||
public byte[] BinaryData { get; set; }
|
||||
public bool MentionAll { get; set; }
|
||||
public IEnumerable<string> MentionIds { get; set; }
|
||||
|
||||
public IDictionary<string, object> Encode()
|
||||
{
|
||||
return new Dictionary<string, object>()
|
||||
{
|
||||
{ "cid",this.ConvId},
|
||||
{ "mid",this.MessageId},
|
||||
{ "from",this.From},
|
||||
{ "timestamp",this.MetaTimestamp},
|
||||
{ "recall",this.Recall},
|
||||
{ "data",this.PatchData},
|
||||
{ "patchTimestamp",this.PatchTimestamp},
|
||||
{ "binaryMsg",this.BinaryData},
|
||||
{ "mentionAll",this.MentionAll},
|
||||
{ "meintonPids",this.MentionIds}
|
||||
} as IDictionary<string, object>;
|
||||
}
|
||||
}
|
||||
|
||||
public PatchCommand()
|
||||
: base(cmd: "patch", op: "modify")
|
||||
{
|
||||
this.Patches = new List<Patch>();
|
||||
}
|
||||
|
||||
public PatchCommand(AVIMCommand source, ICollection<Patch> sourcePatchs)
|
||||
: base(source: source)
|
||||
{
|
||||
this.Patches = sourcePatchs;
|
||||
}
|
||||
|
||||
public ICollection<Patch> Patches { get; set; }
|
||||
|
||||
public IList<IDictionary<string, object>> EncodePatches()
|
||||
{
|
||||
return this.Patches.Select(p => p.Encode().Trim()).ToList();
|
||||
}
|
||||
|
||||
public PatchCommand Recall(IAVIMMessage message)
|
||||
{
|
||||
var patch = new Patch()
|
||||
{
|
||||
ConvId = message.ConversationId,
|
||||
From = message.FromClientId,
|
||||
MessageId = message.Id,
|
||||
MetaTimestamp = message.ServerTimestamp,
|
||||
Recall = true,
|
||||
PatchTimestamp = DateTime.Now.ToUnixTimeStamp()
|
||||
};
|
||||
|
||||
this.Patches.Add(patch);
|
||||
this.Argument("patches", this.EncodePatches());
|
||||
return new PatchCommand(this, this.Patches);
|
||||
}
|
||||
|
||||
public PatchCommand Modify(IAVIMMessage oldMessage, IAVIMMessage newMessage)
|
||||
{
|
||||
var patch = new Patch()
|
||||
{
|
||||
ConvId = oldMessage.ConversationId,
|
||||
From = oldMessage.FromClientId,
|
||||
MessageId = oldMessage.Id,
|
||||
MetaTimestamp = oldMessage.ServerTimestamp,
|
||||
Recall = false,
|
||||
PatchTimestamp = DateTime.Now.ToUnixTimeStamp(),
|
||||
PatchData = newMessage.Serialize()
|
||||
};
|
||||
|
||||
this.Patches.Add(patch);
|
||||
this.Argument("patches", this.EncodePatches());
|
||||
return new PatchCommand(this, this.Patches);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class ReadCommand : AVIMCommand
|
||||
{
|
||||
internal class ConvRead
|
||||
{
|
||||
internal string ConvId { get; set; }
|
||||
internal string MessageId { get; set; }
|
||||
internal long Timestamp { get; set; }
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
ConvRead cr = obj as ConvRead;
|
||||
return cr.ConvId == this.ConvId;
|
||||
}
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.ConvId.GetHashCode() ^ this.MessageId.GetHashCode() ^ this.Timestamp.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public ReadCommand()
|
||||
: base(cmd: "read")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ReadCommand(AVIMCommand source)
|
||||
: base(source)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ReadCommand ConvId(string convId)
|
||||
{
|
||||
return new ReadCommand(this.Argument("cid", convId));
|
||||
}
|
||||
|
||||
public ReadCommand ConvIds(IEnumerable<string> convIds)
|
||||
{
|
||||
if (convIds != null)
|
||||
{
|
||||
if (convIds.Count() > 0)
|
||||
{
|
||||
return new ReadCommand(this.Argument("cids", convIds.ToList()));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
public ReadCommand Conv(ConvRead conv)
|
||||
{
|
||||
return Convs(new ConvRead[] { conv });
|
||||
}
|
||||
|
||||
public ReadCommand Convs(IEnumerable<ConvRead> convReads)
|
||||
{
|
||||
if (convReads != null)
|
||||
{
|
||||
if (convReads.Count() > 0)
|
||||
{
|
||||
IList<IDictionary<string, object>> payload = new List<IDictionary<string, object>>();
|
||||
|
||||
foreach (var convRead in convReads)
|
||||
{
|
||||
var convDic = new Dictionary<string, object>();
|
||||
convDic.Add("cid", convRead.ConvId);
|
||||
if (!string.IsNullOrEmpty(convRead.MessageId))
|
||||
{
|
||||
convDic.Add("mid", convRead.MessageId);
|
||||
}
|
||||
if (convRead.Timestamp != 0)
|
||||
{
|
||||
convDic.Add("timestamp", convRead.Timestamp);
|
||||
}
|
||||
payload.Add(convDic);
|
||||
}
|
||||
|
||||
return new ReadCommand(this.Argument("convs", payload));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class SessionCommand : AVIMCommand
|
||||
{
|
||||
static readonly int MESSAGE_RECALL_AND_MODIFY = 0x1;
|
||||
|
||||
public SessionCommand()
|
||||
: base(cmd: "session")
|
||||
{
|
||||
arguments.Add("configBitmap", MESSAGE_RECALL_AND_MODIFY);
|
||||
}
|
||||
|
||||
public SessionCommand(AVIMCommand source)
|
||||
:base(source: source)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SessionCommand UA(string ua)
|
||||
{
|
||||
return new SessionCommand(this.Argument("ua", ua));
|
||||
}
|
||||
|
||||
public SessionCommand Tag(string tag)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tag)) return new SessionCommand(this);
|
||||
return new SessionCommand(this.Argument("tag", tag));
|
||||
}
|
||||
|
||||
public SessionCommand DeviceId(string deviceId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(deviceId)) return new SessionCommand(this);
|
||||
return new SessionCommand(this.Argument("deviceId", deviceId));
|
||||
}
|
||||
|
||||
public SessionCommand R(int r)
|
||||
{
|
||||
return new SessionCommand(this.Argument("r", r));
|
||||
}
|
||||
|
||||
public SessionCommand SessionToken(string st)
|
||||
{
|
||||
return new SessionCommand(this.Argument("st", st));
|
||||
}
|
||||
|
||||
public SessionCommand SessionPeerIds(IEnumerable<string> sessionPeerIds)
|
||||
{
|
||||
return new SessionCommand(this.Argument("sessionPeerIds", sessionPeerIds.ToList()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal enum UnixTimeStampUnit
|
||||
{
|
||||
Second = 1,
|
||||
Milisecond = 1000,
|
||||
}
|
||||
internal static class DateTimeEngine
|
||||
{
|
||||
public static long ToUnixTimeStamp(this DateTime date, UnixTimeStampUnit unit = UnixTimeStampUnit.Milisecond)
|
||||
{
|
||||
long unixTimestamp = (long)(date.ToUniversalTime().Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
|
||||
return (unixTimestamp * (int)unit);
|
||||
}
|
||||
|
||||
public static DateTime ToDateTime(this long timestamp, UnixTimeStampUnit unit = UnixTimeStampUnit.Milisecond)
|
||||
{
|
||||
var timespan = timestamp * 1000 / (int)(unit);
|
||||
DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
|
||||
dtDateTime = dtDateTime.AddMilliseconds(timespan).ToLocalTime();
|
||||
return dtDateTime;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal static class DictionaryEngine
|
||||
{
|
||||
internal static IDictionary<string, object> Merge(this IDictionary<string, object> dataLeft, IDictionary<string, object> dataRight)
|
||||
{
|
||||
if (dataRight == null)
|
||||
return dataLeft;
|
||||
foreach (var kv in dataRight)
|
||||
{
|
||||
if (dataLeft.ContainsKey(kv.Key))
|
||||
{
|
||||
dataLeft[kv.Key] = kv.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataLeft.Add(kv);
|
||||
}
|
||||
}
|
||||
return dataLeft;
|
||||
}
|
||||
|
||||
internal static object Grab(this IDictionary<string, object> data, string path)
|
||||
{
|
||||
var keys = path.Split('.').ToList<string>();
|
||||
if (keys.Count == 1) return data[keys[0]];
|
||||
|
||||
var deep = data[keys[0]] as IDictionary<string, object>;
|
||||
|
||||
keys.RemoveAt(0);
|
||||
string deepPath = string.Join(".", keys.ToArray());
|
||||
|
||||
return Grab(deep, deepPath);
|
||||
}
|
||||
|
||||
internal static IDictionary<TKey, TValue> Trim<TKey, TValue>(this IDictionary<TKey, TValue> data)
|
||||
{
|
||||
return data.Where(kvp => kvp.Value != null).ToDictionary(k => k.Key, v => v.Value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal static class StringEngine
|
||||
{
|
||||
internal static string Random(this string str, 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 string TempConvId<T>(this IEnumerable<T> objs)
|
||||
{
|
||||
var orderedBase64Strs = objs.Select(obj => Encoding.UTF8.ToBase64(obj.ToString())).OrderBy(a => a, StringComparer.Ordinal).ToArray();
|
||||
return "_tmp:" + string.Join("$", orderedBase64Strs);
|
||||
}
|
||||
|
||||
internal static string ToBase64(this System.Text.Encoding encoding, string text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] textAsBytes = encoding.GetBytes(text);
|
||||
return Convert.ToBase64String(textAsBytes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
interface IAVIMPlatformHooks
|
||||
{
|
||||
IWebSocketClient WebSocketClient { get; }
|
||||
|
||||
string ua { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class FreeStyleMessageClassInfo
|
||||
{
|
||||
public TypeInfo TypeInfo { get; private set; }
|
||||
public IDictionary<String, String> PropertyMappings { get; private set; }
|
||||
private ConstructorInfo Constructor { get; set; }
|
||||
//private MethodInfo ValidateMethod { get; set; }
|
||||
|
||||
public int TypeInt { get; set; }
|
||||
|
||||
public FreeStyleMessageClassInfo(Type type, ConstructorInfo constructor)
|
||||
{
|
||||
TypeInfo = type.GetTypeInfo();
|
||||
Constructor = constructor;
|
||||
PropertyMappings = ReflectionHelpers.GetProperties(type)
|
||||
.Select(prop => Tuple.Create(prop, prop.GetCustomAttribute<AVIMMessageFieldNameAttribute>(true)))
|
||||
.Where(t => t.Item2 != null)
|
||||
.Select(t => Tuple.Create(t.Item1, t.Item2.FieldName))
|
||||
.ToDictionary(t => t.Item1.Name, t => t.Item2);
|
||||
}
|
||||
public bool Validate(string msgStr)
|
||||
{
|
||||
var instance = Instantiate(msgStr);
|
||||
if (instance is AVIMTypedMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
var msgDic = Json.Parse(msgStr) as IDictionary<string, object>;
|
||||
if (msgDic != null)
|
||||
{
|
||||
if (msgDic.ContainsKey(AVIMProtocol.LCTYPE))
|
||||
{
|
||||
return msgDic[AVIMProtocol.LCTYPE].ToString() == TypeInt.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is ArgumentException)
|
||||
{
|
||||
return instance.Validate(msgStr);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return instance.Validate(msgStr);
|
||||
}
|
||||
|
||||
public IAVIMMessage Instantiate(string msgStr)
|
||||
{
|
||||
var rtn = (IAVIMMessage)Constructor.Invoke(null);
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public static string GetMessageClassName(TypeInfo type)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<AVIMMessageClassNameAttribute>();
|
||||
return attribute != null ? attribute.ClassName : null;
|
||||
}
|
||||
|
||||
public static int GetTypedInteger(TypeInfo type)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<AVIMTypedMessageTypeIntAttribute>();
|
||||
return attribute != null ? attribute.TypeInteger : 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class FreeStyleMessageClassingController : IFreeStyleMessageClassingController
|
||||
{
|
||||
private static readonly string messageClassName = "_AVIMMessage";
|
||||
private readonly IDictionary<string, FreeStyleMessageClassInfo> registeredInterfaces;
|
||||
private readonly ReaderWriterLockSlim mutex;
|
||||
|
||||
public FreeStyleMessageClassingController()
|
||||
{
|
||||
mutex = new ReaderWriterLockSlim();
|
||||
registeredInterfaces = new Dictionary<string, FreeStyleMessageClassInfo>();
|
||||
}
|
||||
|
||||
public Type GetType(IDictionary<string, object> msg)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IAVIMMessage Instantiate(string msgStr, IDictionary<string, object> buildInData)
|
||||
{
|
||||
FreeStyleMessageClassInfo info = null;
|
||||
mutex.EnterReadLock();
|
||||
bool bin = false;
|
||||
if (buildInData.ContainsKey("bin"))
|
||||
{
|
||||
bool.TryParse(buildInData["bin"].ToString(), out bin);
|
||||
}
|
||||
|
||||
if (bin)
|
||||
{
|
||||
var binMessage = new AVIMBinaryMessage();
|
||||
this.DecodeProperties(binMessage, buildInData);
|
||||
return binMessage;
|
||||
}
|
||||
|
||||
var reverse = registeredInterfaces.Values.Reverse();
|
||||
foreach (var subInterface in reverse)
|
||||
{
|
||||
if (subInterface.Validate(msgStr))
|
||||
{
|
||||
info = subInterface;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex.ExitReadLock();
|
||||
|
||||
var message = info != null ? info.Instantiate(msgStr) : new AVIMMessage();
|
||||
|
||||
this.DecodeProperties(message, buildInData);
|
||||
|
||||
message.Deserialize(msgStr);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public IAVIMMessage DecodeProperties(IAVIMMessage message, IDictionary<string, object> buildInData)
|
||||
{
|
||||
long timestamp;
|
||||
if (buildInData.ContainsKey("timestamp"))
|
||||
{
|
||||
if (long.TryParse(buildInData["timestamp"].ToString(), out timestamp))
|
||||
{
|
||||
message.ServerTimestamp = timestamp;
|
||||
}
|
||||
}
|
||||
long ackAt;
|
||||
if (buildInData.ContainsKey("ackAt"))
|
||||
{
|
||||
if (long.TryParse(buildInData["ackAt"].ToString(), out ackAt))
|
||||
{
|
||||
message.RcpTimestamp = ackAt;
|
||||
}
|
||||
}
|
||||
|
||||
if (buildInData.ContainsKey("from"))
|
||||
{
|
||||
message.FromClientId = buildInData["from"].ToString();
|
||||
}
|
||||
if (buildInData.ContainsKey("msgId"))
|
||||
{
|
||||
message.Id = buildInData["msgId"].ToString();
|
||||
}
|
||||
if (buildInData.ContainsKey("cid"))
|
||||
{
|
||||
message.ConversationId = buildInData["cid"].ToString();
|
||||
}
|
||||
if (buildInData.ContainsKey("fromPeerId"))
|
||||
{
|
||||
message.FromClientId = buildInData["fromPeerId"].ToString();
|
||||
}
|
||||
if (buildInData.ContainsKey("id"))
|
||||
{
|
||||
message.Id = buildInData["id"].ToString();
|
||||
}
|
||||
if (buildInData.ContainsKey("mid"))
|
||||
{
|
||||
message.Id = buildInData["mid"].ToString();
|
||||
}
|
||||
if (buildInData.ContainsKey("mentionPids"))
|
||||
{
|
||||
message.MentionList = AVDecoder.Instance.DecodeList<string>(buildInData["mentionPids"]);
|
||||
}
|
||||
if (buildInData.TryGetValue("patchTimestamp", out object patchTimestampObj)) {
|
||||
if (long.TryParse(patchTimestampObj.ToString(), out long patchTimestamp)) {
|
||||
message.UpdatedAt = patchTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
bool mentionAll;
|
||||
if (buildInData.ContainsKey("mentionAll"))
|
||||
{
|
||||
if (bool.TryParse(buildInData["mentionAll"].ToString(), out mentionAll))
|
||||
{
|
||||
message.MentionAll = mentionAll;
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public IDictionary<string, object> EncodeProperties(IAVIMMessage subclass)
|
||||
{
|
||||
var type = subclass.GetType();
|
||||
var result = new Dictionary<string, object>();
|
||||
var className = GetClassName(type);
|
||||
var typeInt = GetTypeInt(type);
|
||||
var propertMappings = GetPropertyMappings(className);
|
||||
foreach (var propertyPair in propertMappings)
|
||||
{
|
||||
var propertyInfo = ReflectionHelpers.GetProperty(type, propertyPair.Key);
|
||||
var operation = propertyInfo.GetValue(subclass, null);
|
||||
if (operation != null)
|
||||
result[propertyPair.Value] = PointerOrLocalIdEncoder.Instance.Encode(operation);
|
||||
}
|
||||
if (typeInt != 0)
|
||||
{
|
||||
result[AVIMProtocol.LCTYPE] = typeInt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool IsTypeValid(IDictionary<string, object> msg, Type type)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RegisterSubclass(Type type)
|
||||
{
|
||||
TypeInfo typeInfo = type.GetTypeInfo();
|
||||
|
||||
if (!typeof(IAVIMMessage).GetTypeInfo().IsAssignableFrom(typeInfo))
|
||||
{
|
||||
throw new ArgumentException("Cannot register a type that is not a implementation of IAVIMMessage");
|
||||
}
|
||||
var className = GetClassName(type);
|
||||
var typeInt = GetTypeInt(type);
|
||||
try
|
||||
{
|
||||
mutex.EnterWriteLock();
|
||||
ConstructorInfo constructor = type.FindConstructor();
|
||||
if (constructor == null)
|
||||
{
|
||||
throw new ArgumentException("Cannot register a type that does not implement the default constructor!");
|
||||
}
|
||||
var classInfo = new FreeStyleMessageClassInfo(type, constructor);
|
||||
if (typeInt != 0)
|
||||
{
|
||||
classInfo.TypeInt = typeInt;
|
||||
}
|
||||
registeredInterfaces[className] = classInfo;
|
||||
}
|
||||
finally
|
||||
{
|
||||
mutex.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
public String GetClassName(Type type)
|
||||
{
|
||||
return type == typeof(IAVIMMessage)
|
||||
? messageClassName
|
||||
: FreeStyleMessageClassInfo.GetMessageClassName(type.GetTypeInfo());
|
||||
}
|
||||
public int GetTypeInt(Type type)
|
||||
{
|
||||
return type == typeof(AVIMTypedMessage) ? 0 : FreeStyleMessageClassInfo.GetTypedInteger(type.GetTypeInfo());
|
||||
}
|
||||
public IDictionary<String, String> GetPropertyMappings(String className)
|
||||
{
|
||||
FreeStyleMessageClassInfo info = null;
|
||||
mutex.EnterReadLock();
|
||||
registeredInterfaces.TryGetValue(className, out info);
|
||||
if (info == null)
|
||||
{
|
||||
registeredInterfaces.TryGetValue(messageClassName, out info);
|
||||
}
|
||||
mutex.ExitReadLock();
|
||||
|
||||
return info.PropertyMappings;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
interface IFreeStyleMessageClassingController
|
||||
{
|
||||
bool IsTypeValid(IDictionary<string,object> msg, Type type);
|
||||
void RegisterSubclass(Type t);
|
||||
IAVIMMessage Instantiate(string msgStr,IDictionary<string,object> buildInData);
|
||||
IDictionary<string, object> EncodeProperties(IAVIMMessage subclass);
|
||||
Type GetType(IDictionary<string, object> msg);
|
||||
String GetClassName(Type type);
|
||||
IDictionary<String, String> GetPropertyMappings(String className);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class AVIMProtocol
|
||||
{
|
||||
#region msg format
|
||||
static internal readonly string LCTYPE = "_lctype";
|
||||
static internal readonly string LCFILE = "_lcfile";
|
||||
static internal readonly string LCTEXT = "_lctext";
|
||||
static internal readonly string LCATTRS = "_lcattrs";
|
||||
static internal readonly string LCLOC = "_lcloc";
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class AVRouterController : IAVRouterController
|
||||
{
|
||||
const string routerUrl = "http://router.g0.push.leancloud.cn/v1/route?appId={0}";
|
||||
const string routerKey = "LeanCloud_RouterState";
|
||||
public Task<PushRouterState> GetAsync(string pushRouter = null, bool secure = true, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
//return Task.FromResult(new PushRouterState()
|
||||
//{
|
||||
// server = "wss://rtm57.leancloud.cn/"
|
||||
//});
|
||||
return LoadAysnc(cancellationToken).OnSuccess(_ =>
|
||||
{
|
||||
var cache = _.Result;
|
||||
var task = Task.FromResult<PushRouterState>(cache);
|
||||
|
||||
if (cache == null || cache.expire < DateTime.Now.ToUnixTimeStamp())
|
||||
{
|
||||
task = QueryAsync(pushRouter, secure, cancellationToken);
|
||||
}
|
||||
|
||||
return task;
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理地址缓存
|
||||
/// </summary>
|
||||
/// <returns>The cache.</returns>
|
||||
public Task ClearCache() {
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
AVPlugins.Instance.StorageController.LoadAsync().ContinueWith(t => {
|
||||
if (t.IsFaulted) {
|
||||
tcs.SetResult(true);
|
||||
} else {
|
||||
var storage = t.Result;
|
||||
if (storage.ContainsKey(routerKey)) {
|
||||
storage.RemoveAsync(routerKey).ContinueWith(_ => tcs.SetResult(true));
|
||||
} else {
|
||||
tcs.SetResult(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
Task<PushRouterState> LoadAysnc(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
return AVPlugins.Instance.StorageController.LoadAsync().OnSuccess(_ =>
|
||||
{
|
||||
var currentCache = _.Result;
|
||||
object routeCacheStr = null;
|
||||
if (currentCache.TryGetValue(routerKey, out routeCacheStr))
|
||||
{
|
||||
var routeCache = routeCacheStr as IDictionary<string, object>;
|
||||
var routerState = new PushRouterState()
|
||||
{
|
||||
groupId = routeCache["groupId"] as string,
|
||||
server = routeCache["server"] as string,
|
||||
secondary = routeCache["secondary"] as string,
|
||||
ttl = long.Parse(routeCache["ttl"].ToString()),
|
||||
expire = long.Parse(routeCache["expire"].ToString()),
|
||||
source = "localCache"
|
||||
};
|
||||
return routerState;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Task.FromResult<PushRouterState>(null);
|
||||
}
|
||||
}
|
||||
|
||||
Task<PushRouterState> QueryAsync(string pushRouter, bool secure, CancellationToken cancellationToken)
|
||||
{
|
||||
var routerHost = pushRouter;
|
||||
if (routerHost == null) {
|
||||
var appRouter = AVPlugins.Instance.AppRouterController.Get();
|
||||
routerHost = string.Format("https://{0}/v1/route?appId={1}", appRouter.RealtimeRouterServer, AVClient.CurrentConfiguration.ApplicationId) ?? appRouter.RealtimeRouterServer ?? string.Format(routerUrl, AVClient.CurrentConfiguration.ApplicationId);
|
||||
}
|
||||
AVRealtime.PrintLog($"router: {routerHost}");
|
||||
AVRealtime.PrintLog($"push: {pushRouter}");
|
||||
if (!string.IsNullOrEmpty(pushRouter))
|
||||
{
|
||||
var rtmUri = new Uri(pushRouter);
|
||||
if (!string.IsNullOrEmpty(rtmUri.Scheme))
|
||||
{
|
||||
var url = new Uri(rtmUri, "v1/route").ToString();
|
||||
routerHost = string.Format("{0}?appId={1}", url, AVClient.CurrentConfiguration.ApplicationId);
|
||||
}
|
||||
else
|
||||
{
|
||||
routerHost = string.Format("https://{0}/v1/route?appId={1}", pushRouter, AVClient.CurrentConfiguration.ApplicationId);
|
||||
}
|
||||
}
|
||||
if (secure)
|
||||
{
|
||||
routerHost += "&secure=1";
|
||||
}
|
||||
|
||||
AVRealtime.PrintLog("use push router url:" + routerHost);
|
||||
|
||||
return AVClient.RequestAsync(uri: new Uri(routerHost),
|
||||
method: "GET",
|
||||
headers: null,
|
||||
data: null,
|
||||
contentType: "application/json",
|
||||
cancellationToken: CancellationToken.None).ContinueWith<PushRouterState>(t =>
|
||||
{
|
||||
if (t.Exception != null)
|
||||
{
|
||||
var innnerException = t.Exception.InnerException;
|
||||
AVRealtime.PrintLog(innnerException.Message);
|
||||
throw innnerException;
|
||||
}
|
||||
var httpStatus = (int)t.Result.Item1;
|
||||
if (httpStatus != 200)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
var result = t.Result.Item2;
|
||||
|
||||
var routerState = Json.Parse(result) as IDictionary<string, object>;
|
||||
if (routerState.Keys.Count == 0)
|
||||
{
|
||||
throw new KeyNotFoundException("Can not get websocket url from server,please check the appId.");
|
||||
}
|
||||
var ttl = long.Parse(routerState["ttl"].ToString());
|
||||
var expire = DateTime.Now.AddSeconds(ttl);
|
||||
routerState["expire"] = expire.ToUnixTimeStamp();
|
||||
|
||||
//save to local cache async.
|
||||
AVPlugins.Instance.StorageController.LoadAsync().OnSuccess(storage => storage.Result.AddAsync(routerKey, routerState));
|
||||
var routerStateObj = new PushRouterState()
|
||||
{
|
||||
groupId = routerState["groupId"] as string,
|
||||
server = routerState["server"] as string,
|
||||
secondary = routerState["secondary"] as string,
|
||||
ttl = long.Parse(routerState["ttl"].ToString()),
|
||||
expire = expire.ToUnixTimeStamp(),
|
||||
source = "online"
|
||||
};
|
||||
|
||||
return routerStateObj;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is KeyNotFoundException)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
interface IAVRouterController
|
||||
{
|
||||
Task<PushRouterState> GetAsync(string pushRouter, bool secure, CancellationToken cancellationToken = default(CancellationToken));
|
||||
Task ClearCache();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal class PushRouterState
|
||||
{
|
||||
public string groupId { get; internal set; }
|
||||
public string server { get; internal set; }
|
||||
public long ttl { get; internal set; }
|
||||
public long expire { get; internal set; }
|
||||
public string secondary { get; internal set; }
|
||||
public string groupUrl { get; internal set; }
|
||||
|
||||
public string source { get; internal set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
public interface IAVTimer
|
||||
{
|
||||
/// <summary>
|
||||
/// Start this timer.
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Stop this timer.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
|
||||
bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of milliseconds between timer events.
|
||||
/// </summary>
|
||||
/// <value>The interval.</value>
|
||||
double Interval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已经执行了多少次
|
||||
/// </summary>
|
||||
long Executed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when elapsed.
|
||||
/// </summary>
|
||||
event EventHandler<TimerEventArgs> Elapsed;
|
||||
}
|
||||
/// <summary>
|
||||
/// Timer event arguments.
|
||||
/// </summary>
|
||||
public class TimerEventArgs : EventArgs
|
||||
{
|
||||
public TimerEventArgs(DateTime signalTime)
|
||||
{
|
||||
SignalTime = signalTime;
|
||||
}
|
||||
public DateTime SignalTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
internal delegate void TimerCallback();
|
||||
|
||||
internal sealed class Timer : CancellationTokenSource, IDisposable
|
||||
{
|
||||
TimerCallback exe;
|
||||
int Interval { get; set; }
|
||||
|
||||
internal Timer(TimerCallback callback, int interval, bool enable)
|
||||
{
|
||||
exe = callback;
|
||||
Interval = interval;
|
||||
|
||||
Enabled = enable;
|
||||
Execute();
|
||||
}
|
||||
|
||||
Task Execute()
|
||||
{
|
||||
if (Enabled)
|
||||
return Task.Delay(Interval).ContinueWith(t =>
|
||||
{
|
||||
if (!Enabled)
|
||||
return null;
|
||||
exe();
|
||||
return this.Execute();
|
||||
});
|
||||
else
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
volatile bool enabled;
|
||||
public bool Enabled
|
||||
{
|
||||
get {
|
||||
return enabled;
|
||||
} set {
|
||||
enabled = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AVTimer : IAVTimer
|
||||
{
|
||||
public AVTimer()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return timer.Enabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
timer.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public double Interval
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
long executed;
|
||||
|
||||
public long Executed
|
||||
{
|
||||
get
|
||||
{
|
||||
return executed;
|
||||
}
|
||||
|
||||
internal set
|
||||
{
|
||||
executed = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (timer == null)
|
||||
{
|
||||
timer = new Timer(() =>
|
||||
{
|
||||
Elapsed(this, new TimerEventArgs(DateTime.Now));
|
||||
}, (int)Interval, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (timer != null) timer.Enabled = false;
|
||||
}
|
||||
|
||||
public event EventHandler<TimerEventArgs> Elapsed;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
using System;
|
||||
using System.Timers;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
public class AVTimer : IAVTimer
|
||||
{
|
||||
public AVTimer()
|
||||
{
|
||||
timer = new Timer();
|
||||
}
|
||||
|
||||
Timer timer;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return timer.Enabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
timer.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public double Interval
|
||||
{
|
||||
get
|
||||
{
|
||||
return timer.Interval;
|
||||
}
|
||||
set
|
||||
{
|
||||
timer.Interval = value;
|
||||
}
|
||||
}
|
||||
|
||||
long executed;
|
||||
|
||||
public long Executed
|
||||
{
|
||||
get
|
||||
{
|
||||
return executed;
|
||||
}
|
||||
|
||||
internal set
|
||||
{
|
||||
executed = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
public event EventHandler<TimerEventArgs> Elapsed
|
||||
{
|
||||
add
|
||||
{
|
||||
timer.Elapsed += (object sender, ElapsedEventArgs e) =>
|
||||
{
|
||||
value(this, new TimerEventArgs(e.SignalTime));
|
||||
};
|
||||
}
|
||||
remove
|
||||
{
|
||||
timer.Elapsed -= (object sender, ElapsedEventArgs e) =>
|
||||
{
|
||||
value(this, new TimerEventArgs(e.SignalTime));
|
||||
}; ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// LeanCloud WebSocket 客户端接口
|
||||
/// </summary>
|
||||
public interface IWebSocketClient
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端 WebSocket 长连接是否打开
|
||||
/// </summary>
|
||||
bool IsOpen { get; }
|
||||
|
||||
/// <summary>
|
||||
/// WebSocket 长连接关闭时触发的事件回调
|
||||
/// </summary>
|
||||
event Action<int, string, string> OnClosed;
|
||||
|
||||
/// <summary>
|
||||
/// 云端发送数据包给客户端,WebSocket 接受到时触发的事件回调
|
||||
/// </summary>
|
||||
event Action<string> OnMessage;
|
||||
|
||||
/// <summary>
|
||||
/// 客户端 WebSocket 长连接成功打开时,触发的事件回调
|
||||
/// </summary>
|
||||
event Action OnOpened;
|
||||
|
||||
/// <summary>
|
||||
/// 主动关闭连接
|
||||
/// </summary>
|
||||
void Close();
|
||||
|
||||
void Disconnect();
|
||||
|
||||
/// <summary>
|
||||
/// 打开连接
|
||||
/// </summary>
|
||||
/// <param name="url">wss 地址</param>
|
||||
/// <param name="protocol">子协议</param>
|
||||
void Open(string url, string protocol = null);
|
||||
/// <summary>
|
||||
/// 发送数据包的接口
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
void Send(string message);
|
||||
|
||||
Task<bool> Connect(string url, string protocol = null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
public class DefaultWebSocketClient : IWebSocketClient
|
||||
{
|
||||
private const int ReceiveChunkSize = 1024;
|
||||
private const int SendChunkSize = 1024;
|
||||
|
||||
private ClientWebSocket _ws;
|
||||
private Uri _uri;
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
private readonly CancellationToken _cancellationToken;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when on closed.
|
||||
/// </summary>
|
||||
public event Action<int, string, string> OnClosed;
|
||||
/// <summary>
|
||||
/// Occurs when on error.
|
||||
/// </summary>
|
||||
public event Action<string> OnError;
|
||||
/// <summary>
|
||||
/// Occurs when on log.
|
||||
/// </summary>
|
||||
public event Action<string> OnLog;
|
||||
/// <summary>
|
||||
/// Occurs when on opened.
|
||||
/// </summary>
|
||||
public event Action OnOpened;
|
||||
|
||||
public bool IsOpen => _ws.State == WebSocketState.Open;
|
||||
|
||||
public DefaultWebSocketClient()
|
||||
{
|
||||
_ws = NewWebSocket();
|
||||
_cancellationToken = _cancellationTokenSource.Token;
|
||||
}
|
||||
|
||||
public event Action<string> OnMessage;
|
||||
|
||||
private async void StartListen()
|
||||
{
|
||||
var buffer = new byte[8192];
|
||||
|
||||
try
|
||||
{
|
||||
while (_ws.State == WebSocketState.Open)
|
||||
{
|
||||
var stringResult = new StringBuilder();
|
||||
|
||||
WebSocketReceiveResult result;
|
||||
do
|
||||
{
|
||||
result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationToken);
|
||||
|
||||
if (result.MessageType == WebSocketMessageType.Close)
|
||||
{
|
||||
await
|
||||
_ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
CallOnDisconnected();
|
||||
}
|
||||
else
|
||||
{
|
||||
var str = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||||
stringResult.Append(str);
|
||||
}
|
||||
|
||||
} while (!result.EndOfMessage);
|
||||
|
||||
CallOnMessage(stringResult);
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CallOnDisconnected();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ws.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void CallOnMessage(StringBuilder stringResult)
|
||||
{
|
||||
if (OnMessage != null)
|
||||
RunInTask(() => OnMessage(stringResult.ToString()));
|
||||
}
|
||||
|
||||
private void CallOnDisconnected()
|
||||
{
|
||||
AVRealtime.PrintLog("PCL websocket closed without parameters.");
|
||||
if (OnClosed != null)
|
||||
RunInTask(() => this.OnClosed(0, "", ""));
|
||||
}
|
||||
|
||||
private void CallOnConnected()
|
||||
{
|
||||
if (OnOpened != null)
|
||||
RunInTask(() => this.OnOpened());
|
||||
}
|
||||
|
||||
private static void RunInTask(Action action)
|
||||
{
|
||||
Task.Factory.StartNew(action);
|
||||
}
|
||||
|
||||
public async void Close()
|
||||
{
|
||||
if (_ws != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
CallOnDisconnected();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CallOnDisconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect() {
|
||||
_ws?.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
||||
}
|
||||
|
||||
public async void Open(string url, string protocol = null)
|
||||
{
|
||||
_uri = new Uri(url);
|
||||
if (_ws.State == WebSocketState.Open || _ws.State == WebSocketState.Connecting)
|
||||
{
|
||||
_ws = NewWebSocket();
|
||||
}
|
||||
try
|
||||
{
|
||||
await _ws.ConnectAsync(_uri, _cancellationToken);
|
||||
CallOnConnected();
|
||||
StartListen();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is ObjectDisposedException)
|
||||
{
|
||||
OnError($"can NOT connect server with url: {url}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async void Send(string message)
|
||||
{
|
||||
if (_ws.State != WebSocketState.Open)
|
||||
{
|
||||
throw new Exception("Connection is not open.");
|
||||
}
|
||||
|
||||
var encoded = Encoding.UTF8.GetBytes(message);
|
||||
var buffer = new ArraySegment<Byte>(encoded, 0, encoded.Length);
|
||||
await _ws.SendAsync(buffer, WebSocketMessageType.Text, true, _cancellationToken);
|
||||
}
|
||||
|
||||
ClientWebSocket NewWebSocket()
|
||||
{
|
||||
var result = new ClientWebSocket();
|
||||
result.Options.KeepAliveInterval = TimeSpan.FromSeconds(20);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using Websockets;
|
||||
using System.Net.WebSockets;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
public class WebSocketClient: IWebSocketClient
|
||||
{
|
||||
internal readonly IWebSocketConnection connection;
|
||||
public WebSocketClient()
|
||||
{
|
||||
Websockets.Net.WebsocketConnection.Link();
|
||||
connection = WebSocketFactory.Create();
|
||||
}
|
||||
|
||||
public event Action OnClosed;
|
||||
public event Action<string> OnError;
|
||||
public event Action<string> OnLog;
|
||||
|
||||
public event Action OnOpened
|
||||
{
|
||||
add
|
||||
{
|
||||
connection.OnOpened += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
connection.OnOpened -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<string> OnMessage
|
||||
{
|
||||
add
|
||||
{
|
||||
connection.OnMessage += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
connection.OnMessage -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return connection.IsOpen;
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void Open(string url, string protocol = null)
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
connection.Open(url, protocol);
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(string message)
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
if (this.IsOpen)
|
||||
{
|
||||
connection.Send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Websockets;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// LeanCloud Realtime SDK for .NET Portable 内置默认的 WebSocketClient
|
||||
/// </summary>
|
||||
public class DefaultWebSocketClient : IWebSocketClient
|
||||
{
|
||||
internal IWebSocketConnection connection;
|
||||
/// <summary>
|
||||
/// LeanCluod .NET Realtime SDK 内置默认的 WebSocketClient
|
||||
/// 开发者可以在初始化的时候指定自定义的 WebSocketClient
|
||||
/// </summary>
|
||||
public DefaultWebSocketClient()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public event Action<int, string, string> OnClosed;
|
||||
public event Action OnOpened;
|
||||
public event Action<string> OnMessage;
|
||||
|
||||
public bool IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return connection != null && connection.IsOpen;
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
connection.OnOpened -= Connection_OnOpened;
|
||||
connection.OnMessage -= Connection_OnMessage;
|
||||
connection.OnClosed -= Connection_OnClosed;
|
||||
connection.OnError -= Connection_OnError;
|
||||
try {
|
||||
connection.Close();
|
||||
} catch (Exception e) {
|
||||
AVRealtime.PrintLog(string.Format("close websocket error: {0}", e.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect() {
|
||||
connection?.Close();
|
||||
}
|
||||
|
||||
public void Open(string url, string protocol = null)
|
||||
{
|
||||
// 在每次打开时,重新创建 WebSocket 对象
|
||||
connection = WebSocketFactory.Create();
|
||||
connection.OnOpened += Connection_OnOpened;
|
||||
connection.OnMessage += Connection_OnMessage;
|
||||
connection.OnClosed += Connection_OnClosed;
|
||||
connection.OnError += Connection_OnError;
|
||||
if (!string.IsNullOrEmpty(protocol))
|
||||
{
|
||||
url = string.Format("{0}?subprotocol={1}", url, protocol);
|
||||
}
|
||||
connection.Open(url, protocol);
|
||||
}
|
||||
|
||||
private void Connection_OnOpened()
|
||||
{
|
||||
OnOpened?.Invoke();
|
||||
}
|
||||
|
||||
private void Connection_OnMessage(string obj)
|
||||
{
|
||||
AVRealtime.PrintLog("websocket<=" + obj);
|
||||
OnMessage?.Invoke(obj);
|
||||
}
|
||||
|
||||
private void Connection_OnClosed()
|
||||
{
|
||||
AVRealtime.PrintLog("PCL websocket closed without parameters..");
|
||||
OnClosed?.Invoke(0, "", "");
|
||||
}
|
||||
|
||||
private void Connection_OnError(string obj)
|
||||
{
|
||||
AVRealtime.PrintLog($"PCL websocket error: {obj}");
|
||||
connection?.Close();
|
||||
}
|
||||
|
||||
public void Send(string message)
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
if (this.IsOpen)
|
||||
{
|
||||
connection.Send(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
var log = "Connection is NOT open when send message";
|
||||
AVRealtime.PrintLog(log);
|
||||
connection?.Close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
AVRealtime.PrintLog("Connection is NULL");
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> Connect(string url, string protocol = null) {
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
Action onOpen = null;
|
||||
Action onClose = null;
|
||||
Action<string> onError = null;
|
||||
onOpen = () => {
|
||||
AVRealtime.PrintLog("PCL websocket opened");
|
||||
connection.OnOpened -= onOpen;
|
||||
connection.OnClosed -= onClose;
|
||||
connection.OnError -= onError;
|
||||
// 注册事件
|
||||
connection.OnMessage += Connection_OnMessage;
|
||||
connection.OnClosed += Connection_OnClosed;
|
||||
connection.OnError += Connection_OnError;
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
onClose = () => {
|
||||
connection.OnOpened -= onOpen;
|
||||
connection.OnClosed -= onClose;
|
||||
connection.OnError -= onError;
|
||||
tcs.SetException(new Exception("连接关闭"));
|
||||
};
|
||||
onError = (err) => {
|
||||
AVRealtime.PrintLog(string.Format("连接错误:{0}", err));
|
||||
connection.OnOpened -= onOpen;
|
||||
connection.OnClosed -= onClose;
|
||||
connection.OnError -= onError;
|
||||
try {
|
||||
connection.Close();
|
||||
} catch (Exception e) {
|
||||
AVRealtime.PrintLog(string.Format("关闭错误的 WebSocket 异常:{0}", e.Message));
|
||||
} finally {
|
||||
tcs.SetException(new Exception(string.Format("连接错误:{0}", err)));
|
||||
}
|
||||
};
|
||||
|
||||
// 在每次打开时,重新创建 WebSocket 对象
|
||||
connection = WebSocketFactory.Create();
|
||||
connection.OnOpened += onOpen;
|
||||
connection.OnClosed += onClose;
|
||||
connection.OnError += onError;
|
||||
//
|
||||
if (!string.IsNullOrEmpty(protocol)) {
|
||||
url = string.Format("{0}?subprotocol={1}", url, protocol);
|
||||
}
|
||||
connection.Open(url, protocol);
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using WebSocketSharp;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime.Internal
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// LeanCluod Unity Realtime SDK 内置默认的 WebSocketClient
|
||||
/// 开发者可以在初始化的时候指定自定义的 WebSocketClient
|
||||
/// </summary>
|
||||
public class DefaultWebSocketClient : IWebSocketClient
|
||||
{
|
||||
WebSocket ws;
|
||||
public bool IsOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ws == null) { return false; }
|
||||
return ws.IsAlive;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<int, string, string> OnClosed;
|
||||
public event Action<string> OnMessage
|
||||
{
|
||||
add
|
||||
{
|
||||
onMesssageCount++;
|
||||
AVRealtime.PrintLog("DefaultWebSocketClient.OnMessage event add with " + onMesssageCount + " times");
|
||||
m_OnMessage += value;
|
||||
|
||||
}
|
||||
remove
|
||||
{
|
||||
onMesssageCount--;
|
||||
AVRealtime.PrintLog("DefaultWebSocketClient.OnMessage event remove with " + onMesssageCount + " times");
|
||||
m_OnMessage -= value;
|
||||
}
|
||||
}
|
||||
private Action<string> m_OnMessage;
|
||||
private int onMesssageCount = 0;
|
||||
public event Action OnOpened;
|
||||
|
||||
public void Close()
|
||||
{
|
||||
ws.CloseAsync();
|
||||
ws.OnOpen -= OnOpen;
|
||||
ws.OnMessage -= OnWebSokectMessage;
|
||||
ws.OnClose -= OnClose;
|
||||
}
|
||||
|
||||
public void Disconnect() {
|
||||
ws.CloseAsync();
|
||||
}
|
||||
|
||||
public void Open(string url, string protocol = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(protocol))
|
||||
{
|
||||
url = string.Format("{0}?subprotocol={1}", url, protocol);
|
||||
}
|
||||
ws = new WebSocket(url);
|
||||
ws.OnOpen += OnOpen;
|
||||
ws.OnMessage += OnWebSokectMessage;
|
||||
ws.OnClose += OnClose;
|
||||
ws.ConnectAsync();
|
||||
}
|
||||
|
||||
private void OnWebSokectMessage(object sender, MessageEventArgs e)
|
||||
{
|
||||
AVRealtime.PrintLog("websocket<=" + e.Data);
|
||||
m_OnMessage?.Invoke(e.Data);
|
||||
}
|
||||
|
||||
private void OnClose(object sender, CloseEventArgs e) {
|
||||
AVRealtime.PrintLog(string.Format("Unity websocket closed with {0}, {1}", e.Code, e.Reason));
|
||||
OnClosed?.Invoke(e.Code, e.Reason, null);
|
||||
}
|
||||
|
||||
void OnWebSocketError(object sender, ErrorEventArgs e) {
|
||||
AVRealtime.PrintLog($"PCL websocket error: {e.Message}");
|
||||
ws?.Close();
|
||||
}
|
||||
|
||||
private void OnOpen(object sender, EventArgs e) {
|
||||
OnOpened?.Invoke();
|
||||
}
|
||||
|
||||
public void Send(string message)
|
||||
{
|
||||
ws.SendAsync(message, (b) =>
|
||||
{
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public Task<bool> Connect(string url, string protocol = null) {
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
EventHandler onOpen = null;
|
||||
EventHandler<CloseEventArgs> onClose = null;
|
||||
EventHandler<ErrorEventArgs> onError = null;
|
||||
onOpen = (sender, e) => {
|
||||
AVRealtime.PrintLog("PCL websocket opened");
|
||||
ws.OnOpen -= onOpen;
|
||||
ws.OnClose -= onClose;
|
||||
ws.OnError -= onError;
|
||||
// 注册事件
|
||||
ws.OnMessage += OnWebSokectMessage;
|
||||
ws.OnClose += OnClose;
|
||||
ws.OnError += OnWebSocketError;
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
onClose = (sender, e) => {
|
||||
ws.OnOpen -= onOpen;
|
||||
ws.OnClose -= onClose;
|
||||
ws.OnError -= onError;
|
||||
tcs.SetException(new Exception("连接关闭"));
|
||||
};
|
||||
onError = (sender, e) => {
|
||||
AVRealtime.PrintLog(string.Format("连接错误:{0}", e.Message));
|
||||
ws.OnOpen -= onOpen;
|
||||
ws.OnClose -= onClose;
|
||||
ws.OnError -= onError;
|
||||
try {
|
||||
ws.Close();
|
||||
} catch (Exception ex) {
|
||||
AVRealtime.PrintLog(string.Format("关闭错误的 WebSocket 异常:{0}", ex.Message));
|
||||
} finally {
|
||||
tcs.SetException(new Exception(string.Format("连接错误:{0}", e.Message)));
|
||||
}
|
||||
};
|
||||
|
||||
// 在每次打开时,重新创建 WebSocket 对象
|
||||
if (!string.IsNullOrEmpty(protocol)) {
|
||||
url = string.Format("{0}?subprotocol={1}", url, protocol);
|
||||
}
|
||||
ws = new WebSocket(url);
|
||||
ws.OnOpen += onOpen;
|
||||
ws.OnClose += onClose;
|
||||
ws.OnError += onError;
|
||||
ws.ConnectAsync();
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Audio message.
|
||||
/// </summary>
|
||||
[AVIMMessageClassName("_AVIMAudioMessage")]
|
||||
[AVIMTypedMessageTypeInt(-3)]
|
||||
public class AVIMAudioMessage : AVIMFileMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Video message.
|
||||
/// </summary>
|
||||
[AVIMMessageClassName("_AVIMVideoMessage")]
|
||||
[AVIMTypedMessageTypeInt(-4)]
|
||||
public class AVIMVideoMessage: AVIMFileMessage
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于二进制数据的消息类型,可以直接发送 Byte 数组
|
||||
/// </summary>
|
||||
[AVIMMessageClassName("_AVIMBinaryMessage")]
|
||||
public class AVIMBinaryMessage : AVIMMessage
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMBinaryMessage"/> class.
|
||||
/// </summary>
|
||||
public AVIMBinaryMessage()
|
||||
{
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// create new instance of AVIMBinnaryMessage
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
public AVIMBinaryMessage(byte[] data)
|
||||
{
|
||||
this.BinaryData = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binary data.
|
||||
/// </summary>
|
||||
/// <value>The binary data.</value>
|
||||
public byte[] BinaryData { get; set; }
|
||||
|
||||
internal override MessageCommand BeforeSend(MessageCommand cmd)
|
||||
{
|
||||
var result = base.BeforeSend(cmd);
|
||||
result = result.Binary(this.BinaryData);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,181 @@
|
|||
using LeanCloud.Realtime.Internal;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话查询类
|
||||
/// </summary>
|
||||
public class AVIMConversationQuery : AVQueryPair<AVIMConversationQuery, AVIMConversation>, IAVQuery
|
||||
{
|
||||
internal AVIMClient CurrentClient { get; set; }
|
||||
internal AVIMConversationQuery(AVIMClient _currentClient)
|
||||
: base()
|
||||
{
|
||||
CurrentClient = _currentClient;
|
||||
}
|
||||
|
||||
bool compact;
|
||||
bool withLastMessageRefreshed;
|
||||
|
||||
private AVIMConversationQuery(AVIMConversationQuery 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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the instance.
|
||||
/// </summary>
|
||||
/// <returns>The instance.</returns>
|
||||
/// <param name="where">Where.</param>
|
||||
/// <param name="replacementOrderBy">Replacement order by.</param>
|
||||
/// <param name="thenBy">Then by.</param>
|
||||
/// <param name="skip">Skip.</param>
|
||||
/// <param name="limit">Limit.</param>
|
||||
/// <param name="includes">Includes.</param>
|
||||
/// <param name="selectedKeys">Selected keys.</param>
|
||||
/// <param name="redirectClassNameForKey">Redirect class name for key.</param>
|
||||
public override AVIMConversationQuery 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)
|
||||
{
|
||||
var rtn = new AVIMConversationQuery(this, where, replacementOrderBy, thenBy, skip, limit, includes);
|
||||
rtn.CurrentClient = this.CurrentClient;
|
||||
rtn.compact = this.compact;
|
||||
rtn.withLastMessageRefreshed = this.withLastMessageRefreshed;
|
||||
return rtn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Withs the last message refreshed.
|
||||
/// </summary>
|
||||
/// <returns>The last message refreshed.</returns>
|
||||
/// <param name="enabled">If set to <c>true</c> enabled.</param>
|
||||
public AVIMConversationQuery WithLastMessageRefreshed(bool enabled)
|
||||
{
|
||||
this.withLastMessageRefreshed = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AVIMConversationQuery Compact(bool enabled)
|
||||
{
|
||||
this.compact = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
internal ConversationCommand GenerateQueryCommand()
|
||||
{
|
||||
var cmd = new ConversationCommand();
|
||||
|
||||
var queryParameters = this.BuildParameters(false);
|
||||
if (queryParameters != null)
|
||||
{
|
||||
if (queryParameters.Keys.Contains("where"))
|
||||
cmd.Where(queryParameters["where"]);
|
||||
|
||||
if (queryParameters.Keys.Contains("skip"))
|
||||
cmd.Skip(int.Parse(queryParameters["skip"].ToString()));
|
||||
|
||||
if (queryParameters.Keys.Contains("limit"))
|
||||
cmd.Limit(int.Parse(queryParameters["limit"].ToString()));
|
||||
|
||||
if (queryParameters.Keys.Contains("sort"))
|
||||
cmd.Sort(queryParameters["order"].ToString());
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public override Task<int> CountAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var convCmd = this.GenerateQueryCommand();
|
||||
convCmd.Count();
|
||||
convCmd.Limit(0);
|
||||
var cmd = convCmd.Option("query");
|
||||
return CurrentClient.RunCommandAsync(convCmd).OnSuccess(t =>
|
||||
{
|
||||
var result = t.Result.Item2;
|
||||
|
||||
if (result.ContainsKey("count"))
|
||||
{
|
||||
return int.Parse(result["count"].ToString());
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 查找符合条件的对话
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public override Task<IEnumerable<AVIMConversation>> FindAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var convCmd = this.GenerateQueryCommand().Option("query");
|
||||
return CurrentClient.RunCommandAsync(convCmd).OnSuccess(t =>
|
||||
{
|
||||
var result = t.Result.Item2;
|
||||
|
||||
IList<AVIMConversation> rtn = new List<AVIMConversation>();
|
||||
var conList = result["results"] as IList<object>;
|
||||
if (conList != null)
|
||||
{
|
||||
foreach (var c in conList)
|
||||
{
|
||||
var cData = c as IDictionary<string, object>;
|
||||
if (cData != null)
|
||||
{
|
||||
var con = AVIMConversation.CreateWithData(cData, CurrentClient);
|
||||
rtn.Add(con);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rtn.AsEnumerable();
|
||||
});
|
||||
}
|
||||
|
||||
public override Task<AVIMConversation> FirstAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return this.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public override Task<AVIMConversation> FirstOrDefaultAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var firstQuery = this.Limit(1);
|
||||
return firstQuery.FindAsync().OnSuccess(t =>
|
||||
{
|
||||
return t.Result.FirstOrDefault();
|
||||
});
|
||||
}
|
||||
|
||||
public override Task<AVIMConversation> GetAsync(string objectId, CancellationToken cancellationToken)
|
||||
{
|
||||
var idQuery = this.WhereEqualTo("objectId", objectId);
|
||||
return idQuery.FirstAsync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,248 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// AVIMM essage pager.
|
||||
/// </summary>
|
||||
public class AVIMMessagePager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the query.
|
||||
/// </summary>
|
||||
/// <value>The query.</value>
|
||||
public AVIMMessageQuery Query { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the page.
|
||||
/// </summary>
|
||||
/// <value>The size of the page.</value>
|
||||
public int PageSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return Query.Limit;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
Query.Limit = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current message identifier flag.
|
||||
/// </summary>
|
||||
/// <value>The current message identifier flag.</value>
|
||||
public string CurrentMessageIdFlag
|
||||
{
|
||||
get
|
||||
{
|
||||
return Query.StartMessageId;
|
||||
}
|
||||
private set
|
||||
{
|
||||
Query.StartMessageId = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current date time flag.
|
||||
/// </summary>
|
||||
/// <value>The current date time flag.</value>
|
||||
public DateTime CurrentDateTimeFlag
|
||||
{
|
||||
get
|
||||
{
|
||||
return Query.From;
|
||||
}
|
||||
private set
|
||||
{
|
||||
Query.From = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal AVIMMessagePager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMMessagePager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="conversation">Conversation.</param>
|
||||
public AVIMMessagePager(AVIMConversation conversation)
|
||||
: this()
|
||||
{
|
||||
Query = conversation.GetMessageQuery();
|
||||
PageSize = 20;
|
||||
CurrentDateTimeFlag = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the size of the page.
|
||||
/// </summary>
|
||||
/// <returns>The page size.</returns>
|
||||
/// <param name="pageSize">Page size.</param>
|
||||
public AVIMMessagePager SetPageSize(int pageSize)
|
||||
{
|
||||
PageSize = pageSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Previouses the async.
|
||||
/// </summary>
|
||||
/// <returns>The async.</returns>
|
||||
public Task<IEnumerable<IAVIMMessage>> PreviousAsync()
|
||||
{
|
||||
return Query.FindAsync().OnSuccess(t =>
|
||||
{
|
||||
var headerMessage = t.Result.FirstOrDefault();
|
||||
if (headerMessage != null)
|
||||
{
|
||||
CurrentMessageIdFlag = headerMessage.Id;
|
||||
CurrentDateTimeFlag = headerMessage.ServerTimestamp.ToDateTime();
|
||||
}
|
||||
return t.Result;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// from previous to lastest.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<IEnumerable<IAVIMMessage>> NextAsync()
|
||||
{
|
||||
return Query.ReverseFindAsync().OnSuccess(t =>
|
||||
{
|
||||
var tailMessage = t.Result.LastOrDefault();
|
||||
if (tailMessage != null)
|
||||
{
|
||||
CurrentMessageIdFlag = tailMessage.Id;
|
||||
CurrentDateTimeFlag = tailMessage.ServerTimestamp.ToDateTime();
|
||||
}
|
||||
return t.Result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// history message interator.
|
||||
/// </summary>
|
||||
public class AVIMMessageQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the convsersation.
|
||||
/// </summary>
|
||||
/// <value>The convsersation.</value>
|
||||
public AVIMConversation Convsersation { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the limit.
|
||||
/// </summary>
|
||||
/// <value>The limit.</value>
|
||||
public int Limit { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets from.
|
||||
/// </summary>
|
||||
/// <value>From.</value>
|
||||
public DateTime From { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets to.
|
||||
/// </summary>
|
||||
/// <value>To.</value>
|
||||
public DateTime To { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the end message identifier.
|
||||
/// </summary>
|
||||
/// <value>The end message identifier.</value>
|
||||
public string EndMessageId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the start message identifier.
|
||||
/// </summary>
|
||||
/// <value>The start message identifier.</value>
|
||||
public string StartMessageId { get; set; }
|
||||
|
||||
|
||||
internal AVIMMessageQuery()
|
||||
{
|
||||
Limit = 20;
|
||||
From = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMMessageQuery"/> class.
|
||||
/// </summary>
|
||||
/// <param name="conversation">Conversation.</param>
|
||||
public AVIMMessageQuery(AVIMConversation conversation)
|
||||
: this()
|
||||
{
|
||||
Convsersation = conversation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the limit.
|
||||
/// </summary>
|
||||
/// <returns>The limit.</returns>
|
||||
/// <param name="limit">Limit.</param>
|
||||
public AVIMMessageQuery SetLimit(int limit)
|
||||
{
|
||||
Limit = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// from lastest to previous.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<IEnumerable<IAVIMMessage>> FindAsync()
|
||||
{
|
||||
return FindAsync<IAVIMMessage>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// from lastest to previous.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<IEnumerable<IAVIMMessage>> ReverseFindAsync()
|
||||
{
|
||||
return ReverseFindAsync<IAVIMMessage>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the async.
|
||||
/// </summary>
|
||||
/// <returns>The async.</returns>
|
||||
/// <param name="reverse">set direction to reverse,it means query direct is from old to new.</param>
|
||||
/// <typeparam name="T">The 1st type parameter.</typeparam>
|
||||
public Task<IEnumerable<T>> FindAsync<T>(bool reverse = false)
|
||||
where T : IAVIMMessage
|
||||
{
|
||||
return Convsersation.QueryMessageAsync<T>(
|
||||
beforeTimeStampPoint: From,
|
||||
afterTimeStampPoint: To,
|
||||
limit: Limit,
|
||||
afterMessageId: EndMessageId,
|
||||
beforeMessageId: StartMessageId,
|
||||
direction: reverse ? 0 : 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// from previous to lastest.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Task<IEnumerable<T>> ReverseFindAsync<T>()
|
||||
where T : IAVIMMessage
|
||||
{
|
||||
return FindAsync<T>(true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class AVIMEventArgs : EventArgs
|
||||
{
|
||||
public AVIMEventArgs()
|
||||
{
|
||||
|
||||
}
|
||||
public AVIMException.ErrorCode ErrorCode { get; internal set; }
|
||||
/// <summary>
|
||||
/// LeanCloud 服务端发往客户端消息通知
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class AVIMDisconnectEventArgs : EventArgs
|
||||
{
|
||||
public int Code { get; private set; }
|
||||
|
||||
public string Reason { get; private set; }
|
||||
|
||||
public string Detail { get; private set; }
|
||||
|
||||
public AVIMDisconnectEventArgs()
|
||||
{
|
||||
|
||||
}
|
||||
public AVIMDisconnectEventArgs(int _code, string _reason, string _detail)
|
||||
{
|
||||
this.Code = _code;
|
||||
this.Reason = _reason;
|
||||
this.Detail = _detail;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始重连之后触发正在重连的事件通知,提供给监听者的事件参数
|
||||
/// </summary>
|
||||
public class AVIMReconnectingEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否由 SDK 内部机制启动的自动重连
|
||||
/// </summary>
|
||||
public bool IsAuto { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重连的 client Id
|
||||
/// </summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重连时使用的 SessionToken
|
||||
/// </summary>
|
||||
public string SessionToken { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重连成功之后的事件回调
|
||||
/// </summary>
|
||||
public class AVIMReconnectedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否由 SDK 内部机制启动的自动重连
|
||||
/// </summary>
|
||||
public bool IsAuto { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重连的 client Id
|
||||
/// </summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重连时使用的 SessionToken
|
||||
/// </summary>
|
||||
public string SessionToken { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重连失败之后的事件回调参数
|
||||
/// </summary>
|
||||
public class AVIMReconnectFailedArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否由 SDK 内部机制启动的自动重连
|
||||
/// </summary>
|
||||
public bool IsAuto { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重连的 client Id
|
||||
/// </summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重连时使用的 SessionToken
|
||||
/// </summary>
|
||||
public string SessionToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 失败的原因
|
||||
/// 0. 客户端网络断开
|
||||
/// 1. sessionToken 错误或者失效,需要重新创建 client
|
||||
/// </summary>
|
||||
public int FailedCode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AVIMM essage event arguments.
|
||||
/// </summary>
|
||||
public class AVIMMessageEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMMessageEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="iMessage">I message.</param>
|
||||
public AVIMMessageEventArgs(IAVIMMessage iMessage)
|
||||
{
|
||||
Message = iMessage;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the message.
|
||||
/// </summary>
|
||||
/// <value>The message.</value>
|
||||
public IAVIMMessage Message { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AVIMMessage event arguments.
|
||||
/// </summary>
|
||||
public class AVIMMessagePatchEventArgs : EventArgs
|
||||
{
|
||||
public AVIMMessagePatchEventArgs(IAVIMMessage message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message.
|
||||
/// </summary>
|
||||
/// <value>The message.</value>
|
||||
public IAVIMMessage Message { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AVIMT ext message event arguments.
|
||||
/// </summary>
|
||||
public class AVIMTextMessageEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMTextMessageEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="raw">Raw.</param>
|
||||
public AVIMTextMessageEventArgs(AVIMTextMessage raw)
|
||||
{
|
||||
TextMessage = raw;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the text message.
|
||||
/// </summary>
|
||||
/// <value>The text message.</value>
|
||||
public AVIMTextMessage TextMessage { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当对话中有人加入时,触发 <seealso cref="AVIMMembersJoinListener.OnMembersJoined"/> 时所携带的事件参数
|
||||
/// </summary>
|
||||
public class AVIMOnMembersJoinedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 加入到对话的 Client Id(s)
|
||||
/// </summary>
|
||||
public IEnumerable<string> JoinedMembers { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 邀请的操作人
|
||||
/// </summary>
|
||||
public string InvitedBy { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 此次操作针对的对话 Id
|
||||
/// </summary>
|
||||
public string ConversationId { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当对话中有人加入时,触发 AVIMMembersJoinListener<seealso cref="AVIMMembersLeftListener.OnMembersLeft"/> 时所携带的事件参数
|
||||
/// </summary>
|
||||
public class AVIMOnMembersLeftEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 离开对话的 Client Id(s)
|
||||
/// </summary>
|
||||
public IEnumerable<string> LeftMembers { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 踢出的操作人
|
||||
/// </summary>
|
||||
public string KickedBy { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 此次操作针对的对话 Id
|
||||
/// </summary>
|
||||
public string ConversationId { get; internal set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 当前用户被邀请加入到对话
|
||||
/// </summary>
|
||||
public class AVIMOnInvitedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 邀请的操作人
|
||||
/// </summary>
|
||||
public string InvitedBy { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 此次操作针对的对话 Id
|
||||
/// </summary>
|
||||
public string ConversationId { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户被他人从对话中踢出
|
||||
/// </summary>
|
||||
public class AVIMOnKickedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 踢出的操作人
|
||||
/// </summary>
|
||||
public string KickedBy { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 此次操作针对的对话 Id
|
||||
/// </summary>
|
||||
public string ConversationId { get; internal set; }
|
||||
}
|
||||
|
||||
public class AVIMSessionClosedEventArgs : EventArgs
|
||||
{
|
||||
public int Code { get; internal set; }
|
||||
|
||||
public string Reason { get; internal set; }
|
||||
|
||||
public string Detail { get; internal set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 实时通信的异常
|
||||
/// </summary>
|
||||
public class AVIMException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 错误代码
|
||||
/// </summary>
|
||||
public enum ErrorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Error code indicating that an unknown error or an error unrelated to LeanCloud
|
||||
/// occurred.
|
||||
/// </summary>
|
||||
OtherCause = -1,
|
||||
|
||||
/// <summary>
|
||||
/// 服务端错误
|
||||
/// </summary>
|
||||
FromServer = 4000,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// websocket 连接非正常关闭,通常见于路由器配置对长连接限制的情况。SDK 会自动重连,无需人工干预。
|
||||
/// </summary>
|
||||
UnknownError = 1006,
|
||||
|
||||
/// <summary>
|
||||
/// 应用不存在或应用禁用了实时通信服务
|
||||
/// </summary>
|
||||
APP_NOT_AVAILABLE = 4100,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 登录签名验证失败
|
||||
/// </summary>
|
||||
SIGNATURE_FAILED = 4102,
|
||||
|
||||
/// <summary>
|
||||
/// Client ClientId 格式错误,超过 64 个字符。
|
||||
/// </summary>
|
||||
INVALID_LOGIN = 4103,
|
||||
|
||||
/// <summary>
|
||||
/// Session 没有打开就发送消息,或执行其他操作。常见的错误场景是调用 open session 后直接发送消息,正确的用法是在 Session 打开的回调里执行。
|
||||
/// </summary>
|
||||
SESSION_REQUIRED = 4105,
|
||||
|
||||
/// <summary>
|
||||
/// 读超时,服务器端长时间没有收到客户端的数据,切断连接。SDK 包装了心跳包的机制,出现此错误通常是网络问题。SDK 会自动重连,无需人工干预。
|
||||
/// </summary>
|
||||
READ_TIMEOUT = 4107,
|
||||
|
||||
/// <summary>
|
||||
/// 登录超时,连接后长时间没有完成 session open。通常是登录被拒绝等原因,出现此问题可能是使用方式有误,可以 创建工单,由我们技术顾问来给出建议。
|
||||
/// </summary>
|
||||
LOGIN_TIMEOUT = 4108,
|
||||
|
||||
/// <summary>
|
||||
/// 包过长。消息大小超过 5 KB,请缩短消息或者拆分消息。
|
||||
/// </summary>
|
||||
FRAME_TOO_LONG = 4109,
|
||||
|
||||
/// <summary>
|
||||
/// 设置安全域名后,当前登录的域名与安全域名不符合。
|
||||
/// </summary>
|
||||
INVALID_ORIGIN = 4110,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 设置单设备登录后,客户端被其他设备挤下线。
|
||||
/// </summary>
|
||||
SESSION_CONFLICT = 4111,
|
||||
|
||||
/// <summary>
|
||||
/// 应用容量超限,当天登录用户数已经超过应用设定的最大值。
|
||||
/// </summary>
|
||||
APP_QUOTA_EXCEEDED = 4113,
|
||||
|
||||
/// <summary>
|
||||
/// 客户端发送的序列化数据服务器端无法解析。
|
||||
/// </summary>
|
||||
UNPARSEABLE_RAW_MESSAGE = 4114,
|
||||
|
||||
/// <summary>
|
||||
/// 客户端被 REST API 管理接口强制下线。
|
||||
/// </summary>
|
||||
KICKED_BY_APP = 4115,
|
||||
|
||||
/// <summary>
|
||||
/// 应用单位时间内发送消息量超过限制,消息被拒绝。
|
||||
/// </summary>
|
||||
MESSAGE_SENT_QUOTA_EXCEEDED = 4116,
|
||||
|
||||
/// <summary>
|
||||
/// 服务器内部错误,如果反复出现请收集相关线索并 创建工单,我们会尽快解决。
|
||||
/// </summary>
|
||||
INTERNAL_ERROR = 4200,
|
||||
|
||||
/// <summary>
|
||||
/// 通过 API 发送消息超时
|
||||
/// </summary>
|
||||
SEND_MESSAGE_TIMEOUT = 4201,
|
||||
|
||||
/// <summary>
|
||||
/// 上游 API 调用异常,请查看报错信息了解错误详情
|
||||
/// </summary>
|
||||
CONVERSATION_API_FAILED = 4301,
|
||||
|
||||
/// <summary>
|
||||
/// 对话相关操作签名错误
|
||||
/// </summary>
|
||||
CONVERSATION_SIGNATURE_FAILED = 4302,
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息,或邀请等操作对应的对话不存在。
|
||||
/// </summary>
|
||||
CONVERSATION_NOT_FOUND = 4303,
|
||||
|
||||
/// <summary>
|
||||
/// 对话成员已满,不能再添加。
|
||||
/// </summary>
|
||||
CONVERSATION_FULL = 4304,
|
||||
|
||||
/// <summary>
|
||||
/// 对话操作被应用的云引擎 Hook 拒绝
|
||||
/// </summary>
|
||||
CONVERSATION_REJECTED_BY_APP = 4305,
|
||||
|
||||
/// <summary>
|
||||
/// 更新对话操作失败
|
||||
/// </summary>
|
||||
CONVERSATION_UPDATE_FAILED = 4306,
|
||||
|
||||
/// <summary>
|
||||
/// 该对话为只读,不能更新或增删成员。
|
||||
/// </summary>
|
||||
CONVERSATION_READ_ONLY = 4307,
|
||||
|
||||
/// <summary>
|
||||
/// 该对话禁止当前用户发送消息
|
||||
/// </summary>
|
||||
CONVERSATION_NOT_ALLOWED = 4308,
|
||||
|
||||
/// <summary>
|
||||
/// 更新对话的请求被拒绝,当前用户不在对话中
|
||||
/// </summary>
|
||||
CONVERSATION_UPDATE_REJECT = 4309,
|
||||
|
||||
/// <summary>
|
||||
/// 查询对话失败,常见于慢查询导致的超时或受其他慢查询导致的数据库响应慢
|
||||
/// </summary>
|
||||
CONVERSATION_QUERY_FAILED = 4310,
|
||||
|
||||
/// <summary>
|
||||
/// 拉取对话消息记录失败,常见与超时的情况
|
||||
/// </summary>
|
||||
CONVERSATION_LOG_FAILED = 4311,
|
||||
|
||||
/// <summary>
|
||||
/// 拉去对话消息记录被拒绝,当前用户不再对话中
|
||||
/// </summary>
|
||||
CONVERSATION_LOG_REJECT = 4312,
|
||||
|
||||
/// <summary>
|
||||
/// 该功能仅对系统对话有效
|
||||
/// </summary>
|
||||
SYSTEM_CONVERSATION_REQUIRED = 4313,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 该功能仅对普通对话有效。
|
||||
/// </summary>
|
||||
NORMAL_CONVERSATION_REQUIRED = 4314,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户被加入此对话的黑名单,无法发送消息。
|
||||
/// </summary>
|
||||
CONVERSATION_BLACKLISTED = 4315,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 该功能仅对暂态对话有效。
|
||||
/// </summary>
|
||||
TRANSIENT_CONVERSATION_REQUIRED = 4316,
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息的对话不存在,或当前用户不在对话中
|
||||
/// </summary>
|
||||
INVALID_MESSAGING_TARGET = 4401,
|
||||
|
||||
/// <summary>
|
||||
/// 发送的消息被应用的云引擎 Hook 拒绝
|
||||
/// </summary>
|
||||
MESSAGE_REJECTED_BY_APP = 4402,
|
||||
|
||||
/// <summary>
|
||||
/// 客户端无法通过 WebSocket 发送数据包
|
||||
/// </summary>
|
||||
CAN_NOT_EXCUTE_COMMAND = 1002,
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 用户云代码返回的错误码
|
||||
/// </summary>
|
||||
public int AppCode { get; private set; }
|
||||
|
||||
|
||||
internal AVIMException(ErrorCode code, string message, Exception cause = null)
|
||||
: base(message, cause)
|
||||
{
|
||||
this.Code = code;
|
||||
}
|
||||
|
||||
internal AVIMException(int code, int appCode, string message, Exception cause = null)
|
||||
: this((ErrorCode)code, message, cause)
|
||||
{
|
||||
this.AppCode = appCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The LeanCloud error code associated with the exception.
|
||||
/// </summary>
|
||||
public ErrorCode Code { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
using LeanCloud;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 图像消息
|
||||
/// </summary>
|
||||
[AVIMMessageClassName("_AVIMImageMessage")]
|
||||
[AVIMTypedMessageTypeInt(-2)]
|
||||
public class AVIMImageMessage : AVIMFileMessage
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// File message.
|
||||
/// </summary>
|
||||
[AVIMMessageClassName("_AVIMFileMessage")]
|
||||
[AVIMTypedMessageTypeInt(-6)]
|
||||
public class AVIMFileMessage : AVIMMessageDecorator
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMFileMessage"/> class.
|
||||
/// </summary>
|
||||
public AVIMFileMessage()
|
||||
: base(new AVIMTypedMessage())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMFileMessage"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">Message.</param>
|
||||
public AVIMFileMessage(AVIMTypedMessage message)
|
||||
: base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the file.
|
||||
/// </summary>
|
||||
/// <value>The file.</value>
|
||||
public AVFile File { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Froms the URL.
|
||||
/// </summary>
|
||||
/// <returns>The URL.</returns>
|
||||
/// <param name="url">URL.</param>
|
||||
/// <typeparam name="T">The 1st type parameter.</typeparam>
|
||||
public static T FromUrl<T>(string url) where T : AVIMFileMessage, new()
|
||||
{
|
||||
return FromUrl<T>(string.Empty.Random(8), url, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From the URL.
|
||||
/// </summary>
|
||||
/// <returns>The URL.</returns>
|
||||
/// <param name="fileName">File name.</param>
|
||||
/// <param name="externalUrl">External URL.</param>
|
||||
/// <param name="textTitle">Text title.</param>
|
||||
/// <param name="customAttributes">Custom attributes.</param>
|
||||
/// <typeparam name="T">The 1st type parameter.</typeparam>
|
||||
public static T FromUrl<T>(string fileName, string externalUrl, string textTitle, IDictionary<string, object> customAttributes = null) where T : AVIMFileMessage, new()
|
||||
{
|
||||
T message = new T();
|
||||
message.File = new AVFile(fileName, externalUrl);
|
||||
message.TextContent = textTitle;
|
||||
message.MergeCustomAttributes(customAttributes);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// From the stream.
|
||||
/// </summary>
|
||||
/// <returns>The stream.</returns>
|
||||
/// <param name="fileName">File name.</param>
|
||||
/// <param name="data">Data.</param>
|
||||
/// <param name="mimeType">MIME type.</param>
|
||||
/// <param name="textTitle">Text title.</param>
|
||||
/// <param name="metaData">Meta data.</param>
|
||||
/// <param name="customAttributes">Custom attributes.</param>
|
||||
/// <typeparam name="T">The 1st type parameter.</typeparam>
|
||||
public static T FromStream<T>(string fileName, Stream data, string mimeType, string textTitle, IDictionary<string, object> metaData, IDictionary<string, object> customAttributes = null) where T : AVIMFileMessage, new()
|
||||
{
|
||||
T message = new T();
|
||||
message.File = new AVFile(fileName, data, mimeType, metaData);
|
||||
message.TextContent = textTitle;
|
||||
message.MergeCustomAttributes(customAttributes);
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the decorator.
|
||||
/// </summary>
|
||||
/// <returns>The decorator.</returns>
|
||||
public override IDictionary<string, object> EncodeDecorator()
|
||||
{
|
||||
if (File.Url == null) throw new InvalidOperationException("File.Url can not be null before it can be sent.");
|
||||
File.MetaData["name"] = File.Name;
|
||||
File.MetaData["format"] = File.MimeType;
|
||||
var fileData = new Dictionary<string, object>
|
||||
{
|
||||
{ "url", File.Url.ToString()},
|
||||
{ "objId", File.ObjectId },
|
||||
{ "metaData", File.MetaData }
|
||||
};
|
||||
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ AVIMProtocol.LCFILE, fileData }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize the specified msgStr.
|
||||
/// </summary>
|
||||
/// <returns>The deserialize.</returns>
|
||||
/// <param name="msgStr">Message string.</param>
|
||||
public override IAVIMMessage Deserialize(string msgStr)
|
||||
{
|
||||
var msg = Json.Parse(msgStr) as IDictionary<string, object>;
|
||||
var fileData = msg[AVIMProtocol.LCFILE] as IDictionary<string, object>;
|
||||
string mimeType = null;
|
||||
string url = null;
|
||||
string name = null;
|
||||
string objId = null;
|
||||
IDictionary<string, object> metaData = null;
|
||||
if (fileData != null)
|
||||
{
|
||||
object metaDataObj = null;
|
||||
|
||||
if (fileData.TryGetValue("metaData", out metaDataObj))
|
||||
{
|
||||
metaData = metaDataObj as IDictionary<string, object>;
|
||||
object fileNameObj = null;
|
||||
if (metaData != null)
|
||||
{
|
||||
if (metaData.TryGetValue("name", out fileNameObj))
|
||||
{
|
||||
name = fileNameObj.ToString();
|
||||
}
|
||||
}
|
||||
object mimeTypeObj = null;
|
||||
if (metaData != null)
|
||||
{
|
||||
if (metaData.TryGetValue("format", out mimeTypeObj))
|
||||
{
|
||||
if (mimeTypeObj != null)
|
||||
mimeType = mimeTypeObj.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object objIdObj = null;
|
||||
if (fileData.TryGetValue("objId", out objIdObj))
|
||||
{
|
||||
if (objIdObj != null)
|
||||
objId = objIdObj.ToString();
|
||||
}
|
||||
|
||||
object urlObj = null;
|
||||
if (fileData.TryGetValue("url", out urlObj))
|
||||
{
|
||||
url = urlObj.ToString();
|
||||
}
|
||||
File = AVFile.CreateWithData(objId, name, url, metaData);
|
||||
}
|
||||
|
||||
return base.Deserialize(msgStr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Location message.
|
||||
/// </summary>
|
||||
[AVIMMessageClassName("_AVIMMessageClassName")]
|
||||
[AVIMTypedMessageTypeInt(-5)]
|
||||
public class AVIMLocationMessage : AVIMMessageDecorator
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMLocationMessage"/> class.
|
||||
/// </summary>
|
||||
public AVIMLocationMessage()
|
||||
: base(new AVIMTypedMessage())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the location.
|
||||
/// </summary>
|
||||
/// <value>The location.</value>
|
||||
public AVGeoPoint Location { get; set; }
|
||||
|
||||
public AVIMLocationMessage(AVGeoPoint location)
|
||||
: this()
|
||||
{
|
||||
Location = location;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the decorator.
|
||||
/// </summary>
|
||||
/// <returns>The decorator.</returns>
|
||||
public override IDictionary<string, object> EncodeDecorator()
|
||||
{
|
||||
var locationData = new Dictionary<string, object>
|
||||
{
|
||||
{ "longitude", Location.Longitude},
|
||||
{ "latitude", Location.Latitude }
|
||||
};
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ AVIMProtocol.LCLOC, locationData }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize the specified msgStr.
|
||||
/// </summary>
|
||||
/// <returns>The deserialize.</returns>
|
||||
/// <param name="msgStr">Message string.</param>
|
||||
public override IAVIMMessage Deserialize(string msgStr)
|
||||
{
|
||||
var msg = Json.Parse(msgStr) as IDictionary<string, object>;
|
||||
var locationData = msg[AVIMProtocol.LCLOC] as IDictionary<string, object>;
|
||||
if (locationData != null)
|
||||
{
|
||||
Location = new AVGeoPoint(double.Parse(locationData["latitude"].ToString()), double.Parse(locationData["longitude"].ToString()));
|
||||
}
|
||||
return base.Deserialize(msgStr);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud;
|
||||
using System.Reflection;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 实时消息的核心基类,它是 Json schema 消息的父类
|
||||
/// </summary>
|
||||
[AVIMMessageClassName("_AVIMMessage")]
|
||||
public class AVIMMessage : IAVIMMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认的构造函数
|
||||
/// </summary>
|
||||
public AVIMMessage()
|
||||
{
|
||||
|
||||
}
|
||||
internal readonly object mutex = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 对话的Id
|
||||
/// </summary>
|
||||
public string ConversationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息的 ClientId
|
||||
/// </summary>
|
||||
public string FromClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息在全局的唯一标识Id
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务器端的时间戳
|
||||
/// </summary>
|
||||
public long ServerTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content.
|
||||
/// </summary>
|
||||
/// <value>The content.</value>
|
||||
public string Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 对方收到消息的时间戳,如果是多人聊天,那以最早收到消息的人回发的 ACK 为准
|
||||
/// </summary>
|
||||
public long RcpTimestamp { get; set; }
|
||||
|
||||
public long UpdatedAt { get; set; }
|
||||
|
||||
internal string cmdId { get; set; }
|
||||
|
||||
#region
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this <see cref="T:LeanCloud.Realtime.IAVIMMessage"/> mention all.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if mention all; otherwise, <c>false</c>.</value>
|
||||
public bool MentionAll { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mention list.
|
||||
/// </summary>
|
||||
/// <value>The mention list.</value>
|
||||
public IEnumerable<string> MentionList { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region register convertor for custom typed message
|
||||
|
||||
/// <summary>
|
||||
/// Serialize this message.
|
||||
/// </summary>
|
||||
/// <returns>The serialize.</returns>
|
||||
public virtual string Serialize()
|
||||
{
|
||||
return Content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate the specified msgStr.
|
||||
/// </summary>
|
||||
/// <returns>The validate.</returns>
|
||||
/// <param name="msgStr">Message string.</param>
|
||||
public virtual bool Validate(string msgStr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize the specified msgStr to message subclass instance
|
||||
/// </summary>
|
||||
/// <returns>The deserialize.</returns>
|
||||
/// <param name="msgStr">Message string.</param>
|
||||
public virtual IAVIMMessage Deserialize(string msgStr)
|
||||
{
|
||||
Content = msgStr;
|
||||
return this;
|
||||
}
|
||||
|
||||
internal virtual MessageCommand BeforeSend(MessageCommand cmd)
|
||||
{
|
||||
return cmd;
|
||||
}
|
||||
|
||||
internal static IAVIMMessage CopyMetaData(IAVIMMessage srcMsg, IAVIMMessage desMsg) {
|
||||
if (srcMsg == null)
|
||||
return desMsg;
|
||||
|
||||
desMsg.ConversationId = srcMsg.ConversationId;
|
||||
desMsg.FromClientId = srcMsg.FromClientId;
|
||||
desMsg.Id = srcMsg.Id;
|
||||
desMsg.ServerTimestamp = srcMsg.ServerTimestamp;
|
||||
desMsg.RcpTimestamp = srcMsg.RcpTimestamp;
|
||||
desMsg.UpdatedAt = srcMsg.UpdatedAt;
|
||||
return desMsg;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 消息的发送选项
|
||||
/// </summary>
|
||||
public struct AVIMSendOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否需要送达回执
|
||||
/// </summary>
|
||||
public bool Receipt;
|
||||
/// <summary>
|
||||
/// 是否是暂态消息,暂态消息不返回送达回执(ack),不保留离线消息,不触发离线推送
|
||||
/// </summary>
|
||||
public bool Transient;
|
||||
/// <summary>
|
||||
/// 消息的优先级,默认是1,可选值还有 2|3
|
||||
/// </summary>
|
||||
public int Priority;
|
||||
/// <summary>
|
||||
/// 是否为 Will 类型的消息,这条消息会被缓存在服务端,一旦当前客户端下线,这条消息会被发送到对话内的其他成员
|
||||
/// </summary>
|
||||
public bool Will;
|
||||
|
||||
/// <summary>
|
||||
/// 如果消息的接收者已经下线了,这个字段的内容就会被离线推送到接收者
|
||||
///<remarks>例如,一张图片消息的离线消息内容可以类似于:[您收到一条图片消息,点击查看] 这样的推送内容,参照微信的做法</remarks>
|
||||
/// </summary>
|
||||
public IDictionary<string, object> PushData;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class AVIMMessageClassNameAttribute: Attribute
|
||||
{
|
||||
public AVIMMessageClassNameAttribute(string className)
|
||||
{
|
||||
this.ClassName = className;
|
||||
}
|
||||
public string ClassName { get; private set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class AVIMMessageFieldNameAttribute: Attribute
|
||||
{
|
||||
public AVIMMessageFieldNameAttribute(string fieldName)
|
||||
{
|
||||
FieldName = fieldName;
|
||||
}
|
||||
|
||||
public string FieldName { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
using LeanCloud.Realtime.Internal;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认的消息监听器,它主要承担的指责是回执的发送与用户自定义的监听器不冲突
|
||||
/// </summary>
|
||||
public class AVIMMessageListener : IAVIMListener
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认的 AVIMMessageListener 只会监听 direct 协议,但是并不会触发针对消息类型的判断的监听器
|
||||
/// </summary>
|
||||
public AVIMMessageListener()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Protocols the hook.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c>, if hook was protocoled, <c>false</c> otherwise.</returns>
|
||||
/// <param name="notice">Notice.</param>
|
||||
public virtual bool ProtocolHook(AVIMNotice notice)
|
||||
{
|
||||
if (notice.CommandName != "direct") return false;
|
||||
if (notice.RawData.ContainsKey("offline")) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private EventHandler<AVIMMessageEventArgs> m_OnMessageReceived;
|
||||
/// <summary>
|
||||
/// 接收到聊天消息的事件通知
|
||||
/// </summary>
|
||||
public event EventHandler<AVIMMessageEventArgs> OnMessageReceived
|
||||
{
|
||||
add
|
||||
{
|
||||
m_OnMessageReceived += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
m_OnMessageReceived -= value;
|
||||
}
|
||||
}
|
||||
internal virtual void OnMessage(AVIMNotice notice)
|
||||
{
|
||||
if (m_OnMessageReceived != null)
|
||||
{
|
||||
var msgStr = notice.RawData["msg"].ToString();
|
||||
var iMessage = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, notice.RawData);
|
||||
//var messageNotice = new AVIMMessageNotice(notice.RawData);
|
||||
//var messaegObj = AVIMMessage.Create(messageNotice);
|
||||
var args = new AVIMMessageEventArgs(iMessage);
|
||||
m_OnMessageReceived.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ons the notice received.
|
||||
/// </summary>
|
||||
/// <param name="notice">Notice.</param>
|
||||
public virtual void OnNoticeReceived(AVIMNotice notice)
|
||||
{
|
||||
this.OnMessage(notice);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文本消息监听器
|
||||
/// </summary>
|
||||
public class AVIMTextMessageListener : IAVIMListener
|
||||
{
|
||||
/// <summary>
|
||||
/// 构建默认的文本消息监听器
|
||||
/// </summary>
|
||||
public AVIMTextMessageListener()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建文本消息监听者
|
||||
/// </summary>
|
||||
/// <param name="textMessageReceived"></param>
|
||||
public AVIMTextMessageListener(Action<AVIMTextMessage> textMessageReceived)
|
||||
{
|
||||
OnTextMessageReceived += (sender, textMessage) =>
|
||||
{
|
||||
textMessageReceived(textMessage.TextMessage);
|
||||
};
|
||||
}
|
||||
|
||||
private EventHandler<AVIMTextMessageEventArgs> m_OnTextMessageReceived;
|
||||
public event EventHandler<AVIMTextMessageEventArgs> OnTextMessageReceived
|
||||
{
|
||||
add
|
||||
{
|
||||
m_OnTextMessageReceived += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
m_OnTextMessageReceived -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool ProtocolHook(AVIMNotice notice)
|
||||
{
|
||||
if (notice.CommandName != "direct") return false;
|
||||
try
|
||||
{
|
||||
var msg = Json.Parse(notice.RawData["msg"].ToString()) as IDictionary<string, object>;
|
||||
if (!msg.Keys.Contains(AVIMProtocol.LCTYPE)) return false;
|
||||
var typInt = 0;
|
||||
int.TryParse(msg[AVIMProtocol.LCTYPE].ToString(), out typInt);
|
||||
if (typInt != -1) return false;
|
||||
return true;
|
||||
}
|
||||
catch(ArgumentException)
|
||||
{
|
||||
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public virtual void OnNoticeReceived(AVIMNotice notice)
|
||||
{
|
||||
if (m_OnTextMessageReceived != null)
|
||||
{
|
||||
var textMessage = new AVIMTextMessage();
|
||||
textMessage.Deserialize(notice.RawData["msg"].ToString());
|
||||
m_OnTextMessageReceived(this, new AVIMTextMessageEventArgs(textMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using LeanCloud;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public interface IAVIMNotice
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="estimatedData"></param>
|
||||
/// <returns></returns>
|
||||
AVIMNotice Restore(IDictionary<string, object> estimatedData);
|
||||
}
|
||||
/// <summary>
|
||||
/// 从服务端接受到的通知
|
||||
/// <para>通知泛指消息,对话信息变更(例如加人和被踢等),服务器的 ACK,消息回执等</para>
|
||||
/// </summary>
|
||||
public class AVIMNotice : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMNotice"/> class.
|
||||
/// </summary>
|
||||
public AVIMNotice()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the command.
|
||||
/// </summary>
|
||||
public readonly string CommandName;
|
||||
public readonly IDictionary<string, object> RawData;
|
||||
public AVIMNotice(IDictionary<string, object> estimatedData)
|
||||
{
|
||||
this.RawData = estimatedData;
|
||||
this.CommandName = estimatedData["cmd"].ToString();
|
||||
}
|
||||
|
||||
public static bool IsValidLeanCloudProtocol(IDictionary<string, object> estimatedData)
|
||||
{
|
||||
if (estimatedData == null) return false;
|
||||
if (estimatedData.Count == 0) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Realtime {
|
||||
/// <summary>
|
||||
/// 撤回消息
|
||||
/// </summary>
|
||||
[AVIMMessageClassName("_AVIMRecalledMessagee")]
|
||||
[AVIMTypedMessageTypeInt(-127)]
|
||||
public class AVIMRecalledMessage : AVIMTypedMessage {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 签名
|
||||
/// </summary>
|
||||
public class AVIMSignature
|
||||
{
|
||||
/// <summary>
|
||||
/// 经过 SHA1 以及相关操作参数计算出来的加密字符串
|
||||
/// </summary>
|
||||
public string SignatureContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务端时间戳
|
||||
/// </summary>
|
||||
public long Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 随机字符串
|
||||
/// </summary>
|
||||
public string Nonce { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个签名
|
||||
/// </summary>
|
||||
/// <param name="s"></param>
|
||||
/// <param name="t"></param>
|
||||
/// <param name="n"></param>
|
||||
public AVIMSignature(string s,long t,string n)
|
||||
{
|
||||
this.Nonce = n;
|
||||
this.SignatureContent = s;
|
||||
this.Timestamp = t;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Temporary conversation.
|
||||
/// </summary>
|
||||
public class AVIMTemporaryConversation : AVIMConversation
|
||||
{
|
||||
public DateTime ExpiredAt
|
||||
{
|
||||
get
|
||||
{
|
||||
if (expiredAt == null)
|
||||
return DateTime.Now.AddDays(1);
|
||||
return expiredAt.Value;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
expiredAt = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal AVIMTemporaryConversation(long ttl)
|
||||
: base(isTemporary: true)
|
||||
{
|
||||
this.expiredAt = DateTime.Now.AddDays(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
using LeanCloud.Realtime.Internal;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 纯文本信息
|
||||
/// </summary>
|
||||
[AVIMMessageClassName("_AVIMTextMessage")]
|
||||
[AVIMTypedMessageTypeInt(-1)]
|
||||
public class AVIMTextMessage : AVIMTypedMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 构建一个文本信息 <see cref="AVIMTextMessage"/> class.
|
||||
/// </summary>
|
||||
public AVIMTextMessage()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文本类型标记
|
||||
/// </summary>
|
||||
[Obsolete("LCType is deprecated, please use AVIMTypedMessageTypeInt instead.")]
|
||||
[AVIMMessageFieldName("_lctype")]
|
||||
public int LCType
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个纯文本信息
|
||||
/// </summary>
|
||||
/// <param name="textContent"></param>
|
||||
public AVIMTextMessage(string textContent)
|
||||
: this()
|
||||
{
|
||||
TextContent = textContent;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[AVIMMessageClassName("_AVIMTypedMessage")]
|
||||
[AVIMTypedMessageTypeInt(0)]
|
||||
public class AVIMTypedMessage : AVIMMessage, IEnumerable<KeyValuePair<string, object>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMTypedMessage"/> class.
|
||||
/// </summary>
|
||||
public AVIMTypedMessage()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文本内容
|
||||
/// </summary>
|
||||
[AVIMMessageFieldName("_lctext")]
|
||||
public string TextContent
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
private IDictionary<string, object> estimatedData = new Dictionary<string, object>();
|
||||
/// <summary>
|
||||
/// Serialize this instance.
|
||||
/// </summary>
|
||||
/// <returns>The serialize.</returns>
|
||||
public override string Serialize()
|
||||
{
|
||||
var result = Encode();
|
||||
var resultStr = Json.Encode(result);
|
||||
this.Content = resultStr;
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode this instance.
|
||||
/// </summary>
|
||||
/// <returns>The encode.</returns>
|
||||
public virtual IDictionary<string, object> Encode()
|
||||
{
|
||||
var result = AVRealtime.FreeStyleMessageClassingController.EncodeProperties(this);
|
||||
var encodedAttrs = PointerOrLocalIdEncoder.Instance.Encode(estimatedData);
|
||||
result[AVIMProtocol.LCATTRS] = estimatedData;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate the specified msgStr.
|
||||
/// </summary>
|
||||
/// <returns>The validate.</returns>
|
||||
/// <param name="msgStr">Message string.</param>
|
||||
public override bool Validate(string msgStr)
|
||||
{
|
||||
try
|
||||
{
|
||||
var msg = Json.Parse(msgStr) as IDictionary<string, object>;
|
||||
return msg.ContainsKey(AVIMProtocol.LCTYPE);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize the specified msgStr.
|
||||
/// </summary>
|
||||
/// <returns>The deserialize.</returns>
|
||||
/// <param name="msgStr">Message string.</param>
|
||||
public override IAVIMMessage Deserialize(string msgStr)
|
||||
{
|
||||
var msg = Json.Parse(msgStr) as IDictionary<string, object>;
|
||||
var className = AVRealtime.FreeStyleMessageClassingController.GetClassName(this.GetType());
|
||||
var PropertyMappings = AVRealtime.FreeStyleMessageClassingController.GetPropertyMappings(className);
|
||||
var messageFieldProperties = PropertyMappings.Where(prop => msg.ContainsKey(prop.Value))
|
||||
.Select(prop => Tuple.Create(ReflectionHelpers.GetProperty(this.GetType(), prop.Key), msg[prop.Value]));
|
||||
|
||||
foreach (var property in messageFieldProperties)
|
||||
{
|
||||
property.Item1.SetValue(this, property.Item2, null);
|
||||
}
|
||||
|
||||
if (msg.ContainsKey(AVIMProtocol.LCATTRS))
|
||||
{
|
||||
object attrs = msg[AVIMProtocol.LCATTRS];
|
||||
this.estimatedData = AVDecoder.Instance.Decode(attrs) as Dictionary<string, object>;
|
||||
}
|
||||
|
||||
return base.Deserialize(msgStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="T:LeanCloud.Realtime.AVIMTypedMessage"/> with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key.</param>
|
||||
public virtual object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (estimatedData.TryGetValue(key, out object value)) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
set
|
||||
{
|
||||
estimatedData[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges the custom attributes.
|
||||
/// </summary>
|
||||
/// <param name="customAttributes">Custom attributes.</param>
|
||||
public void MergeCustomAttributes(IDictionary<string, object> customAttributes)
|
||||
{
|
||||
this.estimatedData = this.estimatedData.Merge(customAttributes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enumerator.
|
||||
/// </summary>
|
||||
/// <returns>The enumerator.</returns>
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
return estimatedData.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<KeyValuePair<string, object>>)this).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AVIMMessage decorator.
|
||||
/// </summary>
|
||||
public abstract class AVIMMessageDecorator : AVIMTypedMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the message.
|
||||
/// </summary>
|
||||
/// <value>The message.</value>
|
||||
public AVIMTypedMessage Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:LeanCloud.Realtime.AVIMMessageDecorator"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">Message.</param>
|
||||
protected AVIMMessageDecorator(AVIMTypedMessage message)
|
||||
{
|
||||
this.Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content of the message.
|
||||
/// </summary>
|
||||
/// <value>The content of the message.</value>
|
||||
public virtual IDictionary<string, object> MessageContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the decorated.
|
||||
/// </summary>
|
||||
/// <returns>The decorated.</returns>
|
||||
public virtual IDictionary<string, object> EncodeDecorated()
|
||||
{
|
||||
return Message.Encode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode this instance.
|
||||
/// </summary>
|
||||
/// <returns>The encode.</returns>
|
||||
public override IDictionary<string, object> Encode()
|
||||
{
|
||||
var decoratedMessageEncoded = EncodeDecorated();
|
||||
var selfEncoded = base.Encode();
|
||||
var decoratoEncoded = this.EncodeDecorator();
|
||||
var resultEncoed = decoratedMessageEncoded.Merge(selfEncoded).Merge(decoratoEncoded);
|
||||
return resultEncoed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the decorator.
|
||||
/// </summary>
|
||||
/// <returns>The decorator.</returns>
|
||||
public abstract IDictionary<string, object> EncodeDecorator();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
|
||||
public sealed class AVIMTypedMessageTypeIntAttribute : Attribute
|
||||
{
|
||||
public AVIMTypedMessageTypeIntAttribute(int typeInt)
|
||||
{
|
||||
this.TypeInteger = typeInt;
|
||||
}
|
||||
|
||||
public int TypeInteger { get; private set; }
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket 监听服务端事件通知的接口
|
||||
/// 所有基于协议层的事件监听都需要实现这个接口,然后自定义监听协议。
|
||||
/// </summary>
|
||||
public interface IAVIMListener
|
||||
{
|
||||
/// <summary>
|
||||
/// 监听的协议 Hook
|
||||
/// 例如,消息的协议是 direct 命令,因此消息监听需要判断 <see cref="AVIMNotice.CommandName"/> == "direct" 才可以调用
|
||||
/// </summary>
|
||||
/// <param name="notice"></param>
|
||||
/// <returns></returns>
|
||||
bool ProtocolHook(AVIMNotice notice);
|
||||
|
||||
///// <summary>
|
||||
///// 如果 <see cref="IAVIMListener.HookFilter"/> 返回 true,则会启动 NoticeAction 里面的回调逻辑
|
||||
///// </summary>
|
||||
//Action<AVIMNotice> NoticeAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 如果 <see cref="IAVIMListener.OnNoticeReceived(AVIMNotice)"/> 返回 true,则会启动 NoticeAction 里面的回调逻辑
|
||||
/// </summary>
|
||||
void OnNoticeReceived(AVIMNotice notice);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息接口
|
||||
/// <para>所有消息必须实现这个接口</para>
|
||||
/// </summary>
|
||||
public interface IAVIMMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Serialize this instance.
|
||||
/// </summary>
|
||||
/// <returns>The serialize.</returns>
|
||||
string Serialize();
|
||||
|
||||
/// <summary>
|
||||
/// Validate the specified msgStr.
|
||||
/// </summary>
|
||||
/// <returns>The validate.</returns>
|
||||
/// <param name="msgStr">Message string.</param>
|
||||
bool Validate(string msgStr);
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize the specified msgStr.
|
||||
/// </summary>
|
||||
/// <returns>The deserialize.</returns>
|
||||
/// <param name="msgStr">Message string.</param>
|
||||
IAVIMMessage Deserialize(string msgStr);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the conversation identifier.
|
||||
/// </summary>
|
||||
/// <value>The conversation identifier.</value>
|
||||
string ConversationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets from client identifier.
|
||||
/// </summary>
|
||||
/// <value>From client identifier.</value>
|
||||
string FromClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier.
|
||||
/// </summary>
|
||||
/// <value>The identifier.</value>
|
||||
string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the server timestamp.
|
||||
/// </summary>
|
||||
/// <value>The server timestamp.</value>
|
||||
long ServerTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rcp timestamp.
|
||||
/// </summary>
|
||||
/// <value>The rcp timestamp.</value>
|
||||
long RcpTimestamp { get; set; }
|
||||
|
||||
long UpdatedAt { get; set; }
|
||||
|
||||
|
||||
#region mention features.
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this <see cref="T:LeanCloud.Realtime.IAVIMMessage"/> mention all.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if mention all; otherwise, <c>false</c>.</value>
|
||||
bool MentionAll { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mention list.
|
||||
/// </summary>
|
||||
/// <value>The mention list.</value>
|
||||
IEnumerable<string> MentionList { get; set; }
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
public interface ISQLStorage
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
using LeanCloud;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话操作的签名类型,比如讲一个 client id 加入到对话中
|
||||
/// <see cref="https://leancloud.cn/docs/realtime_v2.html#群组功能的签名"/>
|
||||
/// </summary>
|
||||
public enum ConversationSignatureAction
|
||||
{
|
||||
/// <summary>
|
||||
/// add 加入对话和邀请对方加入对话
|
||||
/// </summary>
|
||||
Add,
|
||||
/// <summary>
|
||||
/// remove 当前 client Id 离开对话和将其他人踢出对话
|
||||
/// </summary>
|
||||
Remove
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="https://leancloud.cn/docs/realtime_v2.html#群组功能的签名"/>
|
||||
/// </summary>
|
||||
public interface ISignatureFactory
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 构建登录签名
|
||||
/// </summary>
|
||||
/// <param name="clientId">需要登录到云端服务器的 client Id</param>
|
||||
/// <returns></returns>
|
||||
Task<AVIMSignature> CreateConnectSignature(string clientId);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="clientId"></param>
|
||||
/// <param name="targetIds"></param>
|
||||
/// <returns></returns>
|
||||
Task<AVIMSignature> CreateStartConversationSignature(string clientId, IEnumerable<string> targetIds);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="conversationId"></param>
|
||||
/// <param name="clientId"></param>
|
||||
/// <param name="targetIds"></param>
|
||||
/// <param name="action">需要签名的操作</param>
|
||||
/// <returns></returns>
|
||||
Task<AVIMSignature> CreateConversationSignature(string conversationId, string clientId, IEnumerable<string> targetIds, ConversationSignatureAction action);
|
||||
}
|
||||
|
||||
internal class DefaulSiganatureFactory : ISignatureFactory
|
||||
{
|
||||
Task<AVIMSignature> ISignatureFactory.CreateConnectSignature(string clientId)
|
||||
{
|
||||
return Task.FromResult<AVIMSignature>(null);
|
||||
}
|
||||
|
||||
Task<AVIMSignature> ISignatureFactory.CreateConversationSignature(string conversationId, string clientId, IEnumerable<string> targetIds, ConversationSignatureAction action)
|
||||
{
|
||||
return Task.FromResult<AVIMSignature>(null);
|
||||
}
|
||||
|
||||
Task<AVIMSignature> ISignatureFactory.CreateStartConversationSignature(string clientId, IEnumerable<string> targetIds)
|
||||
{
|
||||
return Task.FromResult<AVIMSignature>(null);
|
||||
}
|
||||
}
|
||||
|
||||
public class LeanEngineSignatureFactory : ISignatureFactory
|
||||
{
|
||||
public Task<AVIMSignature> CreateConnectSignature(string clientId)
|
||||
{
|
||||
var data = new Dictionary<string, object>();
|
||||
data.Add("client_id", clientId);
|
||||
return AVCloud.CallFunctionAsync<IDictionary<string, object>>("connect", data).OnSuccess(_ =>
|
||||
{
|
||||
var jsonData = _.Result;
|
||||
var s = jsonData["signature"].ToString();
|
||||
var n = jsonData["nonce"].ToString();
|
||||
var t = long.Parse(jsonData["timestamp"].ToString());
|
||||
var signature = new AVIMSignature(s, t, n);
|
||||
return signature;
|
||||
});
|
||||
}
|
||||
|
||||
public Task<AVIMSignature> CreateStartConversationSignature(string clientId, IEnumerable<string> targetIds)
|
||||
{
|
||||
var data = new Dictionary<string, object>();
|
||||
data.Add("client_id", clientId);
|
||||
data.Add("members", targetIds.ToList());
|
||||
return AVCloud.CallFunctionAsync<IDictionary<string, object>>("startConversation", data).OnSuccess(_ =>
|
||||
{
|
||||
var jsonData = _.Result;
|
||||
var s = jsonData["signature"].ToString();
|
||||
var n = jsonData["nonce"].ToString();
|
||||
var t = long.Parse(jsonData["timestamp"].ToString());
|
||||
var signature = new AVIMSignature(s, t, n);
|
||||
return signature;
|
||||
});
|
||||
}
|
||||
|
||||
public Task<AVIMSignature> CreateConversationSignature(string conversationId, string clientId, IEnumerable<string> targetIds, ConversationSignatureAction action)
|
||||
{
|
||||
var actionList = new string[] { "invite", "kick" };
|
||||
var data = new Dictionary<string, object>();
|
||||
data.Add("client_id", clientId);
|
||||
data.Add("conv_id", conversationId);
|
||||
data.Add("members", targetIds.ToList());
|
||||
data.Add("action", actionList[(int)action]);
|
||||
return AVCloud.CallFunctionAsync<IDictionary<string, object>>("oprateConversation", data).OnSuccess(_ =>
|
||||
{
|
||||
var jsonData = _.Result;
|
||||
var s = jsonData["signature"].ToString();
|
||||
var n = jsonData["nonce"].ToString();
|
||||
var t = long.Parse(jsonData["timestamp"].ToString());
|
||||
var signature = new AVIMSignature(s, t, n);
|
||||
return signature;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话中成员变动的事件参数,它提供被操作的对话(Conversation),操作类型(AffectedType)
|
||||
/// 受影响的成员列表(AffectedMembers)
|
||||
/// </summary>
|
||||
public class AVIMOnMembersChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 本次成员变动中被操作的具体对话(AVIMConversation)的对象
|
||||
/// </summary>
|
||||
public AVIMConversation Conversation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变动的类型
|
||||
/// </summary>
|
||||
public AVIMConversationEventType AffectedType { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 受影响的成员的 Client Ids
|
||||
/// </summary>
|
||||
public IList<string> AffectedMembers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作人的 Client ClientId
|
||||
/// </summary>
|
||||
public string Oprator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作的时间,已转化为本地时间
|
||||
/// </summary>
|
||||
public DateTime OpratedTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 变动的类型,目前支持如下:
|
||||
/// 1、Joined:当前 Client 主动加入,案例:当 A 主动加入到对话,A 将收到 Joined 事件响应,其余的成员收到 MembersJoined 事件响应
|
||||
/// 2、Left:当前 Client 主动退出,案例:当 A 从对话中退出,A 将收到 Left 事件响应,其余的成员收到 MembersLeft 事件响应
|
||||
/// 3、MembersJoined:某个成员加入(区别于Joined和Kicked),案例:当 A 把 B 加入到对话中,C 将收到 MembersJoined 事件响应
|
||||
/// 4、MembersLeft:某个成员加入(区别于Joined和Kicked),案例:当 A 把 B 从对话中剔除,C 将收到 MembersLeft 事件响应
|
||||
/// 5、Invited:当前 Client 被邀请加入,案例:当 A 被 B 邀请加入到对话中,A 将收到 Invited 事件响应,B 将收到 Joined ,其余的成员收到 MembersJoined 事件响应
|
||||
/// 6、Kicked:当前 Client 被剔除,案例:当 A 被 B 从对话中剔除,A 将收到 Kicked 事件响应,B 将收到 Left,其余的成员收到 MembersLeft 事件响应
|
||||
/// </summary>
|
||||
public enum AVIMConversationEventType
|
||||
{
|
||||
/// <summary>
|
||||
/// 自身主动加入
|
||||
/// </summary>
|
||||
Joined = 1,
|
||||
/// <summary>
|
||||
/// 自身主动离开
|
||||
/// </summary>
|
||||
Left,
|
||||
/// <summary>
|
||||
/// 他人加入
|
||||
/// </summary>
|
||||
MembersJoined,
|
||||
/// <summary>
|
||||
/// 他人离开
|
||||
/// </summary>
|
||||
MembersLeft,
|
||||
/// <summary>
|
||||
/// 自身被邀请加入
|
||||
/// </summary>
|
||||
Invited,
|
||||
/// <summary>
|
||||
/// 自身被他人剔除
|
||||
/// </summary>
|
||||
Kicked
|
||||
}
|
||||
|
||||
#region AVIMMembersJoinListener
|
||||
//when Members joined or invited by member,this listener will invoke AVIMOnMembersJoinedEventArgs event.
|
||||
/// <summary>
|
||||
/// 对话中有成员加入的时候,在改对话中的其他成员都会触发 <see cref="AVIMMembersJoinListener.OnMembersJoined"/> 事件
|
||||
/// </summary>
|
||||
public class AVIMMembersJoinListener : IAVIMListener
|
||||
{
|
||||
|
||||
private EventHandler<AVIMOnMembersJoinedEventArgs> m_OnMembersJoined;
|
||||
/// <summary>
|
||||
/// 有成员加入到对话时,触发的事件
|
||||
/// </summary>
|
||||
public event EventHandler<AVIMOnMembersJoinedEventArgs> OnMembersJoined
|
||||
{
|
||||
add
|
||||
{
|
||||
m_OnMembersJoined += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
m_OnMembersJoined -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnNoticeReceived(AVIMNotice notice)
|
||||
{
|
||||
if (m_OnMembersJoined != null)
|
||||
{
|
||||
var joinedMembers = AVDecoder.Instance.DecodeList<string>(notice.RawData["m"]);
|
||||
var ivitedBy = notice.RawData["initBy"].ToString();
|
||||
var conersationId = notice.RawData["cid"].ToString();
|
||||
var args = new AVIMOnMembersJoinedEventArgs()
|
||||
{
|
||||
ConversationId = conersationId,
|
||||
InvitedBy = ivitedBy,
|
||||
JoinedMembers = joinedMembers
|
||||
};
|
||||
m_OnMembersJoined.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool ProtocolHook(AVIMNotice notice)
|
||||
{
|
||||
if (notice.CommandName != "conv") return false;
|
||||
if (!notice.RawData.ContainsKey("op")) return false;
|
||||
var op = notice.RawData["op"].ToString();
|
||||
if (!op.Equals("members-joined")) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AVIMMembersLeftListener
|
||||
// when Members left or kicked by member,this listener will invoke AVIMOnMembersJoinedEventArgs event.
|
||||
/// <summary>
|
||||
/// 对话中有成员加入的时候,在改对话中的其他成员都会触发 <seealso cref="AVIMMembersLeftListener.OnMembersLeft"/>OnMembersJoined 事件
|
||||
/// </summary>
|
||||
public class AVIMMembersLeftListener : IAVIMListener
|
||||
{
|
||||
private EventHandler<AVIMOnMembersLeftEventArgs> m_OnMembersLeft;
|
||||
/// <summary>
|
||||
/// 有成员加入到对话时,触发的事件
|
||||
/// </summary>
|
||||
public event EventHandler<AVIMOnMembersLeftEventArgs> OnMembersLeft
|
||||
{
|
||||
add
|
||||
{
|
||||
m_OnMembersLeft += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
m_OnMembersLeft -= value;
|
||||
}
|
||||
}
|
||||
public virtual void OnNoticeReceived(AVIMNotice notice)
|
||||
{
|
||||
if (m_OnMembersLeft != null)
|
||||
{
|
||||
var leftMembers = AVDecoder.Instance.DecodeList<string>(notice.RawData["m"]);
|
||||
var kickedBy = notice.RawData["initBy"].ToString();
|
||||
var conersationId = notice.RawData["cid"].ToString();
|
||||
var args = new AVIMOnMembersLeftEventArgs()
|
||||
{
|
||||
ConversationId = conersationId,
|
||||
KickedBy = kickedBy,
|
||||
LeftMembers = leftMembers
|
||||
};
|
||||
m_OnMembersLeft.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool ProtocolHook(AVIMNotice notice)
|
||||
{
|
||||
if (notice.CommandName != "conv") return false;
|
||||
if (!notice.RawData.ContainsKey("op")) return false;
|
||||
var op = notice.RawData["op"].ToString();
|
||||
if (!op.Equals("members-left")) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AVIMInvitedListener
|
||||
public class AVIMInvitedListener : IAVIMListener
|
||||
{
|
||||
private EventHandler<AVIMOnInvitedEventArgs> m_OnInvited;
|
||||
public event EventHandler<AVIMOnInvitedEventArgs> OnInvited {
|
||||
add {
|
||||
m_OnInvited += value;
|
||||
} remove {
|
||||
m_OnInvited -= value;
|
||||
}
|
||||
}
|
||||
public void OnNoticeReceived(AVIMNotice notice)
|
||||
{
|
||||
if (m_OnInvited != null)
|
||||
{
|
||||
var ivitedBy = notice.RawData["initBy"].ToString();
|
||||
var conersationId = notice.RawData["cid"].ToString();
|
||||
var args = new AVIMOnInvitedEventArgs()
|
||||
{
|
||||
ConversationId = conersationId,
|
||||
InvitedBy = ivitedBy,
|
||||
};
|
||||
m_OnInvited.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ProtocolHook(AVIMNotice notice)
|
||||
{
|
||||
if (notice.CommandName != "conv") return false;
|
||||
if (!notice.RawData.ContainsKey("op")) return false;
|
||||
var op = notice.RawData["op"].ToString();
|
||||
if (!op.Equals("joined")) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AVIMKickedListener
|
||||
public class AVIMKickedListener : IAVIMListener
|
||||
{
|
||||
private EventHandler<AVIMOnKickedEventArgs> m_OnKicked;
|
||||
public event EventHandler<AVIMOnKickedEventArgs> OnKicked {
|
||||
add {
|
||||
m_OnKicked += value;
|
||||
} remove {
|
||||
m_OnKicked -= value;
|
||||
}
|
||||
}
|
||||
public void OnNoticeReceived(AVIMNotice notice)
|
||||
{
|
||||
if (m_OnKicked != null)
|
||||
{
|
||||
var kickcdBy = notice.RawData["initBy"].ToString();
|
||||
var conersationId = notice.RawData["cid"].ToString();
|
||||
var args = new AVIMOnKickedEventArgs()
|
||||
{
|
||||
ConversationId = conersationId,
|
||||
KickedBy = kickcdBy,
|
||||
};
|
||||
m_OnKicked.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ProtocolHook(AVIMNotice notice)
|
||||
{
|
||||
if (notice.CommandName != "conv") return false;
|
||||
if (!notice.RawData.ContainsKey("op")) return false;
|
||||
var op = notice.RawData["op"].ToString();
|
||||
if (!op.Equals("left")) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
internal class ConversationUnreadListener : IAVIMListener
|
||||
{
|
||||
internal class UnreadConversationNotice : IEqualityComparer<UnreadConversationNotice>
|
||||
{
|
||||
internal readonly object mutex = new object();
|
||||
internal IAVIMMessage LastUnreadMessage { get; set; }
|
||||
internal string ConvId { get; set; }
|
||||
internal int UnreadCount { get; set; }
|
||||
|
||||
public bool Equals(UnreadConversationNotice x, UnreadConversationNotice y)
|
||||
{
|
||||
return x.ConvId == y.ConvId;
|
||||
}
|
||||
|
||||
public int GetHashCode(UnreadConversationNotice obj)
|
||||
{
|
||||
return obj.ConvId.GetHashCode();
|
||||
}
|
||||
|
||||
internal void AutomicIncrement()
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
UnreadCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
internal static readonly object sMutex = new object();
|
||||
internal static long NotifTime;
|
||||
internal static HashSet<UnreadConversationNotice> UnreadConversations;
|
||||
static ConversationUnreadListener()
|
||||
{
|
||||
UnreadConversations = new HashSet<UnreadConversationNotice>(new UnreadConversationNotice());
|
||||
NotifTime = DateTime.Now.ToUnixTimeStamp();
|
||||
}
|
||||
|
||||
internal static void UpdateNotice(IAVIMMessage message)
|
||||
{
|
||||
lock (sMutex)
|
||||
{
|
||||
var convValidators = UnreadConversations.Where(c => c.ConvId == message.ConversationId);
|
||||
if (convValidators != null)
|
||||
{
|
||||
if (convValidators.Count() > 0)
|
||||
{
|
||||
var currentNotice = convValidators.FirstOrDefault();
|
||||
currentNotice.AutomicIncrement();
|
||||
currentNotice.LastUnreadMessage = message;
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentThread = new UnreadConversationNotice();
|
||||
currentThread.ConvId = message.ConversationId;
|
||||
currentThread.LastUnreadMessage = message;
|
||||
currentThread.AutomicIncrement();
|
||||
UnreadConversations.Add(currentThread);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
internal static void ClearUnread(string convId)
|
||||
{
|
||||
UnreadConversations.Remove(Get(convId));
|
||||
}
|
||||
internal static IEnumerable<string> FindAllConvIds()
|
||||
{
|
||||
lock (sMutex)
|
||||
{
|
||||
return ConversationUnreadListener.UnreadConversations.Select(c => c.ConvId);
|
||||
}
|
||||
}
|
||||
|
||||
internal static UnreadConversationNotice Get(string convId)
|
||||
{
|
||||
lock (sMutex)
|
||||
{
|
||||
var unreadValidator = ConversationUnreadListener.UnreadConversations.Where(c => c.ConvId == convId);
|
||||
if (unreadValidator != null)
|
||||
{
|
||||
if (unreadValidator.Count() > 0)
|
||||
{
|
||||
var notice = unreadValidator.FirstOrDefault();
|
||||
return notice;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnNoticeReceived(AVIMNotice notice)
|
||||
{
|
||||
lock (sMutex)
|
||||
{
|
||||
if (notice.RawData.ContainsKey("convs"))
|
||||
{
|
||||
var unreadRawData = notice.RawData["convs"] as List<object>;
|
||||
if (notice.RawData.ContainsKey("notifTime"))
|
||||
{
|
||||
long.TryParse(notice.RawData["notifTime"].ToString(), out NotifTime);
|
||||
}
|
||||
foreach (var data in unreadRawData)
|
||||
{
|
||||
var dataMap = data as IDictionary<string, object>;
|
||||
if (dataMap != null)
|
||||
{
|
||||
var convId = dataMap["cid"].ToString();
|
||||
var ucn = Get(convId);
|
||||
if (ucn == null) ucn = new UnreadConversationNotice();
|
||||
|
||||
ucn.ConvId = convId;
|
||||
var unreadCount = 0;
|
||||
Int32.TryParse(dataMap["unread"].ToString(), out unreadCount);
|
||||
ucn.UnreadCount = unreadCount;
|
||||
|
||||
#region restore last message for the conversation
|
||||
if (dataMap.ContainsKey("data"))
|
||||
{
|
||||
var msgStr = dataMap["data"].ToString();
|
||||
var messageObj = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, dataMap);
|
||||
ucn.LastUnreadMessage = messageObj;
|
||||
}
|
||||
|
||||
UnreadConversations.Add(ucn);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ProtocolHook(AVIMNotice notice)
|
||||
{
|
||||
return notice.CommandName == "unread";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Realtime {
|
||||
/// <summary>
|
||||
/// 强制被踢下线处理
|
||||
/// </summary>
|
||||
internal class GoAwayListener : IAVIMListener {
|
||||
Action onGoAway;
|
||||
|
||||
public event Action OnGoAway {
|
||||
add {
|
||||
onGoAway += value;
|
||||
}
|
||||
remove {
|
||||
onGoAway -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnNoticeReceived(AVIMNotice notice) {
|
||||
// TODO 退出并清理路由缓存
|
||||
onGoAway?.Invoke();
|
||||
}
|
||||
|
||||
public bool ProtocolHook(AVIMNotice notice) {
|
||||
return notice.CommandName == "goaway";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
internal delegate void OnMessagePatch(IEnumerable<IAVIMMessage> messages);
|
||||
internal class MessagePatchListener : IAVIMListener
|
||||
{
|
||||
public OnMessagePatch OnReceived { get; set; }
|
||||
|
||||
public void OnNoticeReceived(AVIMNotice notice)
|
||||
{
|
||||
ICollection<IAVIMMessage> patchedMessages = new List<IAVIMMessage>();
|
||||
var msgObjs = notice.RawData["patches"] as IList<object>;
|
||||
if (msgObjs != null)
|
||||
{
|
||||
foreach (var msgObj in msgObjs)
|
||||
{
|
||||
var msgData = msgObj as IDictionary<string, object>;
|
||||
if (msgData != null)
|
||||
{
|
||||
var msgStr = msgData["data"] as string;
|
||||
var message = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, msgData);
|
||||
patchedMessages.Add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (OnReceived != null)
|
||||
{
|
||||
if (patchedMessages.Count > 0)
|
||||
{
|
||||
this.OnReceived(patchedMessages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ProtocolHook(AVIMNotice notice)
|
||||
{
|
||||
if (notice.CommandName != "patch") return false;
|
||||
if (!notice.RawData.ContainsKey("op")) return false;
|
||||
if (notice.RawData["op"].ToString() != "modify") return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using LeanCloud.Storage.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
internal class OfflineMessageListener : IAVIMListener
|
||||
{
|
||||
private EventHandler<AVIMMessageEventArgs> m_OnOfflineMessageReceived;
|
||||
public event EventHandler<AVIMMessageEventArgs> OnOfflineMessageReceived
|
||||
{
|
||||
add
|
||||
{
|
||||
m_OnOfflineMessageReceived += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
m_OnOfflineMessageReceived -= value;
|
||||
}
|
||||
}
|
||||
public void OnNoticeReceived(AVIMNotice notice)
|
||||
{
|
||||
if (m_OnOfflineMessageReceived != null)
|
||||
{
|
||||
var msgStr = notice.RawData["msg"].ToString();
|
||||
var iMessage = AVRealtime.FreeStyleMessageClassingController.Instantiate(msgStr, notice.RawData);
|
||||
var args = new AVIMMessageEventArgs(iMessage);
|
||||
m_OnOfflineMessageReceived.Invoke(this, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public bool ProtocolHook(AVIMNotice notice)
|
||||
{
|
||||
if (notice.CommandName != "direct") return false;
|
||||
if (!notice.RawData.ContainsKey("offline")) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
internal class SessionListener : IAVIMListener
|
||||
{
|
||||
private Action<int, string, string> _onSessionClosed;
|
||||
public event Action<int, string, string> OnSessionClosed
|
||||
{
|
||||
add
|
||||
{
|
||||
_onSessionClosed += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
_onSessionClosed -= value;
|
||||
}
|
||||
}
|
||||
public void OnNoticeReceived(AVIMNotice notice)
|
||||
{
|
||||
var code = 0;
|
||||
if (notice.RawData.ContainsKey("code"))
|
||||
{
|
||||
int.TryParse(notice.RawData["code"].ToString(), out code);
|
||||
}
|
||||
|
||||
var reason = "";
|
||||
if (notice.RawData.ContainsKey("reason"))
|
||||
{
|
||||
reason = notice.RawData["reason"].ToString();
|
||||
}
|
||||
|
||||
var detail = "";
|
||||
if (notice.RawData.ContainsKey("detail"))
|
||||
{
|
||||
detail = notice.RawData["detail"].ToString();
|
||||
}
|
||||
|
||||
if (_onSessionClosed != null)
|
||||
{
|
||||
_onSessionClosed(code, reason, detail);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ProtocolHook(AVIMNotice notice)
|
||||
{
|
||||
if (notice.CommandName != "session") return false;
|
||||
if (!notice.RawData.ContainsKey("op")) return false;
|
||||
if (notice.RawData.ContainsKey("i")) return false;
|
||||
if (notice.RawData["op"].ToString() != "closed") return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Realtime.Internal;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace LeanCloud.Realtime
|
||||
{
|
||||
/// <summary>
|
||||
/// AVRealtime initialize behavior.
|
||||
/// </summary>
|
||||
public class AVRealtimeBehavior : AVInitializeBehaviour
|
||||
{
|
||||
public string RTMRouter = null;
|
||||
|
||||
//void OnApplicationQuit()
|
||||
//{
|
||||
// if (AVRealtime.clients != null)
|
||||
// {
|
||||
// foreach (var item in AVRealtime.clients)
|
||||
// {
|
||||
// item.Value.LinkedRealtime.LogOut();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//private void Update()
|
||||
//{
|
||||
// var available = Application.internetReachability != NetworkReachability.NotReachable;
|
||||
// if (AVRealtime.clients != null)
|
||||
// foreach (var item in AVRealtime.clients)
|
||||
// {
|
||||
// if (item.Value != null)
|
||||
// if (item.Value.LinkedRealtime != null)
|
||||
// item.Value.LinkedRealtime.InvokeNetworkState(available);
|
||||
// }
|
||||
//}
|
||||
|
||||
//public override void Awake()
|
||||
//{
|
||||
// base.Awake();
|
||||
// StartCoroutine(InitializeRealtime());
|
||||
// gameObject.name = "AVRealtimeInitializeBehavior";
|
||||
//}
|
||||
|
||||
//public IEnumerator InitializeRealtime()
|
||||
//{
|
||||
// if (isRealtimeInitialized)
|
||||
// {
|
||||
// yield break;
|
||||
// }
|
||||
// isRealtimeInitialized = true;
|
||||
// yield return FetchRouter();
|
||||
//}
|
||||
|
||||
|
||||
//[SerializeField]
|
||||
//public bool secure;
|
||||
//private static bool isRealtimeInitialized = false;
|
||||
//public string Server;
|
||||
//private IDictionary<string, object> routerState;
|
||||
|
||||
//public IEnumerator FetchRouter()
|
||||
//{
|
||||
// var router = RTMRouter;
|
||||
// if (string.IsNullOrEmpty(router)) {
|
||||
// var state = AVPlugins.Instance.AppRouterController.Get();
|
||||
// router = state.RealtimeRouterServer;
|
||||
// }
|
||||
// var url = string.Format("https://{0}/v1/route?appId={1}", router, applicationID);
|
||||
// if (secure)
|
||||
// {
|
||||
// url += "&secure=1";
|
||||
// }
|
||||
|
||||
// var request = new UnityWebRequest(url);
|
||||
// request.downloadHandler = new DownloadHandlerBuffer();
|
||||
// yield return request.Send();
|
||||
|
||||
// if (request.isError)
|
||||
// {
|
||||
// throw new AVException(AVException.ErrorCode.ConnectionFailed, "can not reach router.", null);
|
||||
// }
|
||||
|
||||
// var result = request.downloadHandler.text;
|
||||
// routerState = Json.Parse(result) as IDictionary<string, object>;
|
||||
// if (routerState.Keys.Count == 0)
|
||||
// {
|
||||
// throw new KeyNotFoundException("Can not get websocket url from server,please check the appId.");
|
||||
// }
|
||||
// var ttl = long.Parse(routerState["ttl"].ToString());
|
||||
// var expire = DateTime.Now.AddSeconds(ttl);
|
||||
// routerState["expire"] = expire.ToUnixTimeStamp(UnixTimeStampUnit.Second);
|
||||
// Server = routerState["server"].ToString();
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,384 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVPlugins : IAVCorePlugins
|
||||
{
|
||||
private static readonly object instanceMutex = new object();
|
||||
private static IAVCorePlugins instance;
|
||||
public static IAVCorePlugins Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (instanceMutex)
|
||||
{
|
||||
instance = instance ?? new AVPlugins();
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (instanceMutex)
|
||||
{
|
||||
instance = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object mutex = new object();
|
||||
|
||||
#region Server Controllers
|
||||
|
||||
private IHttpClient httpClient;
|
||||
private IAppRouterController appRouterController;
|
||||
private IAVCommandRunner commandRunner;
|
||||
private IStorageController storageController;
|
||||
|
||||
private IAVCloudCodeController cloudCodeController;
|
||||
private IAVConfigController configController;
|
||||
private IAVFileController fileController;
|
||||
private IAVObjectController objectController;
|
||||
private IAVQueryController queryController;
|
||||
private IAVSessionController sessionController;
|
||||
private IAVUserController userController;
|
||||
private IObjectSubclassingController subclassingController;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Current Instance Controller
|
||||
|
||||
private IAVCurrentUserController currentUserController;
|
||||
private IInstallationIdController installationIdController;
|
||||
|
||||
#endregion
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
HttpClient = null;
|
||||
AppRouterController = null;
|
||||
CommandRunner = null;
|
||||
StorageController = null;
|
||||
|
||||
CloudCodeController = null;
|
||||
FileController = null;
|
||||
ObjectController = null;
|
||||
SessionController = null;
|
||||
UserController = null;
|
||||
SubclassingController = null;
|
||||
|
||||
CurrentUserController = null;
|
||||
InstallationIdController = null;
|
||||
}
|
||||
}
|
||||
|
||||
public IHttpClient HttpClient
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
httpClient = httpClient ?? new HttpClient();
|
||||
return httpClient;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
httpClient = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAppRouterController AppRouterController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
appRouterController = appRouterController ?? new AppRouterController();
|
||||
return appRouterController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
appRouterController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVCommandRunner CommandRunner
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
commandRunner = commandRunner ?? new AVCommandRunner(HttpClient, InstallationIdController);
|
||||
return commandRunner;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
commandRunner = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !UNITY
|
||||
public IStorageController StorageController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
storageController = storageController ?? new StorageController(AVClient.CurrentConfiguration.ApplicationId);
|
||||
return storageController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
storageController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if UNITY
|
||||
public IStorageController StorageController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
storageController = storageController ?? new StorageController(AVInitializeBehaviour.IsWebPlayer, AVClient.CurrentConfiguration.ApplicationId);
|
||||
return storageController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
storageController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public IAVCloudCodeController CloudCodeController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
cloudCodeController = cloudCodeController ?? new AVCloudCodeController(CommandRunner);
|
||||
return cloudCodeController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
cloudCodeController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVFileController FileController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (AVClient.CurrentConfiguration.RegionValue == 0)
|
||||
fileController = fileController ?? new QiniuFileController(CommandRunner);
|
||||
else if (AVClient.CurrentConfiguration.RegionValue == 2)
|
||||
fileController = fileController ?? new QCloudCosFileController(CommandRunner);
|
||||
else if (AVClient.CurrentConfiguration.RegionValue == 1)
|
||||
fileController = fileController ?? new AWSS3FileController(CommandRunner);
|
||||
|
||||
return fileController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
fileController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVConfigController ConfigController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (configController == null)
|
||||
{
|
||||
configController = new AVConfigController(CommandRunner, StorageController);
|
||||
}
|
||||
return configController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
configController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVObjectController ObjectController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
objectController = objectController ?? new AVObjectController(CommandRunner);
|
||||
return objectController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
objectController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVQueryController QueryController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (queryController == null)
|
||||
{
|
||||
queryController = new AVQueryController(CommandRunner);
|
||||
}
|
||||
return queryController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
queryController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVSessionController SessionController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
sessionController = sessionController ?? new AVSessionController(CommandRunner);
|
||||
return sessionController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
sessionController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVUserController UserController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
userController = userController ?? new AVUserController(CommandRunner);
|
||||
return userController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
userController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IAVCurrentUserController CurrentUserController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
currentUserController = currentUserController ?? new AVCurrentUserController(StorageController);
|
||||
return currentUserController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
currentUserController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IObjectSubclassingController SubclassingController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
if (subclassingController == null)
|
||||
{
|
||||
subclassingController = new ObjectSubclassingController();
|
||||
subclassingController.AddRegisterHook(typeof(AVUser), () => CurrentUserController.ClearFromMemory());
|
||||
}
|
||||
return subclassingController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
subclassingController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IInstallationIdController InstallationIdController
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
installationIdController = installationIdController ?? new InstallationIdController(StorageController);
|
||||
return installationIdController;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
installationIdController = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AppRouterController : IAppRouterController
|
||||
{
|
||||
private AppRouterState currentState;
|
||||
private object mutex = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Get current app's router state
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AppRouterState Get()
|
||||
{
|
||||
if (string.IsNullOrEmpty(AVClient.CurrentConfiguration.ApplicationId))
|
||||
{
|
||||
throw new AVException(AVException.ErrorCode.NotInitialized, "ApplicationId can not be null.");
|
||||
}
|
||||
AppRouterState state;
|
||||
state = AppRouterState.GetInitial(AVClient.CurrentConfiguration.ApplicationId, AVClient.CurrentConfiguration.Region);
|
||||
|
||||
lock (mutex)
|
||||
{
|
||||
if (currentState != null)
|
||||
{
|
||||
if (!currentState.isExpired())
|
||||
state = currentState;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isExpired())
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
state.FetchedAt = DateTime.Now + TimeSpan.FromMinutes(10);
|
||||
}
|
||||
Task.Factory.StartNew(RefreshAsync);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
public Task RefreshAsync()
|
||||
{
|
||||
return QueryAsync(CancellationToken.None).ContinueWith(t =>
|
||||
{
|
||||
if (!t.IsFaulted && !t.IsCanceled)
|
||||
{
|
||||
lock (mutex)
|
||||
{
|
||||
currentState = t.Result;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Task<AppRouterState> QueryAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
string appId = AVClient.CurrentConfiguration.ApplicationId;
|
||||
string url = string.Format("https://app-router.leancloud.cn/2/route?appId={0}", appId);
|
||||
|
||||
return AVClient.HttpGetAsync(new Uri(url)).ContinueWith(t =>
|
||||
{
|
||||
var tcs = new TaskCompletionSource<AppRouterState>();
|
||||
if (t.Result.Item1 != HttpStatusCode.OK)
|
||||
{
|
||||
tcs.SetException(new AVException(AVException.ErrorCode.ConnectionFailed, "can not reach router.", null));
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = Json.Parse(t.Result.Item2) as IDictionary<String, Object>;
|
||||
tcs.SetResult(ParseAppRouterState(result));
|
||||
}
|
||||
return tcs.Task;
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
private static AppRouterState ParseAppRouterState(IDictionary<string, object> jsonObj)
|
||||
{
|
||||
var state = new AppRouterState()
|
||||
{
|
||||
TTL = (int)jsonObj["ttl"],
|
||||
StatsServer = jsonObj["stats_server"] as string,
|
||||
RealtimeRouterServer = jsonObj["rtm_router_server"] as string,
|
||||
PushServer = jsonObj["push_server"] as string,
|
||||
EngineServer = jsonObj["engine_server"] as string,
|
||||
ApiServer = jsonObj["api_server"] as string,
|
||||
Source = "network",
|
||||
};
|
||||
return state;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
currentState = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
using System;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AppRouterState
|
||||
{
|
||||
public long TTL { get; internal set; }
|
||||
public string ApiServer { get; internal set; }
|
||||
public string EngineServer { get; internal set; }
|
||||
public string PushServer { get; internal set; }
|
||||
public string RealtimeRouterServer { get; internal set; }
|
||||
public string StatsServer { get; internal set; }
|
||||
public string Source { get; internal set; }
|
||||
|
||||
public DateTime FetchedAt { get; internal set; }
|
||||
|
||||
private static object mutex = new object();
|
||||
|
||||
public AppRouterState()
|
||||
{
|
||||
FetchedAt = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is this app router state expired.
|
||||
/// </summary>
|
||||
public bool isExpired()
|
||||
{
|
||||
return DateTime.Now > FetchedAt + TimeSpan.FromSeconds(TTL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the initial usable router state
|
||||
/// </summary>
|
||||
/// <param name="appId">Current app's appId</param>
|
||||
/// <param name="region">Current app's region</param>
|
||||
/// <returns>Initial app router state</returns>
|
||||
public static AppRouterState GetInitial(string appId, AVClient.Configuration.AVRegion region)
|
||||
{
|
||||
var regionValue = (int)region;
|
||||
var prefix = appId.Substring(0, 8).ToLower();
|
||||
switch (regionValue)
|
||||
{
|
||||
case 0:
|
||||
// 华北
|
||||
return new AppRouterState()
|
||||
{
|
||||
TTL = -1,
|
||||
ApiServer = String.Format("{0}.api.lncld.net", prefix),
|
||||
EngineServer = String.Format("{0}.engine.lncld.net", prefix),
|
||||
PushServer = String.Format("{0}.push.lncld.net", prefix),
|
||||
RealtimeRouterServer = String.Format("{0}.rtm.lncld.net", prefix),
|
||||
StatsServer = String.Format("{0}.stats.lncld.net", prefix),
|
||||
Source = "initial",
|
||||
};
|
||||
case 1:
|
||||
// 美国
|
||||
return new AppRouterState()
|
||||
{
|
||||
TTL = -1,
|
||||
ApiServer = string.Format("{0}.api.lncldglobal.com", prefix),
|
||||
EngineServer = string.Format("{0}.engine.lncldglobal.com", prefix),
|
||||
PushServer = string.Format("{0}.push.lncldglobal.com", prefix),
|
||||
RealtimeRouterServer = string.Format("{0}.rtm.lncldglobal.com", prefix),
|
||||
StatsServer = string.Format("{0}.stats.lncldglobal.com", prefix),
|
||||
Source = "initial",
|
||||
};
|
||||
case 2:
|
||||
// 华东
|
||||
return new AppRouterState() {
|
||||
TTL = -1,
|
||||
ApiServer = string.Format("{0}.api.lncldapi.com", prefix),
|
||||
EngineServer = string.Format("{0}.engine.lncldapi.com", prefix),
|
||||
PushServer = string.Format("{0}.push.lncldapi.com", prefix),
|
||||
RealtimeRouterServer = string.Format("{0}.rtm.lncldapi.com", prefix),
|
||||
StatsServer = string.Format("{0}.stats.lncldapi.com", prefix),
|
||||
Source = "initial",
|
||||
};
|
||||
default:
|
||||
throw new AVException(AVException.ErrorCode.OtherCause, "invalid region");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IAppRouterController
|
||||
{
|
||||
AppRouterState Get();
|
||||
/// <summary>
|
||||
/// Start refresh the app router.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task RefreshAsync();
|
||||
void Clear();
|
||||
/// <summary>
|
||||
/// Query the app router.
|
||||
/// </summary>
|
||||
/// <returns>New AppRouterState</returns>
|
||||
Task<AppRouterState> QueryAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IAVAuthenticationProvider {
|
||||
/// <summary>
|
||||
/// Authenticates with the service.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
Task<IDictionary<string, object>> AuthenticateAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deauthenticates (logs out) the user associated with this provider. This
|
||||
/// call may block.
|
||||
/// </summary>
|
||||
void Deauthenticate();
|
||||
|
||||
/// <summary>
|
||||
/// Restores authentication that has been serialized, such as session keys,
|
||||
/// etc.
|
||||
/// </summary>
|
||||
/// <param name="authData">The auth data for the provider. This value may be null
|
||||
/// when unlinking an account.</param>
|
||||
/// <returns><c>true</c> iff the authData was successfully synchronized. A <c>false</c> return
|
||||
/// value indicates that the user should no longer be associated because of bad auth
|
||||
/// data.</returns>
|
||||
bool RestoreAuthentication(IDictionary<string, object> authData);
|
||||
|
||||
/// <summary>
|
||||
/// Provides a unique name for the type of authentication the provider does.
|
||||
/// For example, the FacebookAuthenticationProvider would return "facebook".
|
||||
/// </summary>
|
||||
string AuthType { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Utilities;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVCloudCodeController : IAVCloudCodeController
|
||||
{
|
||||
private readonly IAVCommandRunner commandRunner;
|
||||
|
||||
public AVCloudCodeController(IAVCommandRunner commandRunner)
|
||||
{
|
||||
this.commandRunner = commandRunner;
|
||||
}
|
||||
|
||||
public Task<T> CallFunctionAsync<T>(String name,
|
||||
IDictionary<string, object> parameters,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand(string.Format("functions/{0}", Uri.EscapeUriString(name)),
|
||||
method: "POST",
|
||||
sessionToken: sessionToken,
|
||||
data: NoObjectsEncoder.Instance.Encode(parameters) as IDictionary<string, object>);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary<string, object>;
|
||||
if (!decoded.ContainsKey("result"))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return Conversion.To<T>(decoded["result"]);
|
||||
});
|
||||
}
|
||||
|
||||
public Task<T> RPCFunction<T>(string name, IDictionary<string, object> parameters, string sessionToken, CancellationToken cancellationToken)
|
||||
{
|
||||
var command = new AVCommand(string.Format("call/{0}", Uri.EscapeUriString(name)),
|
||||
method: "POST",
|
||||
sessionToken: sessionToken,
|
||||
data: PointerOrLocalIdEncoder.Instance.Encode(parameters) as IDictionary<string, object>);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
var decoded = AVDecoder.Instance.Decode(t.Result.Item2) as IDictionary<string, object>;
|
||||
if (!decoded.ContainsKey("result"))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
return Conversion.To<T>(decoded["result"]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IAVCloudCodeController
|
||||
{
|
||||
Task<T> CallFunctionAsync<T>(String name,
|
||||
IDictionary<string, object> parameters,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
Task<T> RPCFunction<T>(string name, IDictionary<string, object> parameters,
|
||||
string sessionToken,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using LeanCloud.Storage.Internal;
|
||||
using System.Linq;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// AVCommand is an <see cref="HttpRequest"/> with pre-populated
|
||||
/// headers.
|
||||
/// </summary>
|
||||
public class AVCommand : HttpRequest
|
||||
{
|
||||
public IDictionary<string, object> DataObject { get; private set; }
|
||||
public override Stream Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (base.Data != null)
|
||||
{
|
||||
return base.Data;
|
||||
}
|
||||
|
||||
return base.Data = (DataObject != null
|
||||
? new MemoryStream(Encoding.UTF8.GetBytes(Json.Encode(DataObject)))
|
||||
: null);
|
||||
}
|
||||
set { base.Data = value; }
|
||||
}
|
||||
|
||||
public AVCommand(string relativeUri,
|
||||
string method,
|
||||
string sessionToken = null,
|
||||
IList<KeyValuePair<string, string>> headers = null,
|
||||
IDictionary<string, object> data = null) : this(relativeUri: relativeUri,
|
||||
method: method,
|
||||
sessionToken: sessionToken,
|
||||
headers: headers,
|
||||
stream: null,
|
||||
contentType: data != null ? "application/json" : null)
|
||||
{
|
||||
DataObject = data;
|
||||
}
|
||||
|
||||
public AVCommand(string relativeUri,
|
||||
string method,
|
||||
string sessionToken = null,
|
||||
IList<KeyValuePair<string, string>> headers = null,
|
||||
Stream stream = null,
|
||||
string contentType = null)
|
||||
{
|
||||
var state = AVPlugins.Instance.AppRouterController.Get();
|
||||
var urlTemplate = "https://{0}/{1}/{2}";
|
||||
AVClient.Configuration configuration = AVClient.CurrentConfiguration;
|
||||
var apiVersion = "1.1";
|
||||
if (relativeUri.StartsWith("push") || relativeUri.StartsWith("installations"))
|
||||
{
|
||||
Uri = new Uri(string.Format(urlTemplate, state.PushServer, apiVersion, relativeUri));
|
||||
if (configuration.PushServer != null)
|
||||
{
|
||||
Uri = new Uri(string.Format("{0}{1}/{2}", configuration.PushServer, apiVersion, relativeUri));
|
||||
}
|
||||
}
|
||||
else if (relativeUri.StartsWith("stats") || relativeUri.StartsWith("always_collect") || relativeUri.StartsWith("statistics"))
|
||||
{
|
||||
Uri = new Uri(string.Format(urlTemplate, state.StatsServer, apiVersion, relativeUri));
|
||||
if (configuration.StatsServer != null)
|
||||
{
|
||||
Uri = new Uri(string.Format("{0}{1}/{2}", configuration.StatsServer, apiVersion, relativeUri));
|
||||
}
|
||||
}
|
||||
else if (relativeUri.StartsWith("functions") || relativeUri.StartsWith("call"))
|
||||
{
|
||||
Uri = new Uri(string.Format(urlTemplate, state.EngineServer, apiVersion, relativeUri));
|
||||
|
||||
if (configuration.EngineServer != null)
|
||||
{
|
||||
Uri = new Uri(string.Format("{0}{1}/{2}", configuration.EngineServer, apiVersion, relativeUri));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Uri = new Uri(string.Format(urlTemplate, state.ApiServer, apiVersion, relativeUri));
|
||||
|
||||
if (configuration.ApiServer != null)
|
||||
{
|
||||
Uri = new Uri(string.Format("{0}{1}/{2}", configuration.ApiServer, apiVersion, relativeUri));
|
||||
}
|
||||
}
|
||||
Method = method;
|
||||
Data = stream;
|
||||
Headers = new List<KeyValuePair<string, string>>(headers ?? Enumerable.Empty<KeyValuePair<string, string>>());
|
||||
|
||||
string useProduction = AVClient.UseProduction ? "1" : "0";
|
||||
Headers.Add(new KeyValuePair<string, string>("X-LC-Prod", useProduction));
|
||||
|
||||
if (!string.IsNullOrEmpty(sessionToken))
|
||||
{
|
||||
Headers.Add(new KeyValuePair<string, string>("X-LC-Session", sessionToken));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
Headers.Add(new KeyValuePair<string, string>("Content-Type", contentType));
|
||||
}
|
||||
}
|
||||
|
||||
public AVCommand(AVCommand other)
|
||||
{
|
||||
this.Uri = other.Uri;
|
||||
this.Method = other.Method;
|
||||
this.DataObject = other.DataObject;
|
||||
this.Headers = new List<KeyValuePair<string, string>>(other.Headers);
|
||||
this.Data = other.Data;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Command Runner.
|
||||
/// </summary>
|
||||
public class AVCommandRunner : IAVCommandRunner
|
||||
{
|
||||
private readonly IHttpClient httpClient;
|
||||
private readonly IInstallationIdController installationIdController;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="httpClient"></param>
|
||||
/// <param name="installationIdController"></param>
|
||||
public AVCommandRunner(IHttpClient httpClient, IInstallationIdController installationIdController)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.installationIdController = installationIdController;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="command"></param>
|
||||
/// <param name="uploadProgress"></param>
|
||||
/// <param name="downloadProgress"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public Task<Tuple<HttpStatusCode, IDictionary<string, object>>> RunCommandAsync(AVCommand command,
|
||||
IProgress<AVUploadProgressEventArgs> uploadProgress = null,
|
||||
IProgress<AVDownloadProgressEventArgs> downloadProgress = null,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return PrepareCommand(command).ContinueWith(commandTask =>
|
||||
{
|
||||
var requestLog = commandTask.Result.ToLog();
|
||||
AVClient.PrintLog("http=>" + requestLog);
|
||||
|
||||
return httpClient.ExecuteAsync(commandTask.Result, uploadProgress, downloadProgress, cancellationToken).OnSuccess(t =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var response = t.Result;
|
||||
var contentString = response.Item2;
|
||||
int responseCode = (int)response.Item1;
|
||||
|
||||
var responseLog = responseCode + ";" + contentString;
|
||||
AVClient.PrintLog("http<=" + responseLog);
|
||||
|
||||
if (responseCode >= 500)
|
||||
{
|
||||
// Server error, return InternalServerError.
|
||||
throw new AVException(AVException.ErrorCode.InternalServerError, response.Item2);
|
||||
}
|
||||
else if (contentString != null)
|
||||
{
|
||||
IDictionary<string, object> contentJson = null;
|
||||
try
|
||||
{
|
||||
if (contentString.StartsWith("["))
|
||||
{
|
||||
var arrayJson = Json.Parse(contentString);
|
||||
contentJson = new Dictionary<string, object> { { "results", arrayJson } };
|
||||
}
|
||||
else
|
||||
{
|
||||
contentJson = Json.Parse(contentString) as IDictionary<string, object>;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new AVException(AVException.ErrorCode.OtherCause,
|
||||
"Invalid response from server", e);
|
||||
}
|
||||
if (responseCode < 200 || responseCode > 299)
|
||||
{
|
||||
AVClient.PrintLog("error response code:" + responseCode);
|
||||
int code = (int)(contentJson.ContainsKey("code") ? (int)contentJson["code"] : (int)AVException.ErrorCode.OtherCause);
|
||||
string error = contentJson.ContainsKey("error") ?
|
||||
contentJson["error"] as string : contentString;
|
||||
AVException.ErrorCode ec = (AVException.ErrorCode)code;
|
||||
throw new AVException(ec, error);
|
||||
}
|
||||
return new Tuple<HttpStatusCode, IDictionary<string, object>>(response.Item1,
|
||||
contentJson);
|
||||
}
|
||||
return new Tuple<HttpStatusCode, IDictionary<string, object>>(response.Item1, null);
|
||||
});
|
||||
}).Unwrap();
|
||||
}
|
||||
|
||||
private const string revocableSessionTokenTrueValue = "1";
|
||||
private Task<AVCommand> PrepareCommand(AVCommand command)
|
||||
{
|
||||
AVCommand newCommand = new AVCommand(command);
|
||||
|
||||
Task<AVCommand> installationIdTask = installationIdController.GetAsync().ContinueWith(t =>
|
||||
{
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-Installation-Id", t.Result.ToString()));
|
||||
return newCommand;
|
||||
});
|
||||
|
||||
AVClient.Configuration configuration = AVClient.CurrentConfiguration;
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-Id", configuration.ApplicationId));
|
||||
|
||||
long timestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds;
|
||||
if (!string.IsNullOrEmpty(configuration.MasterKey) && AVClient.UseMasterKey)
|
||||
{
|
||||
string sign = MD5.GetMd5String(timestamp + configuration.MasterKey);
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-Sign", string.Format("{0},{1},master", sign, timestamp)));
|
||||
}
|
||||
else
|
||||
{
|
||||
string sign = MD5.GetMd5String(timestamp + configuration.ApplicationKey);
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-Sign", string.Format("{0},{1}", sign, timestamp)));
|
||||
}
|
||||
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-Client-Version", AVClient.VersionString));
|
||||
|
||||
if (!string.IsNullOrEmpty(configuration.VersionInfo.BuildVersion))
|
||||
{
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-App-Build-Version", configuration.VersionInfo.BuildVersion));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(configuration.VersionInfo.DisplayVersion))
|
||||
{
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-App-Display-Version", configuration.VersionInfo.DisplayVersion));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(configuration.VersionInfo.OSVersion))
|
||||
{
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LC-OS-Version", configuration.VersionInfo.OSVersion));
|
||||
}
|
||||
|
||||
if (AVUser.IsRevocableSessionEnabled)
|
||||
{
|
||||
newCommand.Headers.Add(new KeyValuePair<string, string>("X-LeanCloud-Revocable-Session", revocableSessionTokenTrueValue));
|
||||
}
|
||||
|
||||
if (configuration.AdditionalHTTPHeaders != null)
|
||||
{
|
||||
var headersDictionary = newCommand.Headers.ToDictionary(kv => kv.Key, kv => kv.Value);
|
||||
foreach (var header in configuration.AdditionalHTTPHeaders)
|
||||
{
|
||||
if (headersDictionary.ContainsKey(header.Key))
|
||||
{
|
||||
headersDictionary[header.Key] = header.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
newCommand.Headers.Add(header);
|
||||
}
|
||||
}
|
||||
newCommand.Headers = headersDictionary.ToList();
|
||||
}
|
||||
|
||||
return installationIdTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public interface IAVCommandRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes <see cref="AVCommand"/> and convert the result into Dictionary.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to be run.</param>
|
||||
/// <param name="uploadProgress">Upload progress callback.</param>
|
||||
/// <param name="downloadProgress">Download progress callback.</param>
|
||||
/// <param name="cancellationToken">The cancellation token for the request.</param>
|
||||
/// <returns></returns>
|
||||
Task<Tuple<HttpStatusCode, IDictionary<string, object>>> RunCommandAsync(AVCommand command,
|
||||
IProgress<AVUploadProgressEventArgs> uploadProgress = null,
|
||||
IProgress<AVDownloadProgressEventArgs> downloadProgress = null,
|
||||
CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// Config controller.
|
||||
/// </summary>
|
||||
internal class AVConfigController : IAVConfigController {
|
||||
private readonly IAVCommandRunner commandRunner;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AVConfigController"/> class.
|
||||
/// </summary>
|
||||
public AVConfigController(IAVCommandRunner commandRunner, IStorageController storageController) {
|
||||
this.commandRunner = commandRunner;
|
||||
CurrentConfigController = new AVCurrentConfigController(storageController);
|
||||
}
|
||||
|
||||
public IAVCommandRunner CommandRunner { get; internal set; }
|
||||
public IAVCurrentConfigController CurrentConfigController { get; internal set; }
|
||||
|
||||
public Task<AVConfig> FetchConfigAsync(String sessionToken, CancellationToken cancellationToken) {
|
||||
var command = new AVCommand("config",
|
||||
method: "GET",
|
||||
sessionToken: sessionToken,
|
||||
data: null);
|
||||
|
||||
return commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).OnSuccess(task => {
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return new AVConfig(task.Result.Item2);
|
||||
}).OnSuccess(task => {
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
CurrentConfigController.SetCurrentConfigAsync(task.Result);
|
||||
return task;
|
||||
}).Unwrap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
/// <summary>
|
||||
/// LeanCloud current config controller.
|
||||
/// </summary>
|
||||
internal class AVCurrentConfigController : IAVCurrentConfigController {
|
||||
private const string CurrentConfigKey = "CurrentConfig";
|
||||
|
||||
private readonly TaskQueue taskQueue;
|
||||
private AVConfig currentConfig;
|
||||
|
||||
private IStorageController storageController;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LeanCloud.Storage.Internal.AVCurrentConfigController"/> class.
|
||||
/// </summary>
|
||||
public AVCurrentConfigController(IStorageController storageController) {
|
||||
this.storageController = storageController;
|
||||
|
||||
taskQueue = new TaskQueue();
|
||||
}
|
||||
|
||||
public Task<AVConfig> GetCurrentConfigAsync() {
|
||||
return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => {
|
||||
if (currentConfig == null) {
|
||||
return storageController.LoadAsync().OnSuccess(t => {
|
||||
object tmp;
|
||||
t.Result.TryGetValue(CurrentConfigKey, out tmp);
|
||||
|
||||
string propertiesString = tmp as string;
|
||||
if (propertiesString != null) {
|
||||
var dictionary = AVClient.DeserializeJsonString(propertiesString);
|
||||
currentConfig = new AVConfig(dictionary);
|
||||
} else {
|
||||
currentConfig = new AVConfig();
|
||||
}
|
||||
|
||||
return currentConfig;
|
||||
});
|
||||
}
|
||||
|
||||
return Task.FromResult(currentConfig);
|
||||
}), CancellationToken.None).Unwrap();
|
||||
}
|
||||
|
||||
public Task SetCurrentConfigAsync(AVConfig config) {
|
||||
return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => {
|
||||
currentConfig = config;
|
||||
|
||||
var jsonObject = ((IJsonConvertible)config).ToJSON();
|
||||
var jsonString = AVClient.SerializeJsonString(jsonObject);
|
||||
|
||||
return storageController.LoadAsync().OnSuccess(t => t.Result.AddAsync(CurrentConfigKey, jsonString));
|
||||
}).Unwrap().Unwrap(), CancellationToken.None);
|
||||
}
|
||||
|
||||
public Task ClearCurrentConfigAsync() {
|
||||
return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => {
|
||||
currentConfig = null;
|
||||
|
||||
return storageController.LoadAsync().OnSuccess(t => t.Result.RemoveAsync(CurrentConfigKey));
|
||||
}).Unwrap().Unwrap(), CancellationToken.None);
|
||||
}
|
||||
|
||||
public Task ClearCurrentConfigInMemoryAsync() {
|
||||
return taskQueue.Enqueue(toAwait => toAwait.ContinueWith(_ => {
|
||||
currentConfig = null;
|
||||
}), CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IAVConfigController {
|
||||
/// <summary>
|
||||
/// Gets the current config controller.
|
||||
/// </summary>
|
||||
/// <value>The current config controller.</value>
|
||||
IAVCurrentConfigController CurrentConfigController { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the config from the server asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>The config async.</returns>
|
||||
/// <param name="sessionToken">Session token.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<AVConfig> FetchConfigAsync(String sessionToken, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LeanCloud.Storage.Internal {
|
||||
public interface IAVCurrentConfigController {
|
||||
/// <summary>
|
||||
/// Gets the current config async.
|
||||
/// </summary>
|
||||
/// <returns>The current config async.</returns>
|
||||
Task<AVConfig> GetCurrentConfigAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current config async.
|
||||
/// </summary>
|
||||
/// <returns>The current config async.</returns>
|
||||
/// <param name="config">Config.</param>
|
||||
Task SetCurrentConfigAsync(AVConfig config);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current config async.
|
||||
/// </summary>
|
||||
/// <returns>The current config async.</returns>
|
||||
Task ClearCurrentConfigAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current config in memory async.
|
||||
/// </summary>
|
||||
/// <returns>The current config in memory async.</returns>
|
||||
Task ClearCurrentConfigInMemoryAsync();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the internal Unity dispatcher used by the LeanCloud SDK.
|
||||
///
|
||||
/// It should be initialized once in your game, usually via AVInitializeBehavior.
|
||||
///
|
||||
/// In certain, advanced use-cases, you may wish to use
|
||||
/// this to set up your dispatcher manually.
|
||||
/// </summary>
|
||||
// TODO: (richardross) Review this interface before going public.
|
||||
public sealed class Dispatcher
|
||||
{
|
||||
static Dispatcher()
|
||||
{
|
||||
Instance = new Dispatcher();
|
||||
}
|
||||
|
||||
private Dispatcher()
|
||||
{
|
||||
DispatcherCoroutine = CreateDispatcherCoroutine();
|
||||
}
|
||||
|
||||
public static Dispatcher Instance { get; private set; }
|
||||
|
||||
public GameObject GameObject { get; set; }
|
||||
public IEnumerator DispatcherCoroutine { get; private set; }
|
||||
|
||||
private readonly ReaderWriterLockSlim dispatchQueueLock = new ReaderWriterLockSlim();
|
||||
private readonly Queue<Action> dispatchQueue = new Queue<Action>();
|
||||
|
||||
public void Post(Action action)
|
||||
{
|
||||
if (dispatchQueueLock.IsWriteLockHeld)
|
||||
{
|
||||
dispatchQueue.Enqueue(action);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchQueueLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
dispatchQueue.Enqueue(action);
|
||||
}
|
||||
finally
|
||||
{
|
||||
dispatchQueueLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator CreateDispatcherCoroutine()
|
||||
{
|
||||
// We must stop the first invocation here, so that we don't actually do anything until we begin looping.
|
||||
yield return null;
|
||||
while (true)
|
||||
{
|
||||
dispatchQueueLock.EnterUpgradeableReadLock();
|
||||
try
|
||||
{
|
||||
// We'll only empty what's already in the dispatch queue in this iteration (so that a
|
||||
// nested dispatch behaves like nextTick()).
|
||||
int count = dispatchQueue.Count;
|
||||
if (count > 0)
|
||||
{
|
||||
dispatchQueueLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
while (count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
dispatchQueue.Dequeue()();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// If an exception occurs, catch it and log it so that dispatches aren't broken.
|
||||
Debug.LogException(e);
|
||||
}
|
||||
count--;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
dispatchQueueLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
dispatchQueueLock.ExitUpgradeableReadLock();
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using LeanCloud.Utilities;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
public class AVDecoder
|
||||
{
|
||||
// This class isn't really a Singleton, but since it has no state, it's more efficient to get
|
||||
// the default instance.
|
||||
private static readonly AVDecoder instance = new AVDecoder();
|
||||
public static AVDecoder Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent default constructor.
|
||||
private AVDecoder() { }
|
||||
|
||||
public object Decode(object data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dict = data as IDictionary<string, object>;
|
||||
if (dict != null)
|
||||
{
|
||||
if (dict.ContainsKey("__op"))
|
||||
{
|
||||
return AVFieldOperations.Decode(dict);
|
||||
}
|
||||
|
||||
object type;
|
||||
dict.TryGetValue("__type", out type);
|
||||
var typeString = type as string;
|
||||
|
||||
if (typeString == null)
|
||||
{
|
||||
var newDict = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
newDict[pair.Key] = Decode(pair.Value);
|
||||
}
|
||||
return newDict;
|
||||
}
|
||||
|
||||
if (typeString == "Date")
|
||||
{
|
||||
return ParseDate(dict["iso"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "Bytes")
|
||||
{
|
||||
return Convert.FromBase64String(dict["base64"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "Pointer")
|
||||
{
|
||||
//set a include key to fetch or query.
|
||||
if (dict.Keys.Count > 3)
|
||||
{
|
||||
return DecodeAVObject(dict);
|
||||
}
|
||||
return DecodePointer(dict["className"] as string, dict["objectId"] as string);
|
||||
}
|
||||
|
||||
if (typeString == "File")
|
||||
{
|
||||
return DecodeAVFile(dict);
|
||||
}
|
||||
|
||||
if (typeString == "GeoPoint")
|
||||
{
|
||||
return new AVGeoPoint(Conversion.To<double>(dict["latitude"]),
|
||||
Conversion.To<double>(dict["longitude"]));
|
||||
}
|
||||
|
||||
if (typeString == "Object")
|
||||
{
|
||||
return DecodeAVObject(dict);
|
||||
}
|
||||
|
||||
if (typeString == "Relation")
|
||||
{
|
||||
return AVRelationBase.CreateRelation(null, null, dict["className"] as string);
|
||||
}
|
||||
|
||||
var converted = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
converted[pair.Key] = Decode(pair.Value);
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
var list = data as IList<object>;
|
||||
if (list != null)
|
||||
{
|
||||
return (from item in list
|
||||
select Decode(item)).ToList();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
protected virtual object DecodePointer(string className, string objectId)
|
||||
{
|
||||
if (className == "_File")
|
||||
{
|
||||
return AVFile.CreateWithoutData(objectId);
|
||||
}
|
||||
return AVObject.CreateWithoutData(className, objectId);
|
||||
}
|
||||
protected virtual object DecodeAVObject(IDictionary<string, object> dict)
|
||||
{
|
||||
var className = dict["className"] as string;
|
||||
if (className == "_File")
|
||||
{
|
||||
return DecodeAVFile(dict);
|
||||
}
|
||||
var state = AVObjectCoder.Instance.Decode(dict, this);
|
||||
return AVObject.FromState<AVObject>(state, dict["className"] as string);
|
||||
}
|
||||
protected virtual object DecodeAVFile(IDictionary<string, object> dict)
|
||||
{
|
||||
var objectId = dict["objectId"] as string;
|
||||
var file = AVFile.CreateWithoutData(objectId);
|
||||
file.MergeFromJSON(dict);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
public virtual IList<T> DecodeList<T>(object data)
|
||||
{
|
||||
IList<T> rtn = null;
|
||||
var list = (IList<object>)data;
|
||||
if (list != null)
|
||||
{
|
||||
rtn = new List<T>();
|
||||
foreach (var item in list)
|
||||
{
|
||||
rtn.Add((T)item);
|
||||
}
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public static DateTime ParseDate(string input)
|
||||
{
|
||||
var rtn = DateTime.ParseExact(input,
|
||||
AVClient.DateFormatStrings,
|
||||
CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.AssumeUniversal);
|
||||
return rtn;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using LeanCloud.Utilities;
|
||||
using LeanCloud.Storage.Internal;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A <c>AVEncoder</c> can be used to transform objects such as <see cref="AVObject"/> into JSON
|
||||
/// data structures.
|
||||
/// </summary>
|
||||
/// <seealso cref="AVDecoder"/>
|
||||
public abstract class AVEncoder
|
||||
{
|
||||
#if UNITY
|
||||
private static readonly bool isCompiledByIL2CPP = AppDomain.CurrentDomain.FriendlyName.Equals("IL2CPP Root Domain");
|
||||
#else
|
||||
private static readonly bool isCompiledByIL2CPP = false;
|
||||
#endif
|
||||
|
||||
public static bool IsValidType(object value)
|
||||
{
|
||||
return value == null ||
|
||||
ReflectionHelpers.IsPrimitive(value.GetType()) ||
|
||||
value is string ||
|
||||
value is AVObject ||
|
||||
value is AVACL ||
|
||||
value is AVFile ||
|
||||
value is AVGeoPoint ||
|
||||
value is AVRelationBase ||
|
||||
value is DateTime ||
|
||||
value is byte[] ||
|
||||
Conversion.As<IDictionary<string, object>>(value) != null ||
|
||||
Conversion.As<IList<object>>(value) != null;
|
||||
}
|
||||
|
||||
public object Encode(object value)
|
||||
{
|
||||
// If this object has a special encoding, encode it and return the
|
||||
// encoded object. Otherwise, just return the original object.
|
||||
if (value is DateTime)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"iso", ((DateTime)value).ToUniversalTime().ToString(AVClient.DateFormatStrings.First(), CultureInfo.InvariantCulture)
|
||||
},
|
||||
{
|
||||
"__type", "Date"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (value is AVFile)
|
||||
{
|
||||
var file = value as AVFile;
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{"__type", "Pointer"},
|
||||
{ "className", "_File"},
|
||||
{ "objectId", file.ObjectId}
|
||||
};
|
||||
}
|
||||
|
||||
var bytes = value as byte[];
|
||||
if (bytes != null)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
{ "__type", "Bytes"},
|
||||
{ "base64", Convert.ToBase64String(bytes)}
|
||||
};
|
||||
}
|
||||
|
||||
var obj = value as AVObject;
|
||||
if (obj != null)
|
||||
{
|
||||
return EncodeAVObject(obj);
|
||||
}
|
||||
|
||||
var jsonConvertible = value as IJsonConvertible;
|
||||
if (jsonConvertible != null)
|
||||
{
|
||||
return jsonConvertible.ToJSON();
|
||||
}
|
||||
|
||||
var dict = Conversion.As<IDictionary<string, object>>(value);
|
||||
if (dict != null)
|
||||
{
|
||||
var json = new Dictionary<string, object>();
|
||||
foreach (var pair in dict)
|
||||
{
|
||||
json[pair.Key] = Encode(pair.Value);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
var list = Conversion.As<IList<object>>(value);
|
||||
if (list != null)
|
||||
{
|
||||
return EncodeList(list);
|
||||
}
|
||||
|
||||
// TODO (hallucinogen): convert IAVFieldOperation to IJsonConvertible
|
||||
var operation = value as IAVFieldOperation;
|
||||
if (operation != null)
|
||||
{
|
||||
return operation.Encode();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
protected abstract IDictionary<string, object> EncodeAVObject(AVObject value);
|
||||
|
||||
private object EncodeList(IList<object> list)
|
||||
{
|
||||
var newArray = new List<object>();
|
||||
// We need to explicitly cast `list` to `List<object>` rather than
|
||||
// `IList<object>` because IL2CPP is stricter than the usual Unity AOT compiler pipeline.
|
||||
if (isCompiledByIL2CPP && list.GetType().IsArray)
|
||||
{
|
||||
list = new List<object>(list);
|
||||
}
|
||||
foreach (var item in list)
|
||||
{
|
||||
if (!IsValidType(item))
|
||||
{
|
||||
throw new ArgumentException("Invalid type for value in an array");
|
||||
}
|
||||
newArray.Add(Encode(item));
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LeanCloud.Storage.Internal
|
||||
{
|
||||
// TODO: (richardross) refactor entire LeanCloud coder interfaces.
|
||||
public class AVObjectCoder
|
||||
{
|
||||
private static readonly AVObjectCoder instance = new AVObjectCoder();
|
||||
public static AVObjectCoder Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent default constructor.
|
||||
private AVObjectCoder() { }
|
||||
|
||||
public IDictionary<string, object> Encode<T>(T state,
|
||||
IDictionary<string, IAVFieldOperation> operations,
|
||||
AVEncoder encoder) where T : IObjectState
|
||||
{
|
||||
var result = new Dictionary<string, object>();
|
||||
foreach (var pair in operations)
|
||||
{
|
||||
// AVRPCSerialize the data
|
||||
var operation = pair.Value;
|
||||
|
||||
result[pair.Key] = encoder.Encode(operation);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IObjectState Decode(IDictionary<string, object> data,
|
||||
AVDecoder decoder)
|
||||
{
|
||||
IDictionary<string, object> serverData = new Dictionary<string, object>();
|
||||
var mutableData = new Dictionary<string, object>(data);
|
||||
string objectId = extractFromDictionary<string>(mutableData, "objectId", (obj) =>
|
||||
{
|
||||
return obj as string;
|
||||
});
|
||||
DateTime? createdAt = extractFromDictionary<DateTime?>(mutableData, "createdAt", (obj) =>
|
||||
{
|
||||
return AVDecoder.ParseDate(obj as string);
|
||||
});
|
||||
DateTime? updatedAt = extractFromDictionary<DateTime?>(mutableData, "updatedAt", (obj) =>
|
||||
{
|
||||
return AVDecoder.ParseDate(obj as string);
|
||||
});
|
||||
|
||||
if (mutableData.ContainsKey("ACL"))
|
||||
{
|
||||
serverData["ACL"] = extractFromDictionary<AVACL>(mutableData, "ACL", (obj) =>
|
||||
{
|
||||
return new AVACL(obj as IDictionary<string, object>);
|
||||
});
|
||||
}
|
||||
string className = extractFromDictionary<string>(mutableData, "className", obj =>
|
||||
{
|
||||
return obj as string;
|
||||
});
|
||||
if (createdAt != null && updatedAt == null)
|
||||
{
|
||||
updatedAt = createdAt;
|
||||
}
|
||||
|
||||
// Bring in the new server data.
|
||||
foreach (var pair in mutableData)
|
||||
{
|
||||
if (pair.Key == "__type" || pair.Key == "className")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = pair.Value;
|
||||
serverData[pair.Key] = decoder.Decode(value);
|
||||
}
|
||||
|
||||
return new MutableObjectState
|
||||
{
|
||||
ObjectId = objectId,
|
||||
CreatedAt = createdAt,
|
||||
UpdatedAt = updatedAt,
|
||||
ServerData = serverData,
|
||||
ClassName = className
|
||||
};
|
||||
}
|
||||
|
||||
private T extractFromDictionary<T>(IDictionary<string, object> data, string key, Func<object, T> action)
|
||||
{
|
||||
T result = default(T);
|
||||
if (data.ContainsKey(key))
|
||||
{
|
||||
result = action(data[key]);
|
||||
data.Remove(key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue