33using System . Collections . Generic ;
44using System . Linq ;
55using System . Text ;
6- using System . Net ;
76using System . Reflection ;
8- using System . Web ;
97using Newtonsoft . Json ;
108using System . IO ;
9+ using System . Net . Http ;
10+ using System . Threading . Tasks ;
11+ using System . Net ;
1112
1213namespace Ipfs . Api
1314{
@@ -19,9 +20,15 @@ namespace Ipfs.Api
1920 /// </remarks>
2021 /// <seealso href="https://ipfs.io/docs/api/">IPFS API</seealso>
2122 /// <seealso href="https://ipfs.io/docs/commands/">IPFS commands</seealso>
23+ /// <remarks>
24+ /// <b>IpfsClient</b> is thread safe, only one instance is required
25+ /// by the application.
26+ /// </remarks>
2227 public partial class IpfsClient
2328 {
2429 static ILog log = LogManager . GetCurrentClassLogger ( ) ;
30+ static object safe = new object ( ) ;
31+ static HttpClient api = null ;
2532
2633 /// <summary>
2734 /// The default URL to the IPFS API server. The default is "http://localhost:5001".
@@ -83,7 +90,7 @@ Uri BuildCommand(string command, string arg = null, params string[] options)
8390 if ( arg != null )
8491 {
8592 q . Append ( "&arg=" ) ;
86- q . Append ( HttpUtility . UrlEncode ( arg ) ) ;
93+ q . Append ( WebUtility . UrlEncode ( arg ) ) ;
8794 }
8895
8996 foreach ( var option in options )
@@ -98,7 +105,7 @@ Uri BuildCommand(string command, string arg = null, params string[] options)
98105 {
99106 q . Append ( option . Substring ( 0 , i ) ) ;
100107 q . Append ( '=' ) ;
101- q . Append ( HttpUtility . UrlEncode ( option . Substring ( i + 1 ) ) ) ;
108+ q . Append ( WebUtility . UrlEncode ( option . Substring ( i + 1 ) ) ) ;
102109 }
103110 }
104111
@@ -112,13 +119,28 @@ Uri BuildCommand(string command, string arg = null, params string[] options)
112119 return new Uri ( ApiUri , url ) ;
113120 }
114121
115- WebClient Api ( )
122+ /// <summary>
123+ /// Get the IPFS API.
124+ /// </summary>
125+ /// <returns>
126+ /// A <see cref="HttpClient"/>.
127+ /// </returns>
128+ /// <remarks>
129+ /// Only one client is needed. Its thread safe.
130+ /// </remarks>
131+ HttpClient Api ( )
116132 {
117- var api = new WebClient
133+ if ( api == null )
118134 {
119- Encoding = Encoding . UTF8
120- } ;
121- api . Headers [ "User-Agent" ] = UserAgent ;
135+ lock ( safe )
136+ {
137+ if ( api == null )
138+ {
139+ api = new HttpClient ( ) ;
140+ api . DefaultRequestHeaders . Add ( "User-Agent" , UserAgent ) ;
141+ }
142+ }
143+ }
122144 return api ;
123145 }
124146
@@ -138,17 +160,25 @@ WebClient Api()
138160 /// <returns>
139161 /// A string representation of the command's result.
140162 /// </returns>
141- public string DoCommand ( string command , string arg = null , params string [ ] options )
163+ public async Task < string > DoCommandAsync ( string command , string arg = null , params string [ ] options )
142164 {
143165 try
144166 {
145167 var url = BuildCommand ( command , arg , options ) ;
146168 if ( log . IsDebugEnabled )
147169 log . Debug ( "GET " + url . ToString ( ) ) ;
148- var s = Api ( ) . DownloadString ( url ) ;
149- if ( log . IsDebugEnabled )
150- log . Debug ( "RSP " + s ) ;
151- return s ;
170+ using ( var response = await Api ( ) . GetAsync ( url ) )
171+ {
172+ await ThrowOnError ( response ) ;
173+ var body = await response . Content . ReadAsStringAsync ( ) ;
174+ if ( log . IsDebugEnabled )
175+ log . Debug ( "RSP " + body ) ;
176+ return body ;
177+ }
178+ }
179+ catch ( IpfsException )
180+ {
181+ throw ;
152182 }
153183 catch ( Exception e )
154184 {
@@ -180,9 +210,9 @@ public string DoCommand(string command, string arg = null, params string[] optio
180210 /// The command's response is converted to <typeparamref name="T"/> using
181211 /// <c>JsonConvert</c>.
182212 /// </remarks>
183- public T DoCommand < T > ( string command , string arg = null , params string [ ] options )
213+ public async Task < T > DoCommandAsync < T > ( string command , string arg = null , params string [ ] options )
184214 {
185- var json = DoCommand ( command , arg , options ) ;
215+ var json = await DoCommandAsync ( command , arg , options ) ;
186216 return JsonConvert . DeserializeObject < T > ( json ) ;
187217 }
188218
@@ -203,20 +233,120 @@ public T DoCommand<T>(string command, string arg = null, params string[] options
203233 /// <returns>
204234 /// A <see cref="Stream"/> containing the command's result.
205235 /// </returns>
206- public Stream Download ( string command , string arg = null , params string [ ] options )
236+ public async Task < Stream > DownloadAsync ( string command , string arg = null , params string [ ] options )
207237 {
208238 try
209239 {
210240 var url = BuildCommand ( command , arg , options ) ;
211241 if ( log . IsDebugEnabled )
212242 log . Debug ( "GET " + url . ToString ( ) ) ;
213- return Api ( ) . OpenRead ( url ) ;
243+ var response = await Api ( ) . GetAsync ( url ) ;
244+ await ThrowOnError ( response ) ;
245+ return await response . Content . ReadAsStreamAsync ( ) ;
246+ }
247+ catch ( IpfsException )
248+ {
249+ throw ;
214250 }
215251 catch ( Exception e )
216252 {
217253 throw new IpfsException ( e ) ;
218254 }
219255 }
220-
256+
257+ /// <summary>
258+ /// Perform an <see href="https://ipfs.io/docs/api/">IPFS API command</see> returning a string.
259+ /// </summary>
260+ /// <param name="command">
261+ /// The <see href="https://ipfs.io/docs/api/">IPFS API command</see>, such as
262+ /// <see href="https://ipfs.io/docs/api/#apiv0filels">"file/ls"</see>.
263+ /// </param>
264+ /// <param name="arg">
265+ /// The optional argument to the command.
266+ /// </param>
267+ /// <param name="options">
268+ /// The optional flags to the command.
269+ /// </param>
270+ /// <returns>
271+ /// A string representation of the command's result.
272+ /// </returns>
273+ public string DoCommand ( string command , string arg = null , params string [ ] options )
274+ {
275+ return DoCommandAsync ( command , arg , options ) . Result ;
276+ }
277+
278+ /// <summary>
279+ /// Perform an <see href="https://ipfs.io/docs/api/">IPFS API command</see> returning
280+ /// a specific <see cref="Type"/>.
281+ /// </summary>
282+ /// <typeparam name="T">
283+ /// The <see cref="Type"/> of object to return.
284+ /// </typeparam>
285+ /// <param name="command">
286+ /// The <see href="https://ipfs.io/docs/api/">IPFS API command</see>, such as
287+ /// <see href="https://ipfs.io/docs/api/#apiv0filels">"file/ls"</see>.
288+ /// </param>
289+ /// <param name="arg">
290+ /// The optional argument to the command.
291+ /// </param>
292+ /// <param name="options">
293+ /// The optional flags to the command.
294+ /// </param>
295+ /// <returns>
296+ /// A <typeparamref name="T"/>.
297+ /// </returns>
298+ /// <remarks>
299+ /// The command's response is converted to <typeparamref name="T"/> using
300+ /// <c>JsonConvert</c>.
301+ /// </remarks>
302+ public T DoCommand < T > ( string command , string arg = null , params string [ ] options )
303+ {
304+ return DoCommandAsync < T > ( command , arg , options ) . Result ;
305+ }
306+
307+ /// <summary>
308+ /// Perform an <see href="https://ipfs.io/docs/api/">IPFS API command</see> returning a
309+ /// <see cref="Stream"/>.
310+ /// </summary>
311+ /// <param name="command">
312+ /// The <see href="https://ipfs.io/docs/api/">IPFS API command</see>, such as
313+ /// <see href="https://ipfs.io/docs/api/#apiv0filels">"file/ls"</see>.
314+ /// </param>
315+ /// <param name="arg">
316+ /// The optional argument to the command.
317+ /// </param>
318+ /// <param name="options">
319+ /// The optional flags to the command.
320+ /// </param>
321+ /// <returns>
322+ /// A <see cref="Stream"/> containing the command's result.
323+ /// </returns>
324+ public Stream Download ( string command , string arg = null , params string [ ] options )
325+ {
326+ return DownloadAsync ( command , arg , options ) . Result ;
327+ }
328+
329+ /// <summary>
330+ ///
331+ /// </summary>
332+ /// <param name="response"></param>
333+ /// <returns></returns>
334+ /// <remarks>
335+ /// The API server returns an JSON error in the form <c>{ "Message": "...", "Code": ... }</c>.
336+ /// </remarks>
337+ async Task < bool > ThrowOnError ( HttpResponseMessage response )
338+ {
339+ if ( response . IsSuccessStatusCode )
340+ return true ; ;
341+ if ( response . StatusCode == HttpStatusCode . NotFound )
342+ throw new IpfsException ( "Invalid command" ) ;
343+
344+ var body = await response . Content . ReadAsStringAsync ( ) ;
345+ if ( log . IsDebugEnabled )
346+ log . Debug ( "ERR " + body ) ;
347+ var message = ( string ) JsonConvert . DeserializeObject < dynamic > ( body ) . Message ;
348+ throw new IpfsException ( message ) ;
349+ }
350+
221351 }
222352}
0 commit comments