[Applications.Cion] Fix namespace, ServerBase.DisplayName property, docfx (#3553)
[platform/core/csapi/tizenfx.git] / src / Tizen.Applications.Cion / Tizen.Applications.Cion / ClientBase.cs
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 using System;
18 using System.Collections.Generic;
19 using System.Runtime.InteropServices;
20 using System.Threading.Tasks;
21
22 namespace Tizen.Applications.Cion
23 {
24     /// <summary>
25     /// An abstract class to represent cion client.
26     /// </summary>
27     /// <since_tizen> 9 </since_tizen>
28     public abstract class ClientBase : IDisposable
29     {
30         private readonly string LogTag = "Tizen.Applications.Cion";
31         private readonly ClientSafeHandle _handle;
32
33         private PeerInfo _peer;
34
35         private Interop.CionClient.CionClientServerDiscoveredCb _discoveredCb;
36         private Interop.CionClient.CionClientConnectionResultCb _connectionResultCb;
37         private Interop.CionClient.CionClientPayloadReceivedCb _payloadRecievedCb;
38         private Interop.CionClient.CionClientDisconnectedCb _disconnectedCb;
39         private Interop.CionClient.CionClientPayloadAsyncResultCb _payloadAsyncResultCb;
40         private Dictionary<string, TaskCompletionSource<PayloadAsyncResult>> _tcsDictionary = new Dictionary<string, TaskCompletionSource<PayloadAsyncResult>>();
41         private Dictionary<string, Interop.CionClient.CionClientPayloadAsyncResultCb> _payloadAsyncCbDictionary = new Dictionary<string, Interop.CionClient.CionClientPayloadAsyncResultCb>();
42
43         /// <summary>
44         /// Gets the service name of current cion client.
45         /// </summary>
46         /// <since_tizen> 9 </since_tizen>
47         public string ServiceName { get; }
48
49         /// <summary>
50         /// Gets peer info of connected cion server.
51         /// </summary>
52         /// <since_tizen> 9 </since_tizen>
53         public PeerInfo PeerInfo
54         {
55             get
56             {
57                 return _peer;
58             }
59         }
60
61         /// <summary>
62         /// The constructor of ClientBase class.
63         /// </summary>
64         /// <param name="serviceName">The name of service.</param>
65         /// <remarks>The maximum length of service name is 512.</remarks>
66         /// <exception cref="ArgumentException">Thrown when the given service name is too long.</exception>
67         /// <exception cref="InvalidOperationException">Thrown when there is not enough memory to continue the execution of the method.</exception> 
68         /// <since_tizen> 9 </since_tizen>
69         public ClientBase(string serviceName) : this(serviceName, null) { }
70
71         /// <summary>
72         /// The constructor of ClientBase class.
73         /// </summary>
74         /// <param name="serviceName">The name of service.</param>
75         /// <param name="security">The security configuration.</param>
76         /// <remarks>The maximum length of service name is 512.</remarks>
77         /// <exception cref="ArgumentException">Thrown when the given service name is too long.</exception>
78         /// <exception cref="InvalidOperationException">Thrown when there is not enough memory to continue the execution of the method.</exception> 
79         /// <since_tizen> 9 </since_tizen>
80         public ClientBase(string serviceName, Cion.SecurityInfo security)
81         {
82             ServiceName = serviceName;
83
84             SecuritySafeHandle handle = security?._handle;
85             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientCreate(out _handle, serviceName, handle?.DangerousGetHandle() ?? IntPtr.Zero);
86             if (ret != Interop.Cion.ErrorCode.None)
87             {
88                 throw CionErrorFactory.GetException(ret, "Failed to create client.");
89             }
90
91             _connectionResultCb = new Interop.CionClient.CionClientConnectionResultCb(
92                 (string service, IntPtr peerInfo, IntPtr result, IntPtr userData) =>
93                 {
94                     Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
95                     if (clone_ret != Interop.Cion.ErrorCode.None)
96                     {
97                         Log.Error(LogTag, string.Format("Failed to clone peer info."));
98                         return;
99                     }
100
101                     PeerInfo peer = new PeerInfo(clone);
102                     ConnectionResult connectionResult = new ConnectionResult(result);
103                     if (connectionResult.Status == ConnectionStatus.OK)
104                     {
105                         _peer = peer;
106                     }
107
108                     OnConnectionResult(peer, connectionResult);
109                 });
110             ret = Interop.CionClient.CionClientAddConnectionResultCb(_handle, _connectionResultCb, IntPtr.Zero);
111             if (ret != Interop.Cion.ErrorCode.None)
112             {
113                 _handle.Dispose();
114                 throw CionErrorFactory.GetException(ret, "Failed to add connection status changed callback.");
115             }
116
117             _payloadRecievedCb = new Interop.CionClient.CionClientPayloadReceivedCb(
118                 (string service, IntPtr peerInfo, IntPtr payload, int status, IntPtr userData) =>
119                 {
120                     Payload receivedPayload;
121                     Interop.CionPayload.CionPayloadGetType(payload, out Interop.CionPayload.PayloadType type);
122                     switch (type)
123                     {
124                         case Interop.CionPayload.PayloadType.Data:
125                             receivedPayload = new DataPayload(new PayloadSafeHandle(payload, false));
126                             break;
127                         case Interop.CionPayload.PayloadType.File:
128                             receivedPayload = new FilePayload(new PayloadSafeHandle(payload, false));
129                             break;
130                         default:
131                             Log.Error(LogTag, "Invalid payload type received.");
132                             return;
133                     }
134                     OnPayloadReceived(receivedPayload, (PayloadTransferStatus)status);
135                 });
136             ret = Interop.CionClient.CionClientAddPayloadReceivedCb(_handle, _payloadRecievedCb, IntPtr.Zero);
137             if (ret != Interop.Cion.ErrorCode.None)
138             {
139                 _handle.Dispose();
140                 throw CionErrorFactory.GetException(ret, "Failed to add payload received callback.");
141             }
142
143             _disconnectedCb = new Interop.CionClient.CionClientDisconnectedCb(
144                 (string service, IntPtr peerInfo, IntPtr userData) =>
145                 {
146                     Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
147                     if (clone_ret != Interop.Cion.ErrorCode.None)
148                     {
149                         Log.Error(LogTag, string.Format("Failed to clone peer info."));
150                         return;
151                     }
152                     OnDisconnected(new PeerInfo(clone));
153                 });
154             ret = Interop.CionClient.CionClientAddDisconnectedCb(_handle, _disconnectedCb, IntPtr.Zero);
155             if (ret != Interop.Cion.ErrorCode.None)
156             {
157                 _handle.Dispose();
158                 throw CionErrorFactory.GetException(ret, "Failed to add disconnected callback.");
159             }
160         }
161
162         /// <summary>
163         /// Starts discovering cion servers.
164         /// </summary>
165         /// <privilege>http://tizen.org/privilege/d2d.datasharing</privilege>
166         /// <privilege>http://tizen.org/privilege/internet</privilege>
167         /// <privlevel>public</privlevel>
168         /// <exception cref="InvalidOperationException">Thrown when the discovery operation is already in progress.</exception>
169         /// <exception cref="UnauthorizedAccessException">Thrown when an application does not have the privilege to access this method.</exception>
170         /// <since_tizen> 9 </since_tizen>
171         public void TryDiscovery()
172         {
173             if (_discoveredCb == null)
174             {
175                 Interop.CionClient.CionClientServerDiscoveredCb cb = new Interop.CionClient.CionClientServerDiscoveredCb(
176                     (string serviceName, IntPtr peerInfo, IntPtr userData) =>
177                     {
178                         Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
179                         if (clone_ret != Interop.Cion.ErrorCode.None)
180                         {
181                             Log.Error(LogTag, "Failed to clone peer info.");
182                             return;
183                         }
184                         OnDiscovered(new PeerInfo(clone));
185                     });
186                 _discoveredCb = cb;
187             }
188
189             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientTryDiscovery(_handle, _discoveredCb, IntPtr.Zero);
190             if (ret != Interop.Cion.ErrorCode.None)
191             {
192                 throw CionErrorFactory.GetException(ret, "Failed to try discovery.");
193             }
194         }
195
196         /// <summary>
197         /// Stops discovering.
198         /// </summary>
199         /// <exception cref="InvalidOperationException">Thrown when the client is not discovering.</exception>
200         /// <since_tizen> 9 </since_tizen>
201         public void StopDiscovery()
202         {
203             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientStopDiscovery(_handle);
204             if (ret != Interop.Cion.ErrorCode.None)
205             {
206                 throw CionErrorFactory.GetException(ret, "Failed to stop discovery.");
207             }
208         }
209
210         /// <summary>
211         /// Connects with the cion server.
212         /// </summary>
213         /// <param name="peer">The peer to connect.</param>
214         /// <privilege>http://tizen.org/privilege/d2d.datasharing</privilege>
215         /// <privilege>http://tizen.org/privilege/internet</privilege>
216         /// <privlevel>public</privlevel>
217         /// <exception cref="InvalidOperationException">Thrown when the client cannot connect to server.</exception>
218         /// <exception cref="UnauthorizedAccessException">Thrown when an application does not have the privilege to access this method.</exception>
219         /// <since_tizen> 9 </since_tizen>
220         public void Connect(PeerInfo peer)
221         {
222             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientConnect(_handle, peer?._handle);
223             if (ret != Interop.Cion.ErrorCode.None)
224             {
225                 throw CionErrorFactory.GetException(ret, "Failed to connect.");
226             }
227         }
228
229         /// <summary>
230         /// Disconnects from the cion server.
231         /// </summary>
232         /// <since_tizen> 9 </since_tizen>
233         public void Disconnect()
234         {
235             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientDisconnect(_handle);
236             if (ret != Interop.Cion.ErrorCode.None)
237             {
238                 Log.Error(LogTag, string.Format("Failed to disconnect: {0}", ret));
239             }
240             _peer = null;
241         }
242
243         /// <summary>
244         /// Sends data synchronously to the connected cion server.
245         /// </summary>
246         /// <param name="data">The data to send.</param>
247         /// <param name="timeout">The timeout of sending operation.</param>
248         /// <exception cref="ArgumentException">Thrown when the given data is invalid.</exception>
249         /// <exception cref="InvalidOperationException">Thrown when there is no connected cion server or failed to receive reply.</exception>
250         /// <exception cref="TimeoutException">Thrown when a timeout occurred.</exception>
251         /// <since_tizen> 9 </since_tizen>
252         public byte[] SendData(byte[] data, int timeout)
253         {
254             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientSendData(_handle, data, data?.Length ?? -1, timeout, out IntPtr returnDataPtr, out int returnDataSize);
255             if (ret != Interop.Cion.ErrorCode.None)
256             {
257                 throw CionErrorFactory.GetException(ret, "Failed to send data.");
258             }
259             byte[] returnData = new byte[returnDataSize];
260             Marshal.Copy(returnDataPtr, returnData, 0, returnDataSize);
261             Log.Info(LogTag, string.Format("Returned data size: {0}", returnDataSize));
262
263             return returnData;
264         }
265
266         /// <summary>
267         /// Sends payload asynchronously to the connected cion server.
268         /// </summary>
269         /// <param name="payload">The payload to send.</param>
270         /// <exception cref="ArgumentException">Thrown when the payload is not valid.</exception>
271         /// <exception cref="InvalidOperationException">Thrown when there is no connected cion server or failed to send payload.</exception>
272         /// <since_tizen> 9 </since_tizen>
273         public Task<PayloadAsyncResult> SendPayloadAsync(Payload payload)
274         {
275             if (payload == null || payload.Id.Length == 0)
276             {
277                 throw new ArgumentException("Payload is invalid.");
278             }
279
280             if (_tcsDictionary.ContainsKey(payload.Id))
281             {
282                 throw new InvalidOperationException("Payload is already sent.");
283             }
284
285             TaskCompletionSource<PayloadAsyncResult> tcs = new TaskCompletionSource<PayloadAsyncResult>();
286             _tcsDictionary[payload.Id] = tcs;
287
288             Interop.CionClient.CionClientPayloadAsyncResultCb cb = new Interop.CionClient.CionClientPayloadAsyncResultCb(
289                 (IntPtr result, IntPtr userData) =>
290                 {
291                     TaskCompletionSource<PayloadAsyncResult> tcsToReturn = _tcsDictionary[payload.Id];
292                     PayloadAsyncResult resultPayload = null;
293                     try
294                     {
295                         resultPayload = PayloadAsyncResult.CreateFromHandle(result);
296                     }
297                     catch (Exception e)
298                     {
299                         Log.Error(LogTag, string.Format("Failed to create PayloadAsyncResult from result handle: {0}.", e.Message));
300                         tcsToReturn.SetException(e);
301                         return;
302                     }
303                     tcsToReturn.SetResult(resultPayload);
304                     _payloadAsyncCbDictionary.Remove(resultPayload.PayloadId);
305                     _tcsDictionary.Remove(resultPayload.PayloadId);
306                 });
307
308             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientSendPayloadAsync(_handle, payload?._handle, _payloadAsyncResultCb, IntPtr.Zero);
309             if (ret != Interop.Cion.ErrorCode.None)
310             {
311                 throw CionErrorFactory.GetException(ret, "Failed to send payload.");
312             }
313
314             _payloadAsyncCbDictionary[payload?.Id] = cb;
315
316             return tcs.Task;
317         }
318
319         /// <summary>
320         /// The result callback of connection request.
321         /// </summary>
322         /// <param name="peerInfo">The peer info of the cion server.</param>
323         /// <param name="result">The result of the connection.</param>
324         /// <since_tizen> 9 </since_tizen>
325         protected abstract void OnConnectionResult(PeerInfo peerInfo, ConnectionResult result);
326
327         /// <summary>
328         /// The callback invoked when received payload.
329         /// </summary>
330         /// <param name="payload">The received payload.</param>
331         /// <param name="status">The status of sent payload.</param>
332         /// <since_tizen> 9 </since_tizen>
333         protected abstract void OnPayloadReceived(Payload payload, PayloadTransferStatus status);
334
335         /// <summary>
336         /// The callback invoked when the cion server discovered.
337         /// </summary>
338         /// <param name="peerInfo">The peer info of discovered cion server.</param>
339         /// <since_tizen> 9 </since_tizen>
340         protected abstract void OnDiscovered(PeerInfo peerInfo);
341
342         /// <summary>
343         /// The callback invoked when disconnected with cion client.
344         /// </summary>
345         /// <param name="peerInfo">The peer info of the cion server.</param>
346         /// <since_tizen> 9 </since_tizen>
347         protected abstract void OnDisconnected(PeerInfo peerInfo);
348
349         #region IDisposable Support
350         private bool disposedValue = false;
351
352         /// <summary>
353         /// Releases any unmanaged resources used by this object. Can also dispose any other disposable objects.
354         /// </summary>
355         /// <param name="disposing">If true, disposes any disposable objects. If false, does not dispose disposable objects.</param>
356         /// <since_tizen> 9 </since_tizen>
357         protected virtual void Dispose(bool disposing)
358         {
359             if (!disposedValue)
360             {
361                 if (disposing)
362                 {
363                     _handle.Dispose();
364                 }
365                 disposedValue = true;
366             }
367         }
368
369         /// <summary>
370         /// Releases all resources used by the ClientBase class.
371         /// </summary>
372         /// <since_tizen> 9 </since_tizen>
373         public void Dispose()
374         {
375             Dispose(true);
376             GC.SuppressFinalize(this);
377         }
378         #endregion
379     }
380 }