Skip to content

Commit 943a93a

Browse files
Merge pull request #20 from richardschneider/js-ipfs
Support for js-ipfs
2 parents 03186a2 + 32757e5 commit 943a93a

14 files changed

+576
-516
lines changed

doc/Documentation.csproj

Lines changed: 151 additions & 150 deletions
Large diffs are not rendered by default.

doc/articles/envvars.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Environment variables
2+
3+
The following [environment variables](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682653.aspx)
4+
are used to control the behaviour of the Ipfs Client
5+
6+
| Name | Description |
7+
| --- | --- |
8+
| IpfsHttpUrl | The default URL to the IPFS HTTP API server. See [DefaultApiUri](xref:Ipfs.Api.IpfsClient.DefaultApiUri) for more details. |

doc/articles/toc.yml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
- name: Introduction
2-
href: intro.md
3-
- name: Asynchronous I/O
4-
href: async.md
5-
items:
6-
- name: Cancellation
7-
href: cancellation.md
1+
- name: Introduction
2+
href: intro.md
3+
- name: Asynchronous I/O
4+
href: async.md
5+
items:
6+
- name: Cancellation
7+
href: cancellation.md
8+
- name: Environment variables
9+
href: envvars.md

src/CoreApi/PubSubApi.cs

Lines changed: 162 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,162 @@
1-
using Common.Logging;
2-
using Newtonsoft.Json;
3-
using Newtonsoft.Json.Linq;
4-
using System;
5-
using System.Collections.Generic;
6-
using System.IO;
7-
using System.Linq;
8-
using System.Text;
9-
using System.Threading;
10-
using System.Threading.Tasks;
11-
12-
namespace Ipfs.Api
13-
{
14-
15-
/// <summary>
16-
/// Allows you to publish messages to a given topic, and also to
17-
/// subscribe to new messages on a given topic.
18-
/// </summary>
19-
/// <remarks>
20-
/// This API is accessed via the <see cref="IpfsClient.PubSub"/> property.
21-
/// <para>
22-
/// This is an experimental feature. It is not intended in its current state
23-
/// to be used in a production environment.
24-
/// </para>
25-
/// <para>
26-
/// To use, the daemon must be run with '--enable-pubsub-experiment'.
27-
/// </para>
28-
/// </remarks>
29-
/// <seealso href="https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/PUBSUB.md">PUBSUB API</seealso>
30-
public class PubSubApi
31-
{
32-
static ILog log = LogManager.GetLogger<PubSubApi>();
33-
34-
IpfsClient ipfs;
35-
36-
internal PubSubApi(IpfsClient ipfs)
37-
{
38-
this.ipfs = ipfs;
39-
}
40-
41-
/// <summary>
42-
/// Get the subscribed topics.
43-
/// </summary>
44-
/// <param name="cancel">
45-
/// Is used to stop the task. When cancelled, the <see cref="TaskCanceledException"/> is raised.
46-
/// </param>
47-
/// <returns>
48-
/// A sequence of <see cref="string"/> for each topic.
49-
/// </returns>
50-
public async Task<IEnumerable<string>> SubscribedTopicsAsync(CancellationToken cancel = default(CancellationToken))
51-
{
52-
var json = await ipfs.DoCommandAsync("pubsub/ls", cancel);
53-
var result = JObject.Parse(json);
54-
var strings = result["Strings"] as JArray;
55-
if (strings == null) return new string[0];
56-
return strings.Select(s => (string)s);
57-
}
58-
59-
/// <summary>
60-
/// Get the peers that are pubsubing with us.
61-
/// </summary>
62-
/// <param name="topic">
63-
/// When specified, only peers pubsubing on the topic are returned.
64-
/// </param>
65-
/// <param name="cancel">
66-
/// Is used to stop the task. When cancelled, the <see cref="TaskCanceledException"/> is raised.
67-
/// </param>
68-
/// <returns>
69-
/// A sequence of <see cref="string"/> for each peer ID.
70-
/// </returns>
71-
public async Task<IEnumerable<string>> PeersAsync(string topic = null, CancellationToken cancel = default(CancellationToken))
72-
{
73-
var json = await ipfs.DoCommandAsync("pubsub/peers", cancel, topic);
74-
var result = JObject.Parse(json);
75-
var strings = result["Strings"] as JArray;
76-
if (strings == null) return new string[0];
77-
return strings.Select(s => (string)s);
78-
}
79-
80-
/// <summary>
81-
/// Publish a message to a given topic.
82-
/// </summary>
83-
/// <param name="topic">
84-
/// The topic name.
85-
/// </param>
86-
/// <param name="message">
87-
/// The message to publish.
88-
/// </param>
89-
/// <param name="cancel">
90-
/// Is used to stop the task. When cancelled, the <see cref="TaskCanceledException"/> is raised.
91-
/// </param>
92-
public async Task Publish(string topic, string message, CancellationToken cancel = default(CancellationToken))
93-
{
94-
var _ = await ipfs.PostCommandAsync("pubsub/pub", cancel, topic, "arg=" + message);
95-
return;
96-
}
97-
98-
/// <summary>
99-
/// Subscribe to messages on a given topic.
100-
/// </summary>
101-
/// <param name="topic">
102-
/// The topic name.
103-
/// </param>
104-
/// <param name="handler">
105-
/// The action to perform when a <see cref="PublishedMessage"/> is received.
106-
/// </param>
107-
/// <param name="cancellationToken">
108-
/// Is used to stop the topic listener. When cancelled, the <see cref="OperationCanceledException"/>
109-
/// is <b>NOT</b> raised.
110-
/// </param>
111-
/// <returns>
112-
/// After the topic listener is register with the IPFS server.
113-
/// </returns>
114-
/// <remarks>
115-
/// The <paramref name="handler"/> is invoked on the topic listener thread.
116-
/// </remarks>
117-
public async Task Subscribe(string topic, Action<PublishedMessage> handler, CancellationToken cancellationToken = default(CancellationToken))
118-
{
119-
var messageStream = await ipfs.PostDownloadAsync("pubsub/sub", cancellationToken, topic);
120-
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
121-
Task.Run(() => ProcessMessages(topic, handler, messageStream, cancellationToken));
122-
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
123-
return;
124-
}
125-
126-
void ProcessMessages(string topic, Action<PublishedMessage> handler, Stream stream, CancellationToken ct)
127-
{
128-
log.DebugFormat("Start listening for '{0}' messages", topic);
129-
using (var sr = new StreamReader(stream))
130-
{
131-
while (!sr.EndOfStream && !ct.IsCancellationRequested)
132-
{
133-
var json = sr.ReadLine();
134-
if (log.IsDebugEnabled)
135-
log.DebugFormat("PubSub message {0}", json);
136-
if (json != "{}" && !ct.IsCancellationRequested)
137-
{
138-
handler(new PublishedMessage(json));
139-
}
140-
}
141-
}
142-
log.DebugFormat("Stop listening for '{0}' messages", topic);
143-
}
144-
145-
}
146-
147-
}
1+
using Common.Logging;
2+
using Newtonsoft.Json;
3+
using Newtonsoft.Json.Linq;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
12+
namespace Ipfs.Api
13+
{
14+
15+
/// <summary>
16+
/// Allows you to publish messages to a given topic, and also to
17+
/// subscribe to new messages on a given topic.
18+
/// </summary>
19+
/// <remarks>
20+
/// This API is accessed via the <see cref="IpfsClient.PubSub"/> property.
21+
/// <para>
22+
/// This is an experimental feature. It is not intended in its current state
23+
/// to be used in a production environment.
24+
/// </para>
25+
/// <para>
26+
/// To use, the daemon must be run with '--enable-pubsub-experiment'.
27+
/// </para>
28+
/// </remarks>
29+
/// <seealso href="https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/PUBSUB.md">PUBSUB API</seealso>
30+
public class PubSubApi
31+
{
32+
static ILog log = LogManager.GetLogger<PubSubApi>();
33+
34+
IpfsClient ipfs;
35+
36+
internal PubSubApi(IpfsClient ipfs)
37+
{
38+
this.ipfs = ipfs;
39+
}
40+
41+
/// <summary>
42+
/// Get the subscribed topics.
43+
/// </summary>
44+
/// <param name="cancel">
45+
/// Is used to stop the task. When cancelled, the <see cref="TaskCanceledException"/> is raised.
46+
/// </param>
47+
/// <returns>
48+
/// A sequence of <see cref="string"/> for each topic.
49+
/// </returns>
50+
public async Task<IEnumerable<string>> SubscribedTopicsAsync(CancellationToken cancel = default(CancellationToken))
51+
{
52+
var json = await ipfs.DoCommandAsync("pubsub/ls", cancel);
53+
var result = JObject.Parse(json);
54+
var strings = result["Strings"] as JArray;
55+
if (strings == null) return new string[0];
56+
return strings.Select(s => (string)s);
57+
}
58+
59+
/// <summary>
60+
/// Get the peers that are pubsubing with us.
61+
/// </summary>
62+
/// <param name="topic">
63+
/// When specified, only peers pubsubing on the topic are returned.
64+
/// </param>
65+
/// <param name="cancel">
66+
/// Is used to stop the task. When cancelled, the <see cref="TaskCanceledException"/> is raised.
67+
/// </param>
68+
/// <returns>
69+
/// A sequence of <see cref="string"/> for each peer ID.
70+
/// </returns>
71+
public async Task<IEnumerable<string>> PeersAsync(string topic = null, CancellationToken cancel = default(CancellationToken))
72+
{
73+
var json = await ipfs.DoCommandAsync("pubsub/peers", cancel, topic);
74+
var result = JObject.Parse(json);
75+
var strings = result["Strings"] as JArray;
76+
if (strings == null) return new string[0];
77+
return strings.Select(s => (string)s);
78+
}
79+
80+
/// <summary>
81+
/// Publish a message to a given topic.
82+
/// </summary>
83+
/// <param name="topic">
84+
/// The topic name.
85+
/// </param>
86+
/// <param name="message">
87+
/// The message to publish.
88+
/// </param>
89+
/// <param name="cancel">
90+
/// Is used to stop the task. When cancelled, the <see cref="TaskCanceledException"/> is raised.
91+
/// </param>
92+
public async Task Publish(string topic, string message, CancellationToken cancel = default(CancellationToken))
93+
{
94+
var _ = await ipfs.PostCommandAsync("pubsub/pub", cancel, topic, "arg=" + message);
95+
return;
96+
}
97+
98+
/// <summary>
99+
/// Subscribe to messages on a given topic.
100+
/// </summary>
101+
/// <param name="topic">
102+
/// The topic name.
103+
/// </param>
104+
/// <param name="handler">
105+
/// The action to perform when a <see cref="PublishedMessage"/> is received.
106+
/// </param>
107+
/// <param name="cancellationToken">
108+
/// Is used to stop the topic listener. When cancelled, the <see cref="OperationCanceledException"/>
109+
/// is <b>NOT</b> raised.
110+
/// </param>
111+
/// <returns>
112+
/// After the topic listener is register with the IPFS server.
113+
/// </returns>
114+
/// <remarks>
115+
/// The <paramref name="handler"/> is invoked on the topic listener thread.
116+
/// </remarks>
117+
public async Task Subscribe(string topic, Action<PublishedMessage> handler, CancellationToken cancellationToken)
118+
{
119+
var messageStream = await ipfs.PostDownloadAsync("pubsub/sub", cancellationToken, topic);
120+
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
121+
Task.Run(() => ProcessMessages(topic, handler, messageStream, cancellationToken));
122+
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
123+
return;
124+
}
125+
126+
void ProcessMessages(string topic, Action<PublishedMessage> handler, Stream stream, CancellationToken ct)
127+
{
128+
log.DebugFormat("Start listening for '{0}' messages", topic);
129+
130+
using (var sr = new StreamReader(stream))
131+
{
132+
// .Net needs a ReadLine(CancellationToken)
133+
// As a work-around, we register a function to close the stream
134+
ct.Register(() => sr.Dispose());
135+
try
136+
{
137+
while (!sr.EndOfStream && !ct.IsCancellationRequested)
138+
{
139+
var json = sr.ReadLine();
140+
if (json == null)
141+
break;
142+
if (log.IsDebugEnabled)
143+
log.DebugFormat("PubSub message {0}", json);
144+
if (json != "{}" && !ct.IsCancellationRequested)
145+
{
146+
handler(new PublishedMessage(json));
147+
}
148+
}
149+
}
150+
catch (Exception e)
151+
{
152+
// Do not report errors when cancelled.
153+
if (!ct.IsCancellationRequested)
154+
log.Error(e);
155+
}
156+
}
157+
log.DebugFormat("Stop listening for '{0}' messages", topic);
158+
}
159+
160+
}
161+
162+
}

src/CoreApi/SwarmApi.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ internal SwarmApi(IpfsClient ipfs)
100100

101101
TimeSpan ParseLatency(string latency)
102102
{
103-
if (latency == "n/a")
103+
if (latency == "n/a" || latency == "unknown")
104104
{
105105
return TimeSpan.Zero;
106106
}

src/IpfsApi.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
</PropertyGroup>
2929

3030
<ItemGroup>
31-
<PackageReference Include="Ipfs.Core" Version="0.8.3" />
31+
<PackageReference Include="Ipfs.Core" Version="0.10.0" />
3232
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
3333
<PackageReference Include="System.Net.Http" Version="4.3.3" Condition="'$(TargetFramework)' == 'netstandard14'" />
3434
<PackageReference Include="System.Net.Http" Version="4.3.3" Condition="'$(TargetFramework)' == 'net45'" />

src/IpfsClient.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,17 @@ public partial class IpfsClient
3333
static HttpClient api = null;
3434

3535
/// <summary>
36-
/// The default URL to the IPFS API server. The default is "http://localhost:5001".
36+
/// The default URL to the IPFS HTTP API server.
3737
/// </summary>
38-
public static Uri DefaultApiUri = new Uri("http://localhost:5001");
38+
/// <value>
39+
/// The default is "http://localhost:5001".
40+
/// </value>
41+
/// <remarks>
42+
/// The environment variable "IpfsHttpApi" overrides this value.
43+
/// </remarks>
44+
public static Uri DefaultApiUri = new Uri(
45+
Environment.GetEnvironmentVariable("IpfsHttpApi")
46+
?? "http://localhost:5001");
3947

4048
/// <summary>
4149
/// Creates a new instance of the <see cref="IpfsClient"/> class and sets the
@@ -508,7 +516,12 @@ async Task<bool> ThrowOnErrorAsync(HttpResponseMessage response)
508516
if (response.IsSuccessStatusCode)
509517
return true;
510518
if (response.StatusCode == HttpStatusCode.NotFound)
511-
throw new HttpRequestException("Invalid IPFS command");
519+
{
520+
var error = "Invalid IPFS command: " + response.RequestMessage.RequestUri.ToString();
521+
if (log.IsDebugEnabled)
522+
log.Debug("ERR " + error);
523+
throw new HttpRequestException(error);
524+
}
512525

513526
var body = await response.Content.ReadAsStringAsync();
514527
if (log.IsDebugEnabled)

0 commit comments

Comments
 (0)