/*
* Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Tizen.Applications.Cion
{
///
/// An abstract class to represent cion client.
///
/// 9
public abstract class ClientBase : IDisposable
{
private readonly string LogTag = "Tizen.Applications.Cion";
private readonly ClientSafeHandle _handle;
private PeerInfo _peer;
private Interop.CionClient.CionClientServerDiscoveredCb _discoveredCb;
private Interop.CionClient.CionClientConnectionResultCb _connectionResultCb;
private Interop.CionClient.CionClientPayloadReceivedCb _payloadRecievedCb;
private Interop.CionClient.CionClientDisconnectedCb _disconnectedCb;
private Dictionary> _tcsDictionary = new Dictionary>();
private Dictionary _payloadAsyncCbDictionary = new Dictionary();
///
/// Gets the service name of current cion client.
///
/// 9
public string ServiceName { get; }
///
/// Gets peer info of connected cion server.
///
/// 9
public PeerInfo PeerInfo
{
get
{
return _peer;
}
}
///
/// The constructor of ClientBase class.
///
/// The name of service.
/// The maximum length of service name is 512.
/// Thrown when the given service name is too long.
/// Thrown when there is not enough memory to continue the execution of the method.
/// 9
public ClientBase(string serviceName) : this(serviceName, null) { }
///
/// The constructor of ClientBase class.
///
/// The name of service.
/// The security configuration.
/// The maximum length of service name is 512.
/// Thrown when the given service name is too long.
/// Thrown when there is not enough memory to continue the execution of the method.
/// 9
public ClientBase(string serviceName, Cion.SecurityInfo security)
{
ServiceName = serviceName;
SecuritySafeHandle handle = security?._handle;
Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientCreate(out _handle, serviceName, handle?.DangerousGetHandle() ?? IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to create client.");
}
_connectionResultCb = new Interop.CionClient.CionClientConnectionResultCb(
(string service, IntPtr peerInfo, IntPtr result, IntPtr userData) =>
{
Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
if (clone_ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, string.Format("Failed to clone peer info."));
return;
}
PeerInfo peer = new PeerInfo(clone);
ConnectionResult connectionResult = new ConnectionResult(result);
if (connectionResult.Status == ConnectionStatus.OK)
{
_peer = peer;
}
OnConnectionResult(peer, connectionResult);
});
ret = Interop.CionClient.CionClientAddConnectionResultCb(_handle, _connectionResultCb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
_handle.Dispose();
throw CionErrorFactory.GetException(ret, "Failed to add connection status changed callback.");
}
_payloadRecievedCb = new Interop.CionClient.CionClientPayloadReceivedCb(
(string service, IntPtr peerInfo, IntPtr payload, int status, IntPtr userData) =>
{
Payload receivedPayload;
Interop.CionPayload.CionPayloadGetType(payload, out Interop.CionPayload.PayloadType type);
switch (type)
{
case Interop.CionPayload.PayloadType.Data:
receivedPayload = new DataPayload(new PayloadSafeHandle(payload, false));
break;
case Interop.CionPayload.PayloadType.File:
receivedPayload = new FilePayload(new PayloadSafeHandle(payload, false));
break;
default:
Log.Error(LogTag, "Invalid payload type received.");
return;
}
OnPayloadReceived(receivedPayload, (PayloadTransferStatus)status);
});
ret = Interop.CionClient.CionClientAddPayloadReceivedCb(_handle, _payloadRecievedCb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
_handle.Dispose();
throw CionErrorFactory.GetException(ret, "Failed to add payload received callback.");
}
_disconnectedCb = new Interop.CionClient.CionClientDisconnectedCb(
(string service, IntPtr peerInfo, IntPtr userData) =>
{
Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
if (clone_ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, string.Format("Failed to clone peer info."));
return;
}
OnDisconnected(new PeerInfo(clone));
});
ret = Interop.CionClient.CionClientAddDisconnectedCb(_handle, _disconnectedCb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
_handle.Dispose();
throw CionErrorFactory.GetException(ret, "Failed to add disconnected callback.");
}
}
///
/// Starts discovering cion servers.
///
/// http://tizen.org/privilege/d2d.datasharing
/// http://tizen.org/privilege/internet
/// public
/// Thrown when the discovery operation is already in progress.
/// Thrown when an application does not have the privilege to access this method.
/// 9
public void TryDiscovery()
{
if (_discoveredCb == null)
{
Interop.CionClient.CionClientServerDiscoveredCb cb = new Interop.CionClient.CionClientServerDiscoveredCb(
(string serviceName, IntPtr peerInfo, IntPtr userData) =>
{
Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
if (clone_ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, "Failed to clone peer info.");
return;
}
OnDiscovered(new PeerInfo(clone));
});
_discoveredCb = cb;
}
Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientTryDiscovery(_handle, _discoveredCb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to try discovery.");
}
}
///
/// Stops discovering.
///
/// Thrown when the client is not discovering.
/// 9
public void StopDiscovery()
{
Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientStopDiscovery(_handle);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to stop discovery.");
}
}
///
/// Connects with the cion server.
///
/// The peer to connect.
/// http://tizen.org/privilege/d2d.datasharing
/// http://tizen.org/privilege/internet
/// public
/// Thrown when the client cannot connect to server.
/// Thrown when an application does not have the privilege to access this method.
/// 9
public void Connect(PeerInfo peer)
{
Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientConnect(_handle, peer?._handle);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to connect.");
}
}
///
/// Disconnects from the cion server.
///
/// 9
public void Disconnect()
{
Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientDisconnect(_handle);
if (ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, string.Format("Failed to disconnect: {0}", ret));
}
_peer = null;
}
///
/// Sends data synchronously to the connected cion server.
///
/// The data to send.
/// The timeout of sending operation.
/// Thrown when the given data is invalid.
/// Thrown when there is no connected cion server or failed to receive reply.
/// Thrown when a timeout occurred.
/// 9
public byte[] SendData(byte[] data, int timeout)
{
Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientSendData(_handle, data, data?.Length ?? -1, timeout, out IntPtr returnDataPtr, out int returnDataSize);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to send data.");
}
byte[] returnData = new byte[returnDataSize];
Marshal.Copy(returnDataPtr, returnData, 0, returnDataSize);
Log.Info(LogTag, string.Format("Returned data size: {0}", returnDataSize));
return returnData;
}
///
/// Sends payload asynchronously to the connected cion server.
///
/// The payload to send.
/// Thrown when the payload is not valid.
/// Thrown when there is no connected cion server or failed to send payload.
/// 9
public Task SendPayloadAsync(Payload payload)
{
if (payload == null || payload.Id.Length == 0)
{
throw new ArgumentException("Payload is invalid.");
}
if (_tcsDictionary.ContainsKey(payload.Id))
{
throw new InvalidOperationException("Payload is already sent.");
}
TaskCompletionSource tcs = new TaskCompletionSource();
_tcsDictionary[payload.Id] = tcs;
Interop.CionClient.CionClientPayloadAsyncResultCb cb = new Interop.CionClient.CionClientPayloadAsyncResultCb(
(IntPtr result, IntPtr userData) =>
{
TaskCompletionSource tcsToReturn = _tcsDictionary[payload.Id];
PayloadAsyncResult resultPayload = null;
try
{
resultPayload = PayloadAsyncResult.CreateFromHandle(result);
}
catch (Exception e)
{
Log.Error(LogTag, string.Format("Failed to create PayloadAsyncResult from result handle: {0}.", e.Message));
tcsToReturn.SetException(e);
return;
}
tcsToReturn.SetResult(resultPayload);
_payloadAsyncCbDictionary.Remove(resultPayload.PayloadId);
_tcsDictionary.Remove(resultPayload.PayloadId);
});
Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientSendPayloadAsync(_handle, payload?._handle, cb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to send payload.");
}
_payloadAsyncCbDictionary[payload?.Id] = cb;
return tcs.Task;
}
///
/// The result callback of connection request.
///
/// The peer info of the cion server.
/// The result of the connection.
/// 9
protected abstract void OnConnectionResult(PeerInfo peerInfo, ConnectionResult result);
///
/// The callback invoked when received payload.
///
/// The received payload.
/// The status of sent payload.
/// 9
protected abstract void OnPayloadReceived(Payload payload, PayloadTransferStatus status);
///
/// The callback invoked when the cion server discovered.
///
/// The peer info of discovered cion server.
/// 9
protected abstract void OnDiscovered(PeerInfo peerInfo);
///
/// The callback invoked when disconnected with cion client.
///
/// The peer info of the cion server.
/// 9
protected abstract void OnDisconnected(PeerInfo peerInfo);
#region IDisposable Support
private bool disposedValue = false;
///
/// Releases any unmanaged resources used by this object. Can also dispose any other disposable objects.
///
/// If true, disposes any disposable objects. If false, does not dispose disposable objects.
/// 9
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_handle.Dispose();
}
disposedValue = true;
}
}
///
/// Releases all resources used by the ClientBase class.
///
/// 9
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}