diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..838af7c --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,9 @@ + + + + enable + true + 10.0 + + + diff --git a/src/Block.cs b/src/Block.cs index a4cec47..ede6f07 100644 --- a/src/Block.cs +++ b/src/Block.cs @@ -1,33 +1,34 @@ -using System.IO; +using System; +using System.IO; using System.Runtime.Serialization; - -namespace Ipfs.Http -{ - /// - [DataContract] - public class Block : IDataBlock - { - long? size; - - /// - [DataMember] - public Cid Id { get; set; } - - /// - [DataMember] - public byte[] DataBytes { get; set; } - - /// - public Stream DataStream - { - get - { - return new MemoryStream(DataBytes, false); - } + +namespace Ipfs.Http +{ + /// + [DataContract] + public class Block : IDataBlock + { + private long? size; + private Cid? id; + + /// + [DataMember] + public Cid Id + { + get => id ?? throw new InvalidDataException("Value must be initialized"); + + set => id = value; } - /// - [DataMember] + /// + [DataMember] + public byte[] DataBytes { get; set; } = Array.Empty(); + + /// + public Stream DataStream => new MemoryStream(DataBytes, false); + + /// + [DataMember] public long Size { get @@ -43,7 +44,5 @@ public long Size size = value; } } - - } - -} + } +} diff --git a/src/CoreApi/BitswapApi.cs b/src/CoreApi/BitswapApi.cs index dc3af6c..c97e2c0 100644 --- a/src/CoreApi/BitswapApi.cs +++ b/src/CoreApi/BitswapApi.cs @@ -1,58 +1,57 @@ using Ipfs.CoreApi; -using Newtonsoft.Json.Linq; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; -namespace Ipfs.Http -{ - class BitswapApi : IBitswapApi - { - IpfsClient ipfs; - - internal BitswapApi(IpfsClient ipfs) - { - this.ipfs = ipfs; +namespace Ipfs.Http +{ + class BitswapApi : IBitswapApi + { + private readonly IpfsClient ipfs; + + internal BitswapApi(IpfsClient ipfs) + { + this.ipfs = ipfs; } - public Task GetAsync(Cid id, CancellationToken cancel = default(CancellationToken)) + public Task GetAsync(Cid id, CancellationToken cancel = default) { return ipfs.Block.GetAsync(id, cancel); } - public async Task> WantsAsync(MultiHash peer = null, CancellationToken cancel = default(CancellationToken)) + public async Task> WantsAsync(MultiHash? peer = null, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("bitswap/wantlist", cancel, peer?.ToString()); - var keys = (JArray)(JObject.Parse(json)["Keys"]); - // https://github.com/ipfs/go-ipfs/issues/5077 - return keys + var keys = (JArray?)(JObject.Parse(json)["Keys"]); + // https://github.com/ipfs/go-ipfs/issues/5077 + return keys .Select(k => { if (k.Type == JTokenType.String) - return Cid.Decode(k.ToString()); + return Cid.Decode(k.ToString()); var obj = (JObject)k; - return Cid.Decode(obj["/"].ToString()); - }); + return Cid.Decode(obj["/"]!.ToString()); + }); } - - public async Task UnwantAsync(Cid id, CancellationToken cancel = default(CancellationToken)) + + public async Task UnwantAsync(Cid id, CancellationToken cancel = default) { await ipfs.DoCommandAsync("bitswap/unwant", cancel, id); } - public async Task LedgerAsync(Peer peer, CancellationToken cancel = default(CancellationToken)) + public async Task LedgerAsync(Peer peer, CancellationToken cancel = default) { - var json = await ipfs.DoCommandAsync("bitswap/ledger", cancel, peer.Id.ToString()); + var json = await ipfs.DoCommandAsync("bitswap/ledger", cancel, peer.Id?.ToString()); var o = JObject.Parse(json); return new BitswapLedger { - Peer = (string)o["Peer"], - DataReceived = (ulong)o["Sent"], - DataSent = (ulong)o["Recv"], - BlocksExchanged = (ulong)o["Exchanged"] + Peer = (string)o["Peer"]!, + DataReceived = (ulong?)o["Sent"] ?? 0, + DataSent = (ulong?)o["Recv"] ?? 0, + BlocksExchanged = (ulong?)o["Exchanged"] ?? 0, }; } - } - -} + } +} diff --git a/src/CoreApi/BlockApi.cs b/src/CoreApi/BlockApi.cs index 7b1eb0b..dfed8f4 100644 --- a/src/CoreApi/BlockApi.cs +++ b/src/CoreApi/BlockApi.cs @@ -1,41 +1,41 @@ using Ipfs.CoreApi; -using Newtonsoft.Json.Linq; -using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; using System.IO; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; -namespace Ipfs.Http -{ - class BlockApi : IBlockApi - { - IpfsClient ipfs; - - internal BlockApi(IpfsClient ipfs) - { - this.ipfs = ipfs; - } - - public async Task GetAsync(Cid id, CancellationToken cancel = default(CancellationToken)) - { - var data = await ipfs.DownloadBytesAsync("block/get", cancel, id); - return new Block - { - DataBytes = data, - Id = id - }; - } - - public async Task PutAsync( +namespace Ipfs.Http +{ + class BlockApi : IBlockApi + { + private readonly IpfsClient ipfs; + + internal BlockApi(IpfsClient ipfs) + { + this.ipfs = ipfs; + } + + public async Task GetAsync(Cid id, CancellationToken cancel = default) + { + var data = await ipfs.DownloadBytesAsync("block/get", cancel, id); + return new Block + { + DataBytes = data, + Id = id + }; + } + + public async Task PutAsync( byte[] data, - string contentType = Cid.DefaultContentType, - string multiHash = MultiHash.DefaultAlgorithmName, - string encoding = MultiBase.DefaultAlgorithmName, - bool pin = false, - CancellationToken cancel = default(CancellationToken)) - { - var options = new List(); + string contentType = Cid.DefaultContentType, + string multiHash = MultiHash.DefaultAlgorithmName, + string encoding = MultiBase.DefaultAlgorithmName, + bool pin = false, + CancellationToken cancel = default) + { + var options = new List(); if (multiHash != MultiHash.DefaultAlgorithmName || contentType != Cid.DefaultContentType || encoding != MultiBase.DefaultAlgorithmName) @@ -43,28 +43,28 @@ public async Task PutAsync( options.Add($"mhtype={multiHash}"); options.Add($"format={contentType}"); options.Add($"cid-base={encoding}"); - } + } var json = await ipfs.UploadAsync("block/put", cancel, data, options.ToArray()); var info = JObject.Parse(json); - Cid cid = (string)info["Key"]; - + Cid cid = (string)info["Key"]!; + if (pin) { await ipfs.Pin.AddAsync(cid, recursive: false, cancel: cancel); - } - - return cid; - } - - public async Task PutAsync( + } + + return cid; + } + + public async Task PutAsync( Stream data, - string contentType = Cid.DefaultContentType, - string multiHash = MultiHash.DefaultAlgorithmName, - string encoding = MultiBase.DefaultAlgorithmName, - bool pin = false, - CancellationToken cancel = default(CancellationToken)) - { - var options = new List(); + string contentType = Cid.DefaultContentType, + string multiHash = MultiHash.DefaultAlgorithmName, + string encoding = MultiBase.DefaultAlgorithmName, + bool pin = false, + CancellationToken cancel = default) + { + var options = new List(); if (multiHash != MultiHash.DefaultAlgorithmName || contentType != Cid.DefaultContentType || encoding != MultiBase.DefaultAlgorithmName) @@ -72,42 +72,40 @@ public async Task PutAsync( options.Add($"mhtype={multiHash}"); options.Add($"format={contentType}"); options.Add($"cid-base={encoding}"); - } - var json = await ipfs.UploadAsync("block/put", cancel, data, null, options.ToArray()); + } + var json = await ipfs.UploadAsync("block/put", cancel, data, name: null, options.ToArray()); var info = JObject.Parse(json); - Cid cid = (string)info["Key"]; - + Cid cid = (string)info["Key"]!; + if (pin) { await ipfs.Pin.AddAsync(cid, recursive: false, cancel: cancel); - } - - return cid; - } - - public async Task StatAsync(Cid id, CancellationToken cancel = default(CancellationToken)) - { - var json = await ipfs.DoCommandAsync("block/stat", cancel, id); + } + + return cid; + } + + public async Task StatAsync(Cid id, CancellationToken cancel = default) + { + var json = await ipfs.DoCommandAsync("block/stat", cancel, id); var info = JObject.Parse(json); - return new Block - { - Size = (long)info["Size"], - Id = (string)info["Key"] - }; - } - - public async Task RemoveAsync(Cid id, bool ignoreNonexistent = false, CancellationToken cancel = default(CancellationToken)) - { - var json = await ipfs.DoCommandAsync("block/rm", cancel, id, "force=" + ignoreNonexistent.ToString().ToLowerInvariant()); - if (json.Length == 0) - return null; - var result = JObject.Parse(json); - var error = (string)result["Error"]; - if (error != null) - throw new HttpRequestException(error); - return (Cid)(string)result["Hash"]; + return new Block + { + Size = (long?)info["Size"] ?? 0, + Id = (string)info["Key"]! + }; } - } - -} + public async Task RemoveAsync(Cid id, bool ignoreNonexistent = false, CancellationToken cancel = default) + { + var json = await ipfs.DoCommandAsync("block/rm", cancel, id, "force=" + ignoreNonexistent.ToString().ToLowerInvariant()); + if (json.Length == 0) + return null; + var result = JObject.Parse(json); + var error = (string?)result["Error"]; + if (error is not null) + throw new HttpRequestException(error); + return (Cid)(string)result["Hash"]!; + } + } +} diff --git a/src/CoreApi/BlockRepositoryApi.cs b/src/CoreApi/BlockRepositoryApi.cs index 2fa8803..f8745c2 100644 --- a/src/CoreApi/BlockRepositoryApi.cs +++ b/src/CoreApi/BlockRepositoryApi.cs @@ -1,39 +1,39 @@ using Ipfs.CoreApi; -using Newtonsoft.Json.Linq; -using System.Threading; -using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using System.Threading; +using System.Threading.Tasks; -namespace Ipfs.Http -{ - class BlockRepositoryApi : IBlockRepositoryApi - { - IpfsClient ipfs; - - internal BlockRepositoryApi(IpfsClient ipfs) - { - this.ipfs = ipfs; +namespace Ipfs.Http +{ + class BlockRepositoryApi : IBlockRepositoryApi + { + private readonly IpfsClient ipfs; + + internal BlockRepositoryApi(IpfsClient ipfs) + { + this.ipfs = ipfs; } - public async Task RemoveGarbageAsync(CancellationToken cancel = default(CancellationToken)) + public async Task RemoveGarbageAsync(CancellationToken cancel = default) { await ipfs.DoCommandAsync("repo/gc", cancel); } - public Task StatisticsAsync(CancellationToken cancel = default(CancellationToken)) + public Task StatisticsAsync(CancellationToken cancel = default) { return ipfs.DoCommandAsync("repo/stat", cancel); } - public async Task VerifyAsync(CancellationToken cancel = default(CancellationToken)) + public async Task VerifyAsync(CancellationToken cancel = default) { await ipfs.DoCommandAsync("repo/verify", cancel); } - public async Task VersionAsync(CancellationToken cancel = default(CancellationToken)) + public async Task VersionAsync(CancellationToken cancel = default) { - var json = await ipfs.DoCommandAsync("repo/version", cancel); + var json = await ipfs.DoCommandAsync("repo/version", cancel); var info = JObject.Parse(json); - return (string)info["Version"]; + return (string)info["Version"]!; } - } -} + } +} diff --git a/src/CoreApi/BootstrapApi.cs b/src/CoreApi/BootstrapApi.cs index fea4bea..ef9ea9e 100644 --- a/src/CoreApi/BootstrapApi.cs +++ b/src/CoreApi/BootstrapApi.cs @@ -1,63 +1,74 @@ using Ipfs.CoreApi; -using Newtonsoft.Json.Linq; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Ipfs.Http -{ - class BootstrapApi : IBootstrapApi - { - IpfsClient ipfs; - - internal BootstrapApi(IpfsClient ipfs) - { - this.ipfs = ipfs; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Ipfs.Http +{ + class BootstrapApi : IBootstrapApi + { + private readonly IpfsClient ipfs; + + internal BootstrapApi(IpfsClient ipfs) + { + this.ipfs = ipfs; } - public async Task AddAsync(MultiAddress address, CancellationToken cancel = default(CancellationToken)) + public async Task AddAsync(MultiAddress address, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("bootstrap/add", cancel, address.ToString()); - var addrs = (JArray)(JObject.Parse(json)["Peers"]); + var addrs = (JArray?)(JObject.Parse(json)["Peers"]); var a = addrs.FirstOrDefault(); - if (a == null) + if (a is null) return null; - return new MultiAddress((string)a); + return new MultiAddress((string)a!); } - public async Task> AddDefaultsAsync(CancellationToken cancel = default(CancellationToken)) + public async Task> AddDefaultsAsync(CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("bootstrap/add/default", cancel); - var addrs = (JArray)(JObject.Parse(json)["Peers"]); + var addrs = (JArray?)(JObject.Parse(json)["Peers"]); + if (addrs is null) + { + return Enumerable.Empty(); + } + return addrs - .Select(a => MultiAddress.TryCreate((string)a)) - .Where(ma => ma != null); + .Select(a => MultiAddress.TryCreate((string)a!)) + .Where(ma => ma is not null) + .Cast(); } - public async Task> ListAsync(CancellationToken cancel = default(CancellationToken)) + public async Task> ListAsync(CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("bootstrap/list", cancel); - var addrs = (JArray)(JObject.Parse(json)["Peers"]); + var addrs = (JArray?)(JObject.Parse(json)["Peers"]); + if (addrs is null) + { + return Enumerable.Empty(); + } + return addrs - .Select(a => MultiAddress.TryCreate((string)a)) - .Where(ma => ma != null); + .Select(a => MultiAddress.TryCreate((string)a!)) + .Where(ma => ma is not null) + .Cast(); } - public Task RemoveAllAsync(CancellationToken cancel = default(CancellationToken)) + public Task RemoveAllAsync(CancellationToken cancel = default) { return ipfs.DoCommandAsync("bootstrap/rm/all", cancel); } - public async Task RemoveAsync(MultiAddress address, CancellationToken cancel = default(CancellationToken)) + public async Task RemoveAsync(MultiAddress address, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("bootstrap/rm", cancel, address.ToString()); - var addrs = (JArray)(JObject.Parse(json)["Peers"]); + var addrs = (JArray?)(JObject.Parse(json)["Peers"]); var a = addrs.FirstOrDefault(); - if (a == null) + if (a is null) return null; - return new MultiAddress((string)a); + return new MultiAddress((string)a!); } - } - -} + } +} diff --git a/src/CoreApi/ConfigApi.cs b/src/CoreApi/ConfigApi.cs index 60da13f..723173a 100644 --- a/src/CoreApi/ConfigApi.cs +++ b/src/CoreApi/ConfigApi.cs @@ -9,46 +9,43 @@ namespace Ipfs.Http { class ConfigApi : IConfigApi { - IpfsClient ipfs; + private readonly IpfsClient ipfs; internal ConfigApi(IpfsClient ipfs) { this.ipfs = ipfs; } - public async Task GetAsync(CancellationToken cancel = default(CancellationToken)) + public async Task GetAsync(CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("config/show", cancel); return JObject.Parse(json); } - public async Task GetAsync(string key, CancellationToken cancel = default(CancellationToken)) + public async Task GetAsync(string key, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("config", cancel, key); var r = JObject.Parse(json); - return r["Value"]; + return r["Value"]!; } - public async Task SetAsync(string key, string value, CancellationToken cancel = default(CancellationToken)) + public Task SetAsync(string key, string value, CancellationToken cancel = default) { - var _ = await ipfs.DoCommandAsync("config", cancel, key, "arg=" + value); - return; + return ipfs.DoCommandAsync("config", cancel, key, "arg=" + value); } - public async Task SetAsync(string key, JToken value, CancellationToken cancel = default(CancellationToken)) + public Task SetAsync(string key, JToken value, CancellationToken cancel = default) { - var _ = await ipfs.DoCommandAsync("config", cancel, + return ipfs.DoCommandAsync("config", cancel, key, "arg=" + value.ToString(Formatting.None), "json=true"); - return; } - public async Task ReplaceAsync(JObject config) + public Task ReplaceAsync(JObject config) { var data = Encoding.UTF8.GetBytes(config.ToString(Formatting.None)); - await ipfs.UploadAsync("config/replace", CancellationToken.None, data); + return ipfs.UploadAsync("config/replace", CancellationToken.None, data); } } - } diff --git a/src/CoreApi/DagApi.cs b/src/CoreApi/DagApi.cs index 6561c95..6c053af 100644 --- a/src/CoreApi/DagApi.cs +++ b/src/CoreApi/DagApi.cs @@ -1,31 +1,31 @@ -using Ipfs.CoreApi; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Ipfs.CoreApi; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System.Globalization; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; -namespace Ipfs.Http -{ - class DagApi : IDagApi - { - private IpfsClient ipfs; - - internal DagApi(IpfsClient ipfs) - { - this.ipfs = ipfs; - } - - public async Task PutAsync( +namespace Ipfs.Http +{ + class DagApi : IDagApi + { + private readonly IpfsClient ipfs; + + internal DagApi(IpfsClient ipfs) + { + this.ipfs = ipfs; + } + + public async Task PutAsync( JObject data, string contentType = "dag-cbor", - string multiHash = MultiHash.DefaultAlgorithmName, - string encoding = MultiBase.DefaultAlgorithmName, - bool pin = true, - CancellationToken cancel = default(CancellationToken)) - { + string multiHash = MultiHash.DefaultAlgorithmName, + string encoding = MultiBase.DefaultAlgorithmName, + bool pin = true, + CancellationToken cancel = default) + { using (var ms = new MemoryStream()) { using (var sw = new StreamWriter(ms, new UTF8Encoding(false), 4096, true) { AutoFlush = true }) @@ -45,10 +45,10 @@ public async Task PutAsync( public async Task PutAsync( object data, string contentType = "dag-cbor", - string multiHash = MultiHash.DefaultAlgorithmName, - string encoding = MultiBase.DefaultAlgorithmName, + string multiHash = MultiHash.DefaultAlgorithmName, + string encoding = MultiBase.DefaultAlgorithmName, bool pin = true, - CancellationToken cancel = default(CancellationToken)) + CancellationToken cancel = default) { using (var ms = new MemoryStream( Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)), @@ -56,47 +56,43 @@ public async Task PutAsync( { return await PutAsync(ms, contentType, multiHash, encoding, pin, cancel); } - } + } public async Task PutAsync( Stream data, string contentType = "dag-cbor", - string multiHash = MultiHash.DefaultAlgorithmName, - string encoding = MultiBase.DefaultAlgorithmName, + string multiHash = MultiHash.DefaultAlgorithmName, + string encoding = MultiBase.DefaultAlgorithmName, bool pin = true, - CancellationToken cancel = default(CancellationToken)) + CancellationToken cancel = default) { var json = await ipfs.UploadAsync("dag/put", cancel, - data, null, + data, + name: null, $"format={contentType}", $"pin={pin.ToString().ToLowerInvariant()}", $"hash={multiHash}", $"cid-base={encoding}"); var result = JObject.Parse(json); - return (Cid)(string)result["Cid"]["/"]; + return (Cid)(string)result["Cid"]!["/"]!; } - public async Task GetAsync( - Cid id, - CancellationToken cancel = default(CancellationToken)) + public async Task GetAsync(Cid id, CancellationToken cancel = default) { - var json = await ipfs.DoCommandAsync("dag/get", cancel, id); + var json = await ipfs.DoCommandAsync("dag/get", cancel, id); return JObject.Parse(json); } - - public async Task GetAsync( - string path, - CancellationToken cancel = default(CancellationToken)) + public async Task GetAsync(string path, CancellationToken cancel = default) { - var json = await ipfs.DoCommandAsync("dag/get", cancel, path); + var json = await ipfs.DoCommandAsync("dag/get", cancel, path); return JToken.Parse(json); } - public async Task GetAsync(Cid id, CancellationToken cancel = default(CancellationToken)) + public async Task GetAsync(Cid id, CancellationToken cancel = default) { - var json = await ipfs.DoCommandAsync("dag/get", cancel, id); - return JsonConvert.DeserializeObject(json); + var json = await ipfs.DoCommandAsync("dag/get", cancel, id); + return JsonConvert.DeserializeObject(json); } - } -} + } +} diff --git a/src/CoreApi/DhtApi.cs b/src/CoreApi/DhtApi.cs index 55ff741..0757533 100644 --- a/src/CoreApi/DhtApi.cs +++ b/src/CoreApi/DhtApi.cs @@ -1,89 +1,88 @@ -using Ipfs.CoreApi; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Ipfs.Http -{ - class DhtApi : IDhtApi - { - private IpfsClient ipfs; - - internal DhtApi(IpfsClient ipfs) - { - this.ipfs = ipfs; - } - - public Task FindPeerAsync(MultiHash id, CancellationToken cancel = default(CancellationToken)) - { - return ipfs.IdAsync(id, cancel); - } - - public async Task> FindProvidersAsync(Cid id, int limit = 20, Action providerFound = null, CancellationToken cancel = default(CancellationToken)) +using Ipfs.CoreApi; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Ipfs.Http +{ + class DhtApi : IDhtApi + { + private readonly IpfsClient ipfs; + + internal DhtApi(IpfsClient ipfs) + { + this.ipfs = ipfs; + } + + public Task FindPeerAsync(MultiHash id, CancellationToken cancel = default) + { + return ipfs.IdAsync(id, cancel); + } + + public async Task> FindProvidersAsync(Cid id, int limit = 20, Action? providerFound = null, CancellationToken cancel = default) { // TODO: providerFound action - var stream = await ipfs.PostDownloadAsync("dht/findprovs", cancel, id, $"num-providers={limit}"); - return ProviderFromStream(stream, limit); + var stream = await ipfs.PostDownloadAsync("dht/findprovs", cancel, id, $"num-providers={limit}"); + return ProviderFromStream(stream, limit); } - public Task GetAsync(byte[] key, CancellationToken cancel = default(CancellationToken)) + public Task GetAsync(byte[] key, CancellationToken cancel = default) { throw new NotImplementedException(); } - public Task ProvideAsync(Cid cid, bool advertise = true, CancellationToken cancel = default(CancellationToken)) + public Task ProvideAsync(Cid cid, bool advertise = true, CancellationToken cancel = default) { throw new NotImplementedException(); } - public Task PutAsync(byte[] key, out byte[] value, CancellationToken cancel = default(CancellationToken)) + public Task PutAsync(byte[] key, out byte[] value, CancellationToken cancel = default) { throw new NotImplementedException(); } - public Task TryGetAsync(byte[] key, out byte[] value, CancellationToken cancel = default(CancellationToken)) + public Task TryGetAsync(byte[] key, out byte[] value, CancellationToken cancel = default) { throw new NotImplementedException(); } - IEnumerable ProviderFromStream(Stream stream, int limit = int.MaxValue) + IEnumerable ProviderFromStream(Stream stream, int limit = int.MaxValue) { - using (var sr = new StreamReader(stream)) - { - var n = 0; - while (!sr.EndOfStream && n < limit) - { - var json = sr.ReadLine(); - - var r = JObject.Parse(json); - var id = (string)r["ID"]; - if (id != String.Empty) + using (var sr = new StreamReader(stream)) + { + var n = 0; + while (!sr.EndOfStream && n < limit) + { + var json = sr.ReadLine(); + + var r = JObject.Parse(json); + var id = (string?)r["ID"]; + if (!string.IsNullOrEmpty(id)) { ++n; - yield return new Peer { Id = new MultiHash(id) }; - } - else - { - var responses = (JArray)r["Responses"]; - if (responses != null) - { - foreach (var response in responses) - { - var rid = (string)response["ID"]; - if (rid != String.Empty) + yield return new Peer { Id = new MultiHash(id!) }; + } + else + { + var responses = (JArray?)r["Responses"]; + if (responses is not null) + { + foreach (var response in responses) + { + var rid = (string?)response["ID"]; + if (!string.IsNullOrEmpty(rid)) { - ++n; - yield return new Peer { Id = new MultiHash(rid) }; - } - } - } - } - } + ++n; + yield return new Peer { Id = new MultiHash(rid!) }; + } + } + } + } + } } - } - } - -} + } + } +} diff --git a/src/CoreApi/DnsApi.cs b/src/CoreApi/DnsApi.cs index bcc7c08..c3fd43c 100644 --- a/src/CoreApi/DnsApi.cs +++ b/src/CoreApi/DnsApi.cs @@ -1,26 +1,26 @@ using Ipfs.CoreApi; -using Newtonsoft.Json.Linq; -using System.Threading; -using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using System.Threading; +using System.Threading.Tasks; -namespace Ipfs.Http -{ - class DnsApi : IDnsApi - { - private IpfsClient ipfs; - - internal DnsApi(IpfsClient ipfs) - { - this.ipfs = ipfs; +namespace Ipfs.Http +{ + class DnsApi : IDnsApi + { + private readonly IpfsClient ipfs; + + internal DnsApi(IpfsClient ipfs) + { + this.ipfs = ipfs; } - public async Task ResolveAsync(string name, bool recursive = false, CancellationToken cancel = default(CancellationToken)) + public async Task ResolveAsync(string name, bool recursive = false, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("dns", cancel, name, $"recursive={recursive.ToString().ToLowerInvariant()}"); - var path = (string)(JObject.Parse(json)["Path"]); - return path; + var path = (string?)(JObject.Parse(json)["Path"]); + return path ?? string.Empty; } } -} +} diff --git a/src/CoreApi/FileSystemApi.cs b/src/CoreApi/FileSystemApi.cs index b94e702..3bec4a1 100644 --- a/src/CoreApi/FileSystemApi.cs +++ b/src/CoreApi/FileSystemApi.cs @@ -1,71 +1,70 @@ -using Ipfs.CoreApi; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Ipfs.Http -{ - class FileSystemApi : IFileSystemApi +using Ipfs.CoreApi; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ipfs.Http +{ + class FileSystemApi : IFileSystemApi { - private IpfsClient ipfs; - private Lazy emptyFolder; - - internal FileSystemApi(IpfsClient ipfs) - { - this.ipfs = ipfs; - this.emptyFolder = new Lazy(() => ipfs.Object.NewDirectoryAsync().Result); - } - - public async Task AddFileAsync(string path, AddFileOptions options = null, CancellationToken cancel = default(CancellationToken)) - { - using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - var node = await AddAsync(stream, Path.GetFileName(path), options, cancel); - return node; - } - } - - public Task AddTextAsync(string text, AddFileOptions options = null, CancellationToken cancel = default(CancellationToken)) - { - return AddAsync(new MemoryStream(Encoding.UTF8.GetBytes(text), false), "", options, cancel); - } - - public async Task AddAsync(Stream stream, string name = "", AddFileOptions options = null, CancellationToken cancel = default(CancellationToken)) - { - if (options == null) - options = new AddFileOptions(); - var opts = new List(); - if (!options.Pin) - opts.Add("pin=false"); - if (options.Wrap) - opts.Add("wrap-with-directory=true"); - if (options.RawLeaves) - opts.Add("raw-leaves=true"); - if (options.OnlyHash) - opts.Add("only-hash=true"); - if (options.Trickle) - opts.Add("trickle=true"); - if (options.Progress != null) - opts.Add("progress=true"); - if (options.Hash != MultiHash.DefaultAlgorithmName) - opts.Add($"hash=${options.Hash}"); - if (options.Encoding != MultiBase.DefaultAlgorithmName) - opts.Add($"cid-base=${options.Encoding}"); - if (!string.IsNullOrWhiteSpace(options.ProtectionKey)) - opts.Add($"protect={options.ProtectionKey}"); - opts.Add($"chunker=size-{options.ChunkSize}"); - - var response = await ipfs.Upload2Async("add", cancel, stream, name, opts.ToArray()); - - // The result is a stream of LDJSON objects. - // See https://github.com/ipfs/go-ipfs/issues/4852 - FileSystemNode fsn = null; + private readonly IpfsClient ipfs; + private readonly Lazy emptyFolder; + + internal FileSystemApi(IpfsClient ipfs) + { + this.ipfs = ipfs; + this.emptyFolder = new Lazy(() => ipfs.Object.NewDirectoryAsync().Result); + } + + public async Task AddFileAsync(string path, AddFileOptions? options = null, CancellationToken cancel = default) + { + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var node = await AddAsync(stream, Path.GetFileName(path), options, cancel); + return node; + } + } + + public Task AddTextAsync(string text, AddFileOptions? options = null, CancellationToken cancel = default) + { + return AddAsync(new MemoryStream(Encoding.UTF8.GetBytes(text), false), "", options, cancel); + } + + public async Task AddAsync(Stream stream, string name = "", AddFileOptions? options = null, CancellationToken cancel = default) + { + options ??= new AddFileOptions(); + var opts = new List(); + if (!options.Pin) + opts.Add("pin=false"); + if (options.Wrap) + opts.Add("wrap-with-directory=true"); + if (options.RawLeaves) + opts.Add("raw-leaves=true"); + if (options.OnlyHash) + opts.Add("only-hash=true"); + if (options.Trickle) + opts.Add("trickle=true"); + if (options.Progress is not null) + opts.Add("progress=true"); + if (options.Hash != MultiHash.DefaultAlgorithmName) + opts.Add($"hash=${options.Hash}"); + if (options.Encoding != MultiBase.DefaultAlgorithmName) + opts.Add($"cid-base=${options.Encoding}"); + if (!string.IsNullOrWhiteSpace(options.ProtectionKey)) + opts.Add($"protect={options.ProtectionKey}"); + opts.Add($"chunker=size-{options.ChunkSize}"); + + var response = await ipfs.Upload2Async("add", cancel, stream, name, opts.ToArray()); + + // The result is a stream of LDJSON objects. + // See https://github.com/ipfs/go-ipfs/issues/4852 + FileSystemNode? fsn = null; using (var sr = new StreamReader(response)) using (var jr = new JsonTextReader(sr) { SupportMultipleContent = true }) { @@ -78,8 +77,8 @@ internal FileSystemApi(IpfsClient ipfs) { options.Progress?.Report(new TransferProgress { - Name = (string)r["Name"], - Bytes = (ulong)r["Bytes"] + Name = (string?)r["Name"], + Bytes = (ulong?)r["Bytes"] ?? 0 }); } @@ -88,8 +87,8 @@ internal FileSystemApi(IpfsClient ipfs) { fsn = new FileSystemNode { - Id = (string)r["Hash"], - Size = long.Parse((string)r["Size"]), + Id = (string)r["Hash"]!, + Size = long.Parse((string?)r["Size"]), IsDirectory = false, Name = name, IpfsClient = ipfs @@ -98,158 +97,163 @@ internal FileSystemApi(IpfsClient ipfs) } } - fsn.IsDirectory = options.Wrap; + if (fsn is not null) + { + fsn.IsDirectory = options.Wrap; + } + else + { + throw new InvalidDataException("Returned JSON did not contain an added file record"); + } + return fsn; - } - - public async Task AddDirectoryAsync(string path, bool recursive = true, AddFileOptions options = null, CancellationToken cancel = default(CancellationToken)) - { - if (options == null) - options = new AddFileOptions(); + } + + public async Task AddDirectoryAsync(string path, bool recursive = true, AddFileOptions? options = null, CancellationToken cancel = default) + { + options ??= new AddFileOptions(); options.Wrap = false; // Add the files and sub-directories. - path = Path.GetFullPath(path); - var files = Directory - .EnumerateFiles(path) - .Select(p => AddFileAsync(p, options, cancel)); - if (recursive) - { - var folders = Directory - .EnumerateDirectories(path) - .Select(dir => AddDirectoryAsync(dir, recursive, options, cancel)); - files = files.Union(folders); + path = Path.GetFullPath(path); + var files = Directory + .EnumerateFiles(path) + .Select(p => AddFileAsync(p, options, cancel)); + if (recursive) + { + var folders = Directory + .EnumerateDirectories(path) + .Select(dir => AddDirectoryAsync(dir, recursive, options, cancel)); + files = files.Union(folders); } // go-ipfs v0.4.14 sometimes fails when sending lots of 'add file' // requests. It's happy with adding one file at a time. -#if true - var links = new List(); +#if true + var links = new List(); foreach (var file in files) { var node = await file; links.Add(node.ToLink()); - } -#else + } +#else var nodes = await Task.WhenAll(files); - var links = nodes.Select(node => node.ToLink()); -#endif + var links = nodes.Select(node => node.ToLink()); +#endif // Create the directory with links to the created files and sub-directories - var folder = emptyFolder.Value.AddLinks(links); + var folder = emptyFolder.Value.AddLinks(links); var directory = await ipfs.Object.PutAsync(folder, cancel); - return new FileSystemNode - { - Id = directory.Id, - Name = Path.GetFileName(path), - Links = links, - IsDirectory = true, - Size = directory.Size, - IpfsClient = ipfs - }; - - } - - /// - /// Reads the content of an existing IPFS file as text. - /// - /// - /// A path to an existing file, such as "QmXarR6rgkQ2fDSHjSY5nM2kuCXKYGViky5nohtwgF65Ec/about" - /// or "QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V" - /// - /// - /// Is used to stop the task. When cancelled, the is raised. - /// - /// - /// The contents of the as a . - /// - public async Task ReadAllTextAsync(string path, CancellationToken cancel = default(CancellationToken)) - { - using (var data = await ReadFileAsync(path, cancel)) - using (var text = new StreamReader(data)) - { - return await text.ReadToEndAsync(); - } - } - - - /// - /// Opens an existing IPFS file for reading. - /// - /// - /// A path to an existing file, such as "QmXarR6rgkQ2fDSHjSY5nM2kuCXKYGViky5nohtwgF65Ec/about" - /// or "QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V" - /// - /// - /// Is used to stop the task. When cancelled, the is raised. - /// - /// - /// A to the file contents. - /// - public Task ReadFileAsync(string path, CancellationToken cancel = default(CancellationToken)) - { - return ipfs.PostDownloadAsync("cat", cancel, path); - } - - public Task ReadFileAsync(string path, long offset, long length = 0, CancellationToken cancel = default(CancellationToken)) - { - // https://github.com/ipfs/go-ipfs/issues/5380 - if (offset > int.MaxValue) - throw new NotSupportedException("Only int offsets are currently supported."); - if (length > int.MaxValue) - throw new NotSupportedException("Only int lengths are currently supported."); - - if (length == 0) - length = int.MaxValue; // go-ipfs only accepts int lengths + return new FileSystemNode + { + Id = directory.Id, + Name = Path.GetFileName(path), + Links = links, + IsDirectory = true, + Size = directory.Size, + IpfsClient = ipfs + }; + } + + /// + /// Reads the content of an existing IPFS file as text. + /// + /// + /// A path to an existing file, such as "QmXarR6rgkQ2fDSHjSY5nM2kuCXKYGViky5nohtwgF65Ec/about" + /// or "QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V" + /// + /// + /// Is used to stop the task. When cancelled, the is raised. + /// + /// + /// The contents of the as a . + /// + public async Task ReadAllTextAsync(string path, CancellationToken cancel = default) + { + using (var data = await ReadFileAsync(path, cancel)) + using (var text = new StreamReader(data)) + { + return await text.ReadToEndAsync(); + } + } + + + /// + /// Opens an existing IPFS file for reading. + /// + /// + /// A path to an existing file, such as "QmXarR6rgkQ2fDSHjSY5nM2kuCXKYGViky5nohtwgF65Ec/about" + /// or "QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V" + /// + /// + /// Is used to stop the task. When cancelled, the is raised. + /// + /// + /// A to the file contents. + /// + public Task ReadFileAsync(string path, CancellationToken cancel = default) + { + return ipfs.PostDownloadAsync("cat", cancel, path); + } + + public Task ReadFileAsync(string path, long offset, long length = 0, CancellationToken cancel = default) + { + // https://github.com/ipfs/go-ipfs/issues/5380 + if (offset > int.MaxValue) + throw new NotSupportedException("Only int offsets are currently supported."); + if (length > int.MaxValue) + throw new NotSupportedException("Only int lengths are currently supported."); + + if (length == 0) + length = int.MaxValue; // go-ipfs only accepts int lengths return ipfs.PostDownloadAsync("cat", cancel, path, - $"offset={offset}", - $"length={length}"); - } - - /// - /// Get information about the file or directory. - /// - /// - /// A path to an existing file or directory, such as "QmXarR6rgkQ2fDSHjSY5nM2kuCXKYGViky5nohtwgF65Ec/about" - /// or "QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V" - /// - /// - /// Is used to stop the task. When cancelled, the is raised. - /// - /// - public async Task ListFileAsync(string path, CancellationToken cancel = default(CancellationToken)) - { - var json = await ipfs.DoCommandAsync("file/ls", cancel, path); - var r = JObject.Parse(json); - var hash = (string)r["Arguments"][path]; - var o = (JObject)r["Objects"][hash]; - var node = new FileSystemNode() - { - Id = (string)o["Hash"], - Size = (long)o["Size"], - IsDirectory = (string)o["Type"] == "Directory", - Links = new FileSystemLink[0], - IpfsClient = ipfs - }; - var links = o["Links"] as JArray; - if (links != null) - { - node.Links = links - .Select(l => new FileSystemLink() - { - Name = (string)l["Name"], - Id = (string)l["Hash"], - Size = (long)l["Size"], - }) - .ToArray(); - } - - return node; + $"offset={offset}", + $"length={length}"); + } + + /// + /// Get information about the file or directory. + /// + /// + /// A path to an existing file or directory, such as "QmXarR6rgkQ2fDSHjSY5nM2kuCXKYGViky5nohtwgF65Ec/about" + /// or "QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V" + /// + /// + /// Is used to stop the task. When cancelled, the is raised. + /// + /// A + public async Task ListFileAsync(string path, CancellationToken cancel = default) + { + var json = await ipfs.DoCommandAsync("file/ls", cancel, path); + var r = JObject.Parse(json); + var hash = (string)r["Arguments"]![path]!; + var o = (JObject)r["Objects"]![hash]!; + var node = new FileSystemNode + { + Id = (string)o["Hash"]!, + Size = (long?)o["Size"] ?? 0, + IsDirectory = (string?)o["Type"] == "Directory", + Links = new FileSystemLink[0], + IpfsClient = ipfs + }; + var links = o["Links"] as JArray; + if (links is not null) + { + node.Links = links + .Select(l => new FileSystemLink((string)l["Hash"]!) + { + Name = (string?)l["Name"], + Size = (long?)l["Size"] ?? 0, + }) + .ToArray(); + } + + return node; } - public Task GetAsync(string path, bool compress = false, CancellationToken cancel = default(CancellationToken)) + public Task GetAsync(string path, bool compress = false, CancellationToken cancel = default) { - return ipfs.PostDownloadAsync("get", cancel, path, $"compress={compress}"); - } - } -} + return ipfs.PostDownloadAsync("get", cancel, path, $"compress={compress}"); + } + } +} diff --git a/src/CoreApi/GenericApi.cs b/src/CoreApi/GenericApi.cs index e6ca5d2..7fdb17a 100644 --- a/src/CoreApi/GenericApi.cs +++ b/src/CoreApi/GenericApi.cs @@ -1,82 +1,81 @@ -using Ipfs.CoreApi; +using Ipfs.CoreApi; using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; -namespace Ipfs.Http -{ - public partial class IpfsClient : IGenericApi - { +namespace Ipfs.Http +{ + public partial class IpfsClient : IGenericApi + { private const double TicksPerNanosecond = (double)TimeSpan.TicksPerMillisecond * 0.000001; /// - public Task IdAsync(MultiHash peer = null, CancellationToken cancel = default(CancellationToken)) - { - return DoCommandAsync("id", cancel, peer?.ToString()); + public Task IdAsync(MultiHash? peer = null, CancellationToken cancel = default) + { + return DoCommandAsync("id", cancel, peer?.ToString()); } /// - public async Task> PingAsync(MultiHash peer, int count = 10, CancellationToken cancel = default(CancellationToken)) + public async Task> PingAsync(MultiHash peer, int count = 10, CancellationToken cancel = default) { - var stream = await PostDownloadAsync("ping", cancel, - peer.ToString(), - $"count={count.ToString(CultureInfo.InvariantCulture)}"); + var stream = await PostDownloadAsync("ping", cancel, + peer.ToString(), + $"count={count.ToString(CultureInfo.InvariantCulture)}"); return PingResultFromStream(stream); } /// - public async Task> PingAsync(MultiAddress address, int count = 10, CancellationToken cancel = default(CancellationToken)) + public async Task> PingAsync(MultiAddress address, int count = 10, CancellationToken cancel = default) { - var stream = await PostDownloadAsync("ping", cancel, - address.ToString(), - $"count={count.ToString(CultureInfo.InvariantCulture)}"); + var stream = await PostDownloadAsync("ping", cancel, + address.ToString(), + $"count={count.ToString(CultureInfo.InvariantCulture)}"); return PingResultFromStream(stream); } IEnumerable PingResultFromStream(Stream stream) { - using (var sr = new StreamReader(stream)) - { - while (!sr.EndOfStream) - { - var json = sr.ReadLine(); - - var r = JObject.Parse(json); + using (var sr = new StreamReader(stream)) + { + while (!sr.EndOfStream) + { + var json = sr.ReadLine(); + + var r = JObject.Parse(json); yield return new PingResult { - Success = (bool)r["Success"], - Text = (string)r["Text"], - Time = TimeSpan.FromTicks((long)((long)r["Time"] * TicksPerNanosecond)) + Success = (bool?)r["Success"] ?? false, + Text = (string?)r["Text"], + Time = TimeSpan.FromTicks((long)(((long?)r["Time"] ?? 0) * TicksPerNanosecond)) }; - } - } - } + } + } + } /// - public async Task ResolveAsync(string name, bool recursive = true, CancellationToken cancel = default(CancellationToken)) + public async Task ResolveAsync(string name, bool recursive = true, CancellationToken cancel = default) { var json = await DoCommandAsync("resolve", cancel, name, $"recursive={recursive.ToString().ToLowerInvariant()}"); - var path = (string)(JObject.Parse(json)["Path"]); - return path; + var path = (string?)(JObject.Parse(json)["Path"]); + return path ?? string.Empty; } /// public async Task ShutdownAsync() { - await DoCommandAsync("shutdown", default(CancellationToken)); + await DoCommandAsync("shutdown", default); } /// - public Task> VersionAsync(CancellationToken cancel = default(CancellationToken)) - { - return DoCommandAsync>("version", cancel); - } - - } -} + public Task> VersionAsync(CancellationToken cancel = default) + { + return DoCommandAsync>("version", cancel); + } + } +} diff --git a/src/CoreApi/KeyApi.cs b/src/CoreApi/KeyApi.cs index 003fa04..9e556ed 100644 --- a/src/CoreApi/KeyApi.cs +++ b/src/CoreApi/KeyApi.cs @@ -1,41 +1,47 @@ -using Ipfs.CoreApi; -using Newtonsoft.Json.Linq; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Ipfs.CoreApi; +using Newtonsoft.Json.Linq; namespace Ipfs.Http { - class KeyApi : IKeyApi + internal class KeyApi : IKeyApi { /// /// Information about a local key. /// - public class KeyInfo : IKey + public sealed class KeyInfo : IKey { + public KeyInfo(MultiHash id, string name) + { + Id = id; + Name = name; + } + /// - public MultiHash Id { get; set; } + public MultiHash Id { get; } /// - public string Name { get; set; } + public string Name { get; } /// public override string ToString() { - return Name; + return Name ?? string.Empty; } - } - IpfsClient ipfs; + + private readonly IpfsClient ipfs; internal KeyApi(IpfsClient ipfs) { this.ipfs = ipfs; } - public async Task CreateAsync(string name, string keyType, int size, CancellationToken cancel = default(CancellationToken)) + public async Task CreateAsync(string name, string keyType, int size, CancellationToken cancel = default) { return await ipfs.DoCommandAsync("key/gen", cancel, name, @@ -43,49 +49,37 @@ internal KeyApi(IpfsClient ipfs) $"size={size}"); } - public async Task> ListAsync(CancellationToken cancel = default(CancellationToken)) + public async Task> ListAsync(CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("key/list", cancel, null, "l=true"); - var keys = (JArray)(JObject.Parse(json)["Keys"]); + var keys = (JArray?)(JObject.Parse(json)["Keys"]); return keys - .Select(k => new KeyInfo - { - Id = (string)k["Id"], - Name = (string)k["Name"] - }); + .Select(k => new KeyInfo((string)k["Id"]!, (string)k["Name"]!)); } - public async Task RemoveAsync(string name, CancellationToken cancel = default(CancellationToken)) + public async Task RemoveAsync(string name, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("key/rm", cancel, name); var keys = JObject.Parse(json)["Keys"] as JArray; return keys? - .Select(k => new KeyInfo - { - Id = (string)k["Id"], - Name = (string)k["Name"] - }) + .Select(k => new KeyInfo((string)k["Id"]!, (string)k["Name"]!)) .First(); } - public async Task RenameAsync(string oldName, string newName, CancellationToken cancel = default(CancellationToken)) + public async Task RenameAsync(string oldName, string newName, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("key/rename", cancel, oldName, $"arg={newName}"); var key = JObject.Parse(json); - return new KeyInfo - { - Id = (string)key["Id"], - Name = (string)key["Now"] - }; + return new KeyInfo((string)key["Id"]!, (string)key["Now"]!); } - public Task ExportAsync(string name, char[] password, CancellationToken cancel = default(CancellationToken)) + public Task ExportAsync(string name, char[] password, CancellationToken cancel = default) { throw new NotImplementedException(); } - public Task ImportAsync(string name, string pem, char[] password = null, CancellationToken cancel = default(CancellationToken)) + public Task ImportAsync(string name, string pem, char[]? password = null, CancellationToken cancel = default) { throw new NotImplementedException(); } diff --git a/src/CoreApi/NameApi.cs b/src/CoreApi/NameApi.cs index 94d595e..922ddad 100644 --- a/src/CoreApi/NameApi.cs +++ b/src/CoreApi/NameApi.cs @@ -1,21 +1,21 @@ using Ipfs.CoreApi; -using Newtonsoft.Json.Linq; -using System; -using System.Threading; -using System.Threading.Tasks; +using Newtonsoft.Json.Linq; +using System; +using System.Threading; +using System.Threading.Tasks; -namespace Ipfs.Http -{ - class NameApi : INameApi - { - private IpfsClient ipfs; - - internal NameApi(IpfsClient ipfs) - { - this.ipfs = ipfs; +namespace Ipfs.Http +{ + class NameApi : INameApi + { + private readonly IpfsClient ipfs; + + internal NameApi(IpfsClient ipfs) + { + this.ipfs = ipfs; } - public async Task PublishAsync(string path, bool resolve = true, string key = "self", TimeSpan? lifetime = null, CancellationToken cancel = default(CancellationToken)) + public async Task PublishAsync(string path, bool resolve = true, string key = "self", TimeSpan? lifetime = null, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("name/publish", cancel, path, @@ -26,12 +26,12 @@ internal NameApi(IpfsClient ipfs) var info = JObject.Parse(json); return new NamedContent { - NamePath = (string)info["Name"], - ContentPath = (string)info["Value"] + NamePath = (string?)info["Name"], + ContentPath = (string?)info["Value"] }; } - public Task PublishAsync(Cid id, string key = "self", TimeSpan? lifetime = null, CancellationToken cancel = default(CancellationToken)) + public Task PublishAsync(Cid id, string key = "self", TimeSpan? lifetime = null, CancellationToken cancel = default) { return PublishAsync("/ipfs/" + id.Encode(), false, key, lifetime, cancel); } @@ -42,8 +42,8 @@ internal NameApi(IpfsClient ipfs) name, $"recursive={recursive.ToString().ToLowerInvariant()}", $"nocache={nocache.ToString().ToLowerInvariant()}"); - var path = (string)(JObject.Parse(json)["Path"]); - return path; + var path = (string?)(JObject.Parse(json)["Path"]); + return path ?? string.Empty; } } -} +} diff --git a/src/CoreApi/ObjectApi.cs b/src/CoreApi/ObjectApi.cs index 8ea0210..e87547a 100644 --- a/src/CoreApi/ObjectApi.cs +++ b/src/CoreApi/ObjectApi.cs @@ -11,48 +11,48 @@ namespace Ipfs.Http { class ObjectApi : IObjectApi { - private IpfsClient ipfs; + private readonly IpfsClient ipfs; internal ObjectApi(IpfsClient ipfs) { this.ipfs = ipfs; } - public Task NewDirectoryAsync(CancellationToken cancel = default(CancellationToken)) + public Task NewDirectoryAsync(CancellationToken cancel = default) { return NewAsync("unixfs-dir", cancel); } - public async Task NewAsync(string template = null, CancellationToken cancel = default(CancellationToken)) + public async Task NewAsync(string? template = null, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("object/new", cancel, template); - var hash = (string)(JObject.Parse(json)["Hash"]); - return await GetAsync(hash); + var hash = (string?)(JObject.Parse(json)["Hash"]); + return await GetAsync(hash!); } - public async Task GetAsync(Cid id, CancellationToken cancel = default(CancellationToken)) + public async Task GetAsync(Cid id, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("object/get", cancel, id); return GetDagFromJson(json); } - public Task PutAsync(byte[] data, IEnumerable links = null, CancellationToken cancel = default(CancellationToken)) + public Task PutAsync(byte[] data, IEnumerable? links = null, CancellationToken cancel = default) { return PutAsync(new DagNode(data, links), cancel); } - public async Task PutAsync(DagNode node, CancellationToken cancel = default(CancellationToken)) + public async Task PutAsync(DagNode node, CancellationToken cancel = default) { var json = await ipfs.UploadAsync("object/put", cancel, node.ToArray(), "inputenc=protobuf"); return node; } - public Task DataAsync(Cid id, CancellationToken cancel = default(CancellationToken)) + public Task DataAsync(Cid id, CancellationToken cancel = default) { return ipfs.PostDownloadAsync("object/data", cancel, id); } - public async Task> LinksAsync(Cid id, CancellationToken cancel = default(CancellationToken)) + public async Task> LinksAsync(Cid id, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("object/links", cancel, id); return GetDagFromJson(json).Links; @@ -63,30 +63,30 @@ internal ObjectApi(IpfsClient ipfs) DagNode GetDagFromJson(string json) { var result = JObject.Parse(json); - byte[] data = null; - var stringData = (string)result["Data"]; - if (stringData != null) + byte[]? data = null; + var stringData = (string?)result["Data"]; + if (stringData is not null) data = Encoding.UTF8.GetBytes(stringData); - var links = ((JArray)result["Links"]) + var links = ((JArray?)result["Links"]) .Select(link => new DagLink( - (string)link["Name"], - (string)link["Hash"], - (long)link["Size"])); + (string?)link["Name"], + (string)link["Hash"]!, + (long?)link["Size"] ?? 0)); return new DagNode(data, links); } - public async Task StatAsync(Cid id, CancellationToken cancel = default(CancellationToken)) + public async Task StatAsync(Cid id, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("object/stat", cancel, id); var r = JObject.Parse(json); return new ObjectStat { - LinkCount = (int)r["NumLinks"], - LinkSize = (long)r["LinksSize"], - BlockSize = (long)r["BlockSize"], - DataSize = (long)r["DataSize"], - CumulativeSize = (long)r["CumulativeSize"] + LinkCount = (int?)r["NumLinks"] ?? 0, + LinkSize = (long?)r["LinksSize"] ?? 0, + BlockSize = (long?)r["BlockSize"] ?? 0, + DataSize = (long?)r["DataSize"] ?? 0, + CumulativeSize = (long?)r["CumulativeSize"] ?? 0, }; } } diff --git a/src/CoreApi/PinApi.cs b/src/CoreApi/PinApi.cs index 7f3bf2c..33e2c64 100644 --- a/src/CoreApi/PinApi.cs +++ b/src/CoreApi/PinApi.cs @@ -9,38 +9,41 @@ namespace Ipfs.Http { class PinApi : IPinApi { - private IpfsClient ipfs; + private readonly IpfsClient ipfs; internal PinApi(IpfsClient ipfs) { this.ipfs = ipfs; } - public async Task> AddAsync(string path, bool recursive = true, CancellationToken cancel = default(CancellationToken)) + public async Task> AddAsync(string path, bool recursive = true, CancellationToken cancel = default) { var opts = "recursive=" + recursive.ToString().ToLowerInvariant(); var json = await ipfs.DoCommandAsync("pin/add", cancel, path, opts); - return ((JArray)JObject.Parse(json)["Pins"]) - .Select(p => (Cid)(string)p); + return ((JArray?)JObject.Parse(json)["Pins"]) + .Select(p => (Cid)(string)p!); } - public async Task> ListAsync(CancellationToken cancel = default(CancellationToken)) + public async Task> ListAsync(CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("pin/ls", cancel); - var keys = (JObject)(JObject.Parse(json)["Keys"]); + var keys = (JObject?)(JObject.Parse(json)["Keys"]); + if (keys is null) + { + return Enumerable.Empty(); + } + return keys .Properties() .Select(p => (Cid)p.Name); } - public async Task> RemoveAsync(Cid id, bool recursive = true, CancellationToken cancel = default(CancellationToken)) + public async Task> RemoveAsync(Cid id, bool recursive = true, CancellationToken cancel = default) { var opts = "recursive=" + recursive.ToString().ToLowerInvariant(); var json = await ipfs.DoCommandAsync("pin/rm", cancel, id, opts); - return ((JArray)JObject.Parse(json)["Pins"]) - .Select(p => (Cid)(string)p); + return ((JArray?)JObject.Parse(json)["Pins"]) + .Select(p => (Cid)(string)p!); } - } - } diff --git a/src/CoreApi/PubSubApi.cs b/src/CoreApi/PubSubApi.cs index f2c726d..c75336d 100644 --- a/src/CoreApi/PubSubApi.cs +++ b/src/CoreApi/PubSubApi.cs @@ -1,19 +1,19 @@ -using Ipfs.CoreApi; -using Newtonsoft.Json.Linq; -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Ipfs.CoreApi; using Multiformats.Base; +using Newtonsoft.Json.Linq; namespace Ipfs.Http { class PubSubApi : IPubSubApi { - private IpfsClient ipfs; + private readonly IpfsClient ipfs; internal PubSubApi(IpfsClient ipfs) { @@ -25,20 +25,22 @@ public async Task> SubscribedTopicsAsync(CancellationToken c var json = await ipfs.DoCommandAsync("pubsub/ls", cancel); var result = JObject.Parse(json); var strings = result["Strings"] as JArray; - if (strings == null) return new string[0]; - return strings.Select(s => (string)s); + if (strings is null) return Enumerable.Empty(); + return strings.Select(s => (string)s!); } - public async Task> PeersAsync(string topic = null, CancellationToken cancel = default) + public async Task> PeersAsync(string? topic = null, CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("pubsub/peers", cancel, topic); var result = JObject.Parse(json); var strings = result["Strings"] as JArray; - if (strings == null) - return Array.Empty(); + if (strings is null) + { + return Enumerable.Empty(); + } - return strings.Select(s => new Peer { Id = (string)s }); + return strings.Select(s => new Peer { Id = (string)s! }); } public Task PublishAsync(string topic, byte[] message, CancellationToken cancel = default) @@ -76,10 +78,10 @@ public async Task SubscribeAsync(string topic, Action handler var messageStream = await ipfs.PostDownloadAsync("pubsub/sub", cancellationToken, $"{Multibase.Encode(MultibaseEncoding.Base64Url, Encoding.UTF8.GetBytes(topic))}"); var sr = new StreamReader(messageStream); - _ = Task.Run(() => ProcessMessages(topic, handler, sr, cancellationToken), cancellationToken); + _ = Task.Run(() => ProcessMessages(handler, sr, cancellationToken), cancellationToken); } - void ProcessMessages(string topic, Action handler, StreamReader sr, CancellationToken ct) + void ProcessMessages(Action handler, StreamReader sr, CancellationToken ct) { // .Net needs a ReadLine(CancellationToken) // As a work-around, we register a function to close the stream @@ -89,7 +91,7 @@ void ProcessMessages(string topic, Action handler, StreamReade while (!sr.EndOfStream && !ct.IsCancellationRequested) { var json = sr.ReadLine(); - if (json == null) + if (json is null) break; // go-ipfs 0.4.13 and earlier always send empty JSON @@ -112,7 +114,5 @@ void ProcessMessages(string topic, Action handler, StreamReade sr.Dispose(); } } - } - } diff --git a/src/CoreApi/StatsApi.cs b/src/CoreApi/StatsApi.cs index 1082730..1bfe29c 100644 --- a/src/CoreApi/StatsApi.cs +++ b/src/CoreApi/StatsApi.cs @@ -9,41 +9,39 @@ namespace Ipfs.Http class StatApi : IStatsApi { - private IpfsClient ipfs; + private readonly IpfsClient ipfs; internal StatApi(IpfsClient ipfs) { this.ipfs = ipfs; } - public Task BandwidthAsync(CancellationToken cancel = default(CancellationToken)) + public Task BandwidthAsync(CancellationToken cancel = default) { return ipfs.DoCommandAsync("stats/bw", cancel); } - public async Task BitswapAsync(CancellationToken cancel = default(CancellationToken)) + public async Task BitswapAsync(CancellationToken cancel = default) { var json = await ipfs.DoCommandAsync("stats/bitswap", cancel); var stat = JObject.Parse(json); return new BitswapData { - BlocksReceived = (ulong)stat["BlocksReceived"], - DataReceived = (ulong)stat["DataReceived"], - BlocksSent = (ulong)stat["BlocksSent"], - DataSent = (ulong)stat["DataSent"], - DupBlksReceived = (ulong)stat["DupBlksReceived"], - DupDataReceived = (ulong)stat["DupDataReceived"], - ProvideBufLen = (int)stat["ProvideBufLen"], - Peers = ((JArray)stat["Peers"]).Select(s => new MultiHash((string)s)), - Wantlist = ((JArray)stat["Wantlist"]).Select(o => Cid.Decode(o["/"].ToString())) + BlocksReceived = (ulong?)stat["BlocksReceived"] ?? 0, + DataReceived = (ulong?)stat["DataReceived"] ?? 0, + BlocksSent = (ulong?)stat["BlocksSent"] ?? 0, + DataSent = (ulong?)stat["DataSent"] ?? 0, + DupBlksReceived = (ulong?)stat["DupBlksReceived"] ?? 0, + DupDataReceived = (ulong?)stat["DupDataReceived"] ?? 0, + ProvideBufLen = (int?)stat["ProvideBufLen"] ?? 0, + Peers = ((JArray?)stat["Peers"]).Select(s => new MultiHash((string)s!)).ToList(), + Wantlist = ((JArray?)stat["Wantlist"]).Select(o => Cid.Decode(o["/"]?.ToString() ?? string.Empty)) }; } - public Task RepositoryAsync(CancellationToken cancel = default(CancellationToken)) + public Task RepositoryAsync(CancellationToken cancel = default) { return ipfs.DoCommandAsync("stats/repo", cancel); } - - } } diff --git a/src/CoreApi/SwarmApi.cs b/src/CoreApi/SwarmApi.cs index fc83326..488d1ae 100644 --- a/src/CoreApi/SwarmApi.cs +++ b/src/CoreApi/SwarmApi.cs @@ -1,99 +1,112 @@ -using Ipfs.CoreApi; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Ipfs.Http -{ - class SwarmApi : ISwarmApi - { - private IpfsClient ipfs; - - internal SwarmApi(IpfsClient ipfs) - { - this.ipfs = ipfs; - } - - public async Task> AddressesAsync(CancellationToken cancel = default(CancellationToken)) - { - var json = await ipfs.DoCommandAsync("swarm/addrs", cancel); - return ((JObject)JObject.Parse(json)["Addrs"]) - .Properties() +using Ipfs.CoreApi; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Ipfs.Http +{ + class SwarmApi : ISwarmApi + { + private readonly IpfsClient ipfs; + + internal SwarmApi(IpfsClient ipfs) + { + this.ipfs = ipfs; + } + + public async Task> AddressesAsync(CancellationToken cancel = default) + { + var json = await ipfs.DoCommandAsync("swarm/addrs", cancel); + var obj = (JObject?)JObject.Parse(json); + if (obj is null) + { + return Enumerable.Empty(); + } + + var addrs = (JObject?)obj["Addrs"]; + if (addrs is null) + { + return Enumerable.Empty(); + } + + return addrs + .Properties() .Select(p => new Peer - { - Id = p.Name, - Addresses = ((JArray)p.Value) - .Select(a => MultiAddress.TryCreate((string)a)) - .Where(ma => ma != null) - }); - } - - public async Task> PeersAsync(CancellationToken cancel = default(CancellationToken)) - { - var json = await ipfs.DoCommandAsync("swarm/peers", cancel, null, "verbose=true"); - var result = JObject.Parse(json); - - // Older servers return an array of strings - var strings = (JArray)result["Strings"]; - if (strings != null) - { - return strings - .Select(s => - { - var parts = ((string)s).Split(' '); - var address = new MultiAddress(parts[0]); - return new Peer - { - Id = address.PeerId, - ConnectedAddress = parts[0], - Latency = Duration.Parse(parts[1]) - }; - }); - } - - // Current servers return JSON - var peers = (JArray)result["Peers"]; - if (peers != null) - { - return peers.Select(p => new Peer - { - Id = (string)p["Peer"], - ConnectedAddress = new MultiAddress((string)p["Addr"] + "/ipfs/" + (string)p["Peer"]), - Latency = Duration.Parse((string)p["Latency"]) - }); + { + Id = p.Name, + Addresses = ((JArray)p.Value) + .Select(a => MultiAddress.TryCreate((string)a!)) + .Where(ma => ma is not null) + .Cast() + }); + } + + public async Task> PeersAsync(CancellationToken cancel = default) + { + var json = await ipfs.DoCommandAsync("swarm/peers", cancel, null, "verbose=true"); + var result = JObject.Parse(json); + + // Older servers return an array of strings + var strings = (JArray?)result["Strings"]; + if (strings is not null) + { + return strings + .Select(s => + { + var parts = ((string)s)!.Split(' '); + var address = new MultiAddress(parts[0]); + return new Peer + { + Id = address.PeerId, + ConnectedAddress = parts[0], + Latency = Duration.Parse(parts[1]) + }; + }); + } + + // Current servers return JSON + var peers = (JArray?)result["Peers"]; + if (peers is not null) + { + return peers.Select(p => new Peer + { + Id = (string)p["Peer"]!, + ConnectedAddress = new MultiAddress((string?)p["Addr"] + "/ipfs/" + (string?)p["Peer"]), + Latency = Duration.Parse((string?)p["Latency"] ?? "0") + }); } // Hmmm. Another change we can handle - throw new FormatException("Unknown response from 'swarm/peers"); - } - - public async Task ConnectAsync(MultiAddress address, CancellationToken cancel = default(CancellationToken)) - { - await ipfs.DoCommandAsync("swarm/connect", cancel, address.ToString()); - } - - public async Task DisconnectAsync(MultiAddress address, CancellationToken cancel = default(CancellationToken)) - { - await ipfs.DoCommandAsync("swarm/disconnect", cancel, address.ToString()); + throw new FormatException("Unknown response from 'swarm/peers"); + } + + public async Task ConnectAsync(MultiAddress address, CancellationToken cancel = default) + { + await ipfs.DoCommandAsync("swarm/connect", cancel, address.ToString()); + } + + public async Task DisconnectAsync(MultiAddress address, CancellationToken cancel = default) + { + await ipfs.DoCommandAsync("swarm/disconnect", cancel, address.ToString()); } - public async Task AddAddressFilterAsync(MultiAddress address, bool persist = false, CancellationToken cancel = default(CancellationToken)) + public async Task AddAddressFilterAsync(MultiAddress address, bool persist = false, CancellationToken cancel = default) { // go-ipfs always does persist, https://github.com/ipfs/go-ipfs/issues/4605 var json = await ipfs.DoCommandAsync("swarm/filters/add", cancel, address.ToString()); - var addrs = (JArray)(JObject.Parse(json)["Strings"]); + var addrs = (JArray?)(JObject.Parse(json)["Strings"]); var a = addrs.FirstOrDefault(); - if (a == null) + if (a is null) return null; - return new MultiAddress((string)a); + return new MultiAddress((string)a!); } - public async Task> ListAddressFiltersAsync(bool persist = false, CancellationToken cancel = default(CancellationToken)) + public async Task> ListAddressFiltersAsync(bool persist = false, CancellationToken cancel = default) { - JArray addrs; + JArray? addrs; if (persist) { addrs = await ipfs.Config.GetAsync("Swarm.AddrFilters", cancel) as JArray; @@ -104,23 +117,23 @@ internal SwarmApi(IpfsClient ipfs) addrs = (JObject.Parse(json)["Strings"]) as JArray; } - if (addrs == null) - return new MultiAddress[0]; + if (addrs is null) + return Enumerable.Empty(); return addrs - .Select(a => MultiAddress.TryCreate((string)a)) - .Where(ma => ma != null); + .Select(a => MultiAddress.TryCreate((string)a!)) + .Where(ma => ma is not null) + .Cast(); } - public async Task RemoveAddressFilterAsync(MultiAddress address, bool persist = false, CancellationToken cancel = default(CancellationToken)) + public async Task RemoveAddressFilterAsync(MultiAddress address, bool persist = false, CancellationToken cancel = default) { // go-ipfs always does persist, https://github.com/ipfs/go-ipfs/issues/4605 var json = await ipfs.DoCommandAsync("swarm/filters/rm", cancel, address.ToString()); - var addrs = (JArray)(JObject.Parse(json)["Strings"]); + var addrs = (JArray?)(JObject.Parse(json)["Strings"]); var a = addrs.FirstOrDefault(); - if (a == null) + if (a is null) return null; - return new MultiAddress((string)a); + return new MultiAddress((string)a!); } - } - -} + } +} diff --git a/src/FileSystemLink.cs b/src/FileSystemLink.cs index 3315569..77e7162 100644 --- a/src/FileSystemLink.cs +++ b/src/FileSystemLink.cs @@ -5,8 +5,17 @@ /// public class FileSystemLink : IFileSystemLink { + /// + /// Creates a new instance of . + /// + /// + public FileSystemLink(Cid id) + { + Id = id; + } + /// - public string Name { get; set; } + public string? Name { get; set; } /// public Cid Id { get; set; } diff --git a/src/FileSystemNode.cs b/src/FileSystemNode.cs index 07bd148..3285548 100644 --- a/src/FileSystemNode.cs +++ b/src/FileSystemNode.cs @@ -1,157 +1,156 @@ -using System.Collections.Generic; -using System.IO; +using System; +using System.Collections.Generic; +using System.IO; using System.Runtime.Serialization; - -namespace Ipfs.Http -{ - /// - [DataContract] - public class FileSystemNode : IFileSystemNode - { - IpfsClient ipfsClient; - IEnumerable links; - long? size; - bool? isDirectory; - - /// - public byte[] DataBytes - { - get - { + +namespace Ipfs.Http +{ + /// + [DataContract] + public class FileSystemNode : IFileSystemNode + { + private IpfsClient? ipfsClient; + private IEnumerable? links; + private long? size; + private bool? isDirectory; + private Cid? id; + + /// + public byte[] DataBytes + { + get + { using (var stream = DataStream) { - if (DataStream == null) - return null; + if (stream is null) + return Array.Empty(); using (var data = new MemoryStream()) { stream.CopyTo(data); return data.ToArray(); - } - } - } - } - - /// - public Stream DataStream - { - get - { - return IpfsClient?.FileSystem.ReadFileAsync(Id).Result; - } - } - - /// - [DataMember] - public Cid Id { get; set; } - - /// - [DataMember] + } + } + } + } + + /// + public Stream DataStream => IpfsClient.FileSystem.ReadFileAsync(Id).GetAwaiter().GetResult(); + + /// + [DataMember] + public Cid Id + { + get => id ?? throw new InvalidDataException("Field mus be initialized"); + set => id = value; + } + + /// + [DataMember] public IEnumerable Links - { - get - { - if (links == null) GetInfo(); - return links; - } - set - { - links = value; - } - } - - /// - /// Size of the file contents. - /// - /// - /// This is the size of the file not the raw encoded contents - /// of the block. - /// - [DataMember] - public long Size - { - get - { - if (!size.HasValue) GetInfo(); - return size.Value; - } - set - { - size = value; - } - } - - /// - /// Determines if the link is a directory (folder). - /// - /// - /// true if the link is a directory; Otherwise false, - /// the link is some type of a file. - /// - [DataMember] - public bool IsDirectory - { - get - { - if (!isDirectory.HasValue) GetInfo(); - return isDirectory.Value; - } - set - { - isDirectory = value; - } - } - - /// - /// The file name of the IPFS node. - /// - [DataMember] - public string Name { get; set; } - - /// - public IFileSystemLink ToLink(string name = "") - { - var link = new FileSystemLink - { - Name = string.IsNullOrWhiteSpace(name) ? Name : name, - Id = Id, - Size = Size, - }; - return link; - } - + { + get + { + if (links is null) GetInfo(); + return links!; + } + set + { + links = value; + } + } + + /// + /// Size of the file contents. + /// + /// + /// This is the size of the file not the raw encoded contents + /// of the block. + /// + [DataMember] + public long Size + { + get + { + if (!size.HasValue) GetInfo(); + return size!.Value; + } + set + { + size = value; + } + } + + /// + /// Determines if the link is a directory (folder). + /// + /// + /// true if the link is a directory; Otherwise false, + /// the link is some type of a file. + /// + [DataMember] + public bool IsDirectory + { + get + { + if (!isDirectory.HasValue) GetInfo(); + return isDirectory!.Value; + } + set + { + isDirectory = value; + } + } + + /// + /// The file name of the IPFS node. + /// + [DataMember] + public string? Name { get; set; } + + /// + public IFileSystemLink ToLink(string name = "") + { + var link = new FileSystemLink(Id) + { + Name = string.IsNullOrWhiteSpace(name) ? Name : name, + Id = Id, + Size = Size, + }; + return link; + } + /// /// The client to IPFS. - /// - /// - /// Used to fetch additional information on the node. - /// - public IpfsClient IpfsClient - { - get - { - if (ipfsClient == null) - { - lock (this) - { - ipfsClient = new IpfsClient(); - } - } - return ipfsClient; - } - set - { - ipfsClient = value; - } - } - - void GetInfo() - { - var node = IpfsClient.FileSystem.ListFileAsync(Id).Result; - this.IsDirectory = node.IsDirectory; - this.Links = node.Links; - this.Size = node.Size; - } - - } -} + /// + /// + /// Used to fetch additional information on the node. + /// + public IpfsClient IpfsClient + { + get + { + if (ipfsClient is null) + { + lock (this) + { + ipfsClient ??= new IpfsClient(); + } + } + return ipfsClient; + } + set + { + ipfsClient = value; + } + } + + void GetInfo() + { + var node = IpfsClient.FileSystem.ListFileAsync(Id).Result; + this.IsDirectory = node.IsDirectory; + this.Links = node.Links; + this.Size = node.Size; + } + } +} diff --git a/src/IpfsClient.cs b/src/IpfsClient.cs index 2d4194e..7ff7304 100644 --- a/src/IpfsClient.cs +++ b/src/IpfsClient.cs @@ -26,10 +26,10 @@ namespace Ipfs.Http /// public partial class IpfsClient : ICoreApi { - const string unknownFilename = "unknown"; + private const string unknownFilename = "unknown"; - static object safe = new object(); - static HttpClient api = null; + private static readonly object safe = new object(); + private static HttpClient? api; /// /// The default URL to the IPFS HTTP API server. @@ -42,7 +42,7 @@ public partial class IpfsClient : ICoreApi /// public static Uri DefaultApiUri = new Uri( Environment.GetEnvironmentVariable("IpfsHttpApi") - ?? "http://localhost:5001"); + ?? "http://localhost:1206"); /// /// Creates a new instance of the class and sets the @@ -168,12 +168,12 @@ public IpfsClient(string host) /// public IKeyApi Key { get; private set; } - Uri BuildCommand(string command, string arg = null, params string[] options) + Uri BuildCommand(string command, string? arg = null, params string[] options) { var url = "/api/v0/" + command; var q = new StringBuilder(); - if (arg != null) + if (arg is not null) { q.Append("&arg="); q.Append(WebUtility.UrlEncode(arg)); @@ -206,21 +206,21 @@ Uri BuildCommand(string command, string arg = null, params string[] options) } /// - /// Get the IPFS API. + /// Get the IPFS API singleton. /// /// /// A . /// /// - /// Only one client is needed. Its thread safe. + /// Only one client is needed. It is thread safe. /// HttpClient Api() { - if (api == null) + if (api is null) { lock (safe) { - if (api == null) + if (api is null) { if (HttpMessageHandler is HttpClientHandler handler && handler.SupportsAutomaticDecompression) { @@ -267,7 +267,7 @@ HttpClient Api() /// /// When the IPFS server indicates an error. /// - public async Task DoCommandAsync(string command, CancellationToken cancel, string arg = null, params string[] options) + public async Task DoCommandAsync(string command, CancellationToken cancel, string? arg = null, params string[] options) { var url = BuildCommand(command, arg, options); @@ -304,7 +304,6 @@ internal async Task DoCommandAsync(Uri url, HttpContent content, CancellationTok } } - /// /// Perform an IPFS API command returning /// a specific . @@ -335,10 +334,13 @@ internal async Task DoCommandAsync(Uri url, HttpContent content, CancellationTok /// /// When the IPFS server indicates an error. /// - public async Task DoCommandAsync(string command, CancellationToken cancel, string arg = null, params string[] options) + /// + /// When the response body returned from the request cannot be parsed as valid JSON. + /// + public async Task DoCommandAsync(string command, CancellationToken cancel, string? arg = null, params string[] options) { var json = await DoCommandAsync(command, cancel, arg, options); - return JsonConvert.DeserializeObject(json); + return JsonConvert.DeserializeObject(json) ?? throw new InvalidDataException($"String could not be decoded as JSON: {json}"); } /// @@ -363,7 +365,7 @@ public async Task DoCommandAsync(string command, CancellationToken cancel, /// /// When the IPFS server indicates an error. /// - public async Task PostDownloadAsync(string command, CancellationToken cancel, string arg = null, params string[] options) + public async Task PostDownloadAsync(string command, CancellationToken cancel, string? arg = null, params string[] options) { var url = BuildCommand(command, arg, options); @@ -398,7 +400,7 @@ public async Task PostDownloadAsync(string command, CancellationToken ca /// /// When the IPFS server indicates an error. /// - public async Task DownloadAsync(string command, CancellationToken cancel, string arg = null, params string[] options) + public async Task DownloadAsync(string command, CancellationToken cancel, string? arg = null, params string[] options) { var url = BuildCommand(command, arg, options); @@ -431,7 +433,7 @@ public async Task DownloadAsync(string command, CancellationToken cancel /// /// When the IPFS server indicates an error. /// - public async Task DownloadBytesAsync(string command, CancellationToken cancel, string arg = null, params string[] options) + public async Task DownloadBytesAsync(string command, CancellationToken cancel, string? arg = null, params string[] options) { var url = BuildCommand(command, arg, options); @@ -469,7 +471,7 @@ public async Task DownloadBytesAsync(string command, CancellationToken c /// /// When the IPFS server indicates an error. /// - public async Task UploadAsync(string command, CancellationToken cancel, Stream data, string name, params string[] options) + public async Task UploadAsync(string command, CancellationToken cancel, Stream data, string? name, params string[] options) { var content = new MultipartFormDataContent(); var streamContent = new StreamContent(data); @@ -491,6 +493,7 @@ public async Task UploadAsync(string command, CancellationToken cancel, return json; } } + /// /// Perform an IPFS API command that /// requires uploading of a "file". @@ -519,7 +522,7 @@ public async Task UploadAsync(string command, CancellationToken cancel, /// /// When the IPFS server indicates an error. /// - public async Task Upload2Async(string command, CancellationToken cancel, Stream data, string name, params string[] options) + public async Task Upload2Async(string command, CancellationToken cancel, Stream data, string? name, params string[] options) { var content = new MultipartFormDataContent(); var streamContent = new StreamContent(data); @@ -589,7 +592,10 @@ async Task ThrowOnErrorAsync(HttpResponseMessage response) try { var res = JsonConvert.DeserializeObject(body); - message = (string)res.Message; + if (res is not null) + { + message = (string)res.Message; + } } catch { } diff --git a/src/IpfsHttpClient.csproj b/src/IpfsHttpClient.csproj index 1472560..2b053cc 100644 --- a/src/IpfsHttpClient.csproj +++ b/src/IpfsHttpClient.csproj @@ -6,10 +6,9 @@ Ipfs.Http bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml full - true - 0.0.6 + 0.0.7 $(Version) @@ -46,7 +45,7 @@ - + diff --git a/src/MerkleNode.cs b/src/MerkleNode.cs index 33332e1..2d1c39c 100644 --- a/src/MerkleNode.cs +++ b/src/MerkleNode.cs @@ -1,4 +1,5 @@ -using System; +using Org.BouncyCastle.Bcpg.OpenPgp; +using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; @@ -16,9 +17,9 @@ public class MerkleNode : IMerkleNode, IEquatable { bool hasBlockStats; long blockSize; - string name; - IEnumerable links; - IpfsClient ipfsClient; + string name = string.Empty; + IEnumerable? links; + IpfsClient? ipfsClient; /// /// Creates a new instance of the with the specified @@ -28,13 +29,10 @@ public class MerkleNode : IMerkleNode, IEquatable /// The of the node. /// /// A name for the node. - public MerkleNode(Cid id, string name = null) + public MerkleNode(Cid id, string? name = null) { - if (id == null) - throw new ArgumentNullException("id"); - Id = id; - Name = name; + Name = name ?? string.Empty; } /// @@ -45,7 +43,7 @@ public MerkleNode(Cid id, string name = null) /// The string representation of a of the node or "/ipfs/cid". /// /// A name for the node. - public MerkleNode(string path, string name = null) + public MerkleNode(string path, string? name = null) { if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path"); @@ -54,7 +52,7 @@ public MerkleNode(string path, string name = null) path = path.Substring(6); Id = Cid.Decode(path); - Name = name; + Name = name ?? string.Empty; } /// @@ -65,7 +63,7 @@ public MerkleNode(string path, string name = null) public MerkleNode(IMerkleLink link) { Id = link.Id; - Name = link.Name; + Name = link.Name ?? string.Empty; blockSize = link.Size; hasBlockStats = true; } @@ -74,7 +72,7 @@ internal IpfsClient IpfsClient { get { - if (ipfsClient == null) + if (ipfsClient is null) { lock (this) { @@ -134,11 +132,7 @@ public IEnumerable Links { get { - if (links == null) - { - links = IpfsClient.Object.LinksAsync(Id).Result; - } - + links ??= IpfsClient.Object.LinksAsync(Id).GetAwaiter().GetResult(); return links; } } @@ -163,7 +157,7 @@ public Stream DataStream } /// - public IMerkleLink ToLink(string name = null) + public IMerkleLink ToLink(string? name = null) { return new DagLink(name ?? Name, Id, BlockSize); } @@ -192,26 +186,26 @@ public override int GetHashCode() } /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { var that = obj as MerkleNode; - return that != null && this.Id == that.Id; + return that is not null && this.Id == that.Id; } /// - public bool Equals(MerkleNode that) + public bool Equals(MerkleNode? that) { - return that != null && this.Id == that.Id; + return that is not null && this.Id == that.Id; } /// /// TODO /// - public static bool operator ==(MerkleNode a, MerkleNode b) + public static bool operator ==(MerkleNode? a, MerkleNode? b) { if (object.ReferenceEquals(a, b)) return true; - if (object.ReferenceEquals(a, null)) return false; - if (object.ReferenceEquals(b, null)) return false; + if (a is null) return false; + if (b is null) return false; return a.Equals(b); } @@ -219,13 +213,9 @@ public bool Equals(MerkleNode that) /// /// TODO /// - public static bool operator !=(MerkleNode a, MerkleNode b) + public static bool operator !=(MerkleNode? a, MerkleNode? b) { - if (object.ReferenceEquals(a, b)) return false; - if (object.ReferenceEquals(a, null)) return true; - if (object.ReferenceEquals(b, null)) return true; - - return !a.Equals(b); + return !(a == b); } /// diff --git a/src/PublishedMessage.cs b/src/PublishedMessage.cs index ca257ca..9dbb933 100644 --- a/src/PublishedMessage.cs +++ b/src/PublishedMessage.cs @@ -29,12 +29,12 @@ public PublishedMessage(string json) { var o = JObject.Parse(json); - this.Sender = (string)o["from"]; - this.SequenceNumber = Multibase.Decode((string)o["seqno"], out MultibaseEncoding _); - this.DataBytes = Multibase.Decode((string)o["data"], out MultibaseEncoding _); + this.Sender = (string)o["from"]!; + this.SequenceNumber = Multibase.Decode((string?)o["seqno"], out MultibaseEncoding _); + this.DataBytes = Multibase.Decode((string?)o["data"], out MultibaseEncoding _); - var topics = (JArray) (o["topicIDs"]); - this.Topics = topics.Select(t => Encoding.UTF8.GetString(Multibase.Decode((string)t, out MultibaseEncoding _))); + var topics = (JArray?) (o["topicIDs"]); + this.Topics = topics.Select(t => Encoding.UTF8.GetString(Multibase.Decode((string?)t, out MultibaseEncoding _))); } /// diff --git a/src/TrustedPeerCollection.cs b/src/TrustedPeerCollection.cs index 61e6e88..45dc2ac 100644 --- a/src/TrustedPeerCollection.cs +++ b/src/TrustedPeerCollection.cs @@ -21,11 +21,11 @@ public class TrustedPeerCollection : ICollection { class BootstrapListResponse { - public MultiAddress[] Peers { get; set; } + public MultiAddress[] Peers { get; set; } = Array.Empty(); } - IpfsClient ipfs; - MultiAddress[] peers; + private readonly IpfsClient ipfs; + MultiAddress[]? peers; internal TrustedPeerCollection(IpfsClient ipfs) { @@ -35,10 +35,7 @@ internal TrustedPeerCollection(IpfsClient ipfs) /// public void Add(MultiAddress peer) { - if (peer == null) - throw new ArgumentNullException(); - - ipfs.DoCommandAsync("bootstrap/add", default(CancellationToken), peer.ToString()).Wait(); + ipfs.DoCommandAsync("bootstrap/add", CancellationToken.None, peer.ToString()).Wait(); peers = null; } @@ -50,7 +47,7 @@ public void Add(MultiAddress peer) /// public void AddDefaultNodes() { - ipfs.DoCommandAsync("bootstrap/add", default(CancellationToken), null, "default=true").Wait(); + ipfs.DoCommandAsync("bootstrap/add", CancellationToken.None, arg: null, "default=true").Wait(); peers = null; } @@ -62,7 +59,7 @@ public void AddDefaultNodes() /// public void Clear() { - ipfs.DoCommandAsync("bootstrap/rm", default(CancellationToken), null, "all=true").Wait(); + ipfs.DoCommandAsync("bootstrap/rm", CancellationToken.None, arg: null, "all=true").Wait(); peers = null; } @@ -77,7 +74,7 @@ public bool Contains(MultiAddress item) public void CopyTo(MultiAddress[] array, int index) { Fetch(); - peers.CopyTo(array, index); + peers!.CopyTo(array, index); } /// @@ -85,7 +82,7 @@ public int Count { get { - if (peers == null) + if (peers is null) Fetch(); return peers.Count(); } @@ -105,10 +102,7 @@ public bool IsReadOnly /// public bool Remove(MultiAddress peer) { - if (peer == null) - throw new ArgumentNullException(); - - ipfs.DoCommandAsync("bootstrap/rm", default(CancellationToken), peer.ToString()).Wait(); + ipfs.DoCommandAsync("bootstrap/rm", CancellationToken.None, peer.ToString()).Wait(); peers = null; return true; } @@ -117,19 +111,19 @@ public bool Remove(MultiAddress peer) public IEnumerator GetEnumerator() { Fetch(); - return ((IEnumerable)peers).GetEnumerator(); + return ((IEnumerable)peers!).GetEnumerator(); } /// IEnumerator IEnumerable.GetEnumerator() { Fetch(); - return peers.GetEnumerator(); + return peers!.GetEnumerator(); } void Fetch() { - peers = ipfs.DoCommandAsync("bootstrap/list", default(CancellationToken)).Result.Peers; + peers = ipfs.DoCommandAsync("bootstrap/list", CancellationToken.None).GetAwaiter().GetResult().Peers; } } } diff --git a/test/BlockTest.cs b/test/BlockTest.cs index 8db34c8..f04eeb7 100644 --- a/test/BlockTest.cs +++ b/test/BlockTest.cs @@ -30,6 +30,5 @@ public void DataStream() Assert.AreEqual(3, stream.ReadByte()); Assert.AreEqual(-1, stream.ReadByte(), "at eof"); } - } } diff --git a/test/CoreApi/BitswapApiTest.cs b/test/CoreApi/BitswapApiTest.cs index 1561aaa..ebf0720 100644 --- a/test/CoreApi/BitswapApiTest.cs +++ b/test/CoreApi/BitswapApiTest.cs @@ -1,34 +1,37 @@ -using Ipfs.Http; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.IO; +using System; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Ipfs.Http { [TestClass] - public class BitswapApiTest + public sealed class BitswapApiTest { - private IpfsClient ipfs = TestFixture.Ipfs; + private readonly IpfsClient ipfs = TestFixture.Ipfs; [TestMethod] public async Task Wants() { var block = new DagNode(Encoding.UTF8.GetBytes("BitswapApiTest unknown block")); -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(() => ipfs.Bitswap.GetAsync(block.Id).Wait()); - - var endTime = DateTime.Now.AddSeconds(10); - while (DateTime.Now < endTime) - { - await Task.Delay(100); - var wants = await ipfs.Bitswap.WantsAsync(); - if (wants.Contains(block.Id)) - return; - } - Assert.Fail("wanted block is missing"); + await RunAsyncTaskAndTestAsync( + ct => ipfs.Bitswap.GetAsync(block.Id, ct), + async () => + { + var endTime = DateTime.Now.AddSeconds(10); + while (DateTime.Now < endTime) + { + await Task.Delay(100); + var wants = await ipfs.Bitswap.WantsAsync(); + if (wants.Contains(block.Id)) + { + return; + } + } + Assert.Fail("wanted block is missing"); + }); } [TestMethod] @@ -36,31 +39,33 @@ public async Task Wants() public async Task Unwant() { var block = new DagNode(Encoding.UTF8.GetBytes("BitswapApiTest unknown block 2")); -#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - Task.Run(() => ipfs.Bitswap.GetAsync(block.Id).Wait()); - - var endTime = DateTime.Now.AddSeconds(10); - while (true) - { - if (DateTime.Now > endTime) - Assert.Fail("wanted block is missing"); - await Task.Delay(100); - var wants = await ipfs.Bitswap.WantsAsync(); - if (wants.Contains(block.Id)) - break; - } + await RunAsyncTaskAndTestAsync( + ct => ipfs.Bitswap.GetAsync(block.Id, ct), + async () => + { + var endTime = DateTime.Now.AddSeconds(10); + while (true) + { + if (DateTime.Now > endTime) + Assert.Fail("wanted block is missing"); + await Task.Delay(100); + var wants = await ipfs.Bitswap.WantsAsync(); + if (wants.Contains(block.Id)) + break; + } - await ipfs.Bitswap.UnwantAsync(block.Id); - endTime = DateTime.Now.AddSeconds(10); - while (true) - { - if (DateTime.Now > endTime) - Assert.Fail("unwanted block is present"); - await Task.Delay(100); - var wants = await ipfs.Bitswap.WantsAsync(); - if (!wants.Contains(block.Id)) - break; - } + await ipfs.Bitswap.UnwantAsync(block.Id); + endTime = DateTime.Now.AddSeconds(10); + while (true) + { + if (DateTime.Now > endTime) + Assert.Fail("unwanted block is present"); + await Task.Delay(100); + var wants = await ipfs.Bitswap.WantsAsync(); + if (!wants.Contains(block.Id)) + break; + } + }); } [TestMethod] @@ -69,7 +74,29 @@ public async Task Ledger() var peer = new Peer { Id = "QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3" }; var ledger = await ipfs.Bitswap.LedgerAsync(peer); Assert.IsNotNull(ledger); - Assert.AreEqual(peer.Id, ledger.Peer.Id); + Assert.IsNotNull(ledger.Peer); + Assert.AreEqual(peer.Id, ledger.Peer!.Id); + } + + private static async Task RunAsyncTaskAndTestAsync(Func asyncTaskWork, Func testWork) + { + var cts = new CancellationTokenSource(); + var asyncTask = Task.Run(async () => await asyncTaskWork(cts.Token)); + try + { + await testWork(); + } + finally + { + cts.Cancel(); + try + { + await asyncTask; + } + catch + { + } + } } } } diff --git a/test/CoreApi/BlockApiTest.cs b/test/CoreApi/BlockApiTest.cs index 5f5c232..c2e3aac 100644 --- a/test/CoreApi/BlockApiTest.cs +++ b/test/CoreApi/BlockApiTest.cs @@ -1,5 +1,4 @@ -using Ipfs.Http; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.IO; using System.Linq; @@ -11,109 +10,109 @@ namespace Ipfs.Http [TestClass] public class BlockApiTest { - private IpfsClient ipfs = TestFixture.Ipfs; - private string id = "QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ"; - private byte[] blob = Encoding.UTF8.GetBytes("blorb"); + private readonly IpfsClient ipfs = TestFixture.Ipfs; + private const string id = "bafkreiaxnnnb7qz2focittuqq3ya25q7rcv3bqynnczfzako47346wosmu"; + private readonly byte[] blob = Encoding.UTF8.GetBytes("blorb"); [TestMethod] - public void Put_Bytes() + public async Task Put_Bytes() { - var cid = ipfs.Block.PutAsync(blob).Result; + var cid = await ipfs.Block.PutAsync(blob); Assert.AreEqual(id, (string)cid); - var data = ipfs.Block.GetAsync(cid).Result; + var data = await ipfs.Block.GetAsync(cid); Assert.AreEqual(blob.Length, data.Size); CollectionAssert.AreEqual(blob, data.DataBytes); } [TestMethod] - public void Put_Bytes_ContentType() + public async Task Put_Bytes_ContentType() { - var cid = ipfs.Block.PutAsync(blob, contentType: "raw").Result; + var cid = await ipfs.Block.PutAsync(blob, contentType: "raw"); Assert.AreEqual("bafkreiaxnnnb7qz2focittuqq3ya25q7rcv3bqynnczfzako47346wosmu", (string)cid); - var data = ipfs.Block.GetAsync(cid).Result; + var data = await ipfs.Block.GetAsync(cid); Assert.AreEqual(blob.Length, data.Size); CollectionAssert.AreEqual(blob, data.DataBytes); } [TestMethod] - public void Put_Bytes_Hash() + public async Task Put_Bytes_Hash() { - var cid = ipfs.Block.PutAsync(blob, "raw", "sha2-512").Result; + var cid = await ipfs.Block.PutAsync(blob, "raw", "sha2-512"); Assert.AreEqual("bafkrgqelljziv4qfg5mefz36m2y3h6voaralnw6lwb4f53xcnrf4mlsykkn7vt6eno547tw5ygcz62kxrle45wnbmpbofo5tvu57jvuaf7k7e", (string)cid); - var data = ipfs.Block.GetAsync(cid).Result; + var data = await ipfs.Block.GetAsync(cid); Assert.AreEqual(blob.Length, data.Size); CollectionAssert.AreEqual(blob, data.DataBytes); } [TestMethod] - public void Put_Bytes_Pinned() + public async Task Put_Bytes_Pinned() { var data1 = new byte[] { 23, 24, 127 }; - var cid1 = ipfs.Block.PutAsync(data1, contentType: "raw", pin: true).Result; - var pins = ipfs.Pin.ListAsync().Result; + var cid1 = await ipfs.Block.PutAsync(data1, contentType: "raw", pin: true); + var pins = await ipfs.Pin.ListAsync(); Assert.IsTrue(pins.Any(pin => pin == cid1)); var data2 = new byte[] { 123, 124, 27 }; - var cid2 = ipfs.Block.PutAsync(data2, contentType: "raw", pin: false).Result; - pins = ipfs.Pin.ListAsync().Result; + var cid2 = await ipfs.Block.PutAsync(data2, contentType: "raw", pin: false); + pins = await ipfs.Pin.ListAsync(); Assert.IsFalse(pins.Any(pin => pin == cid2)); } [TestMethod] - public void Put_Stream() + public async Task Put_Stream() { - var cid = ipfs.Block.PutAsync(new MemoryStream(blob)).Result; + var cid = await ipfs.Block.PutAsync(new MemoryStream(blob)); Assert.AreEqual(id, (string)cid); - var data = ipfs.Block.GetAsync(cid).Result; + var data = await ipfs.Block.GetAsync(cid); Assert.AreEqual(blob.Length, data.Size); CollectionAssert.AreEqual(blob, data.DataBytes); } [TestMethod] - public void Put_Stream_ContentType() + public async Task Put_Stream_ContentType() { - var cid = ipfs.Block.PutAsync(new MemoryStream(blob), contentType: "raw").Result; + var cid = await ipfs.Block.PutAsync(new MemoryStream(blob), contentType: "raw"); Assert.AreEqual("bafkreiaxnnnb7qz2focittuqq3ya25q7rcv3bqynnczfzako47346wosmu", (string)cid); - var data = ipfs.Block.GetAsync(cid).Result; + var data = await ipfs.Block.GetAsync(cid); Assert.AreEqual(blob.Length, data.Size); CollectionAssert.AreEqual(blob, data.DataBytes); } [TestMethod] - public void Put_Stream_Hash() + public async Task Put_Stream_Hash() { - var cid = ipfs.Block.PutAsync(new MemoryStream(blob), "raw", "sha2-512").Result; + var cid = await ipfs.Block.PutAsync(new MemoryStream(blob), "raw", "sha2-512"); Assert.AreEqual("bafkrgqelljziv4qfg5mefz36m2y3h6voaralnw6lwb4f53xcnrf4mlsykkn7vt6eno547tw5ygcz62kxrle45wnbmpbofo5tvu57jvuaf7k7e", (string)cid); - var data = ipfs.Block.GetAsync(cid).Result; + var data = await ipfs.Block.GetAsync(cid); Assert.AreEqual(blob.Length, data.Size); CollectionAssert.AreEqual(blob, data.DataBytes); } [TestMethod] - public void Put_Stream_Pinned() + public async Task Put_Stream_Pinned() { var data1 = new MemoryStream(new byte[] { 23, 24, 127 }); - var cid1 = ipfs.Block.PutAsync(data1, contentType: "raw", pin: true).Result; - var pins = ipfs.Pin.ListAsync().Result; + var cid1 = await ipfs.Block.PutAsync(data1, contentType: "raw", pin: true); + var pins = await ipfs.Pin.ListAsync(); Assert.IsTrue(pins.Any(pin => pin == cid1)); var data2 = new MemoryStream(new byte[] { 123, 124, 27 }); - var cid2 = ipfs.Block.PutAsync(data2, contentType: "raw", pin: false).Result; - pins = ipfs.Pin.ListAsync().Result; + var cid2 = await ipfs.Block.PutAsync(data2, contentType: "raw", pin: false); + pins = await ipfs.Pin.ListAsync(); Assert.IsFalse(pins.Any(pin => pin == cid2)); } [TestMethod] - public void Get() + public async Task Get() { - var _ = ipfs.Block.PutAsync(blob).Result; - var block = ipfs.Block.GetAsync(id).Result; + var _ = await ipfs.Block.PutAsync(blob); + var block = await ipfs.Block.GetAsync(id); Assert.AreEqual(id, (string)block.Id); CollectionAssert.AreEqual(blob, block.DataBytes); var blob1 = new byte[blob.Length]; @@ -122,10 +121,10 @@ public void Get() } [TestMethod] - public void Stat() + public async Task Stat() { - var _ = ipfs.Block.PutAsync(blob).Result; - var info = ipfs.Block.StatAsync(id).Result; + var _ = await ipfs.Block.PutAsync(blob); + var info = await ipfs.Block.StatAsync(id); Assert.AreEqual(id, (string)info.Id); Assert.AreEqual(5, info.Size); } @@ -133,22 +132,23 @@ public void Stat() [TestMethod] public async Task Remove() { - var _ = ipfs.Block.PutAsync(blob).Result; + var _ = await ipfs.Block.PutAsync(blob); var cid = await ipfs.Block.RemoveAsync(id); - Assert.AreEqual(id, (string)cid); + Assert.IsNotNull(cid); + Assert.AreEqual(id, (string)cid!); } [TestMethod] public void Remove_Unknown() { - ExceptionAssert.Throws(() => { var _ = ipfs.Block.RemoveAsync("QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rFF").Result; }); + ExceptionAssert.Throws(() => { var _ = ipfs.Block.RemoveAsync("QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rFF").GetAwaiter().GetResult(); }); } [TestMethod] public async Task Remove_Unknown_OK() { - var cid = await ipfs.Block.RemoveAsync("QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rFF", true); + var cid = await ipfs.Block.RemoveAsync("QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rFF", ignoreNonexistent: true); + Assert.IsNull(cid); } - } } diff --git a/test/CoreApi/BlockRepositoryTest.cs b/test/CoreApi/BlockRepositoryTest.cs index b28bb1d..5848eb8 100644 --- a/test/CoreApi/BlockRepositoryTest.cs +++ b/test/CoreApi/BlockRepositoryTest.cs @@ -21,6 +21,5 @@ public async Task Version() var version = await ipfs.BlockRepository.VersionAsync(); Assert.IsFalse(string.IsNullOrWhiteSpace(version)); } - } } diff --git a/test/CoreApi/BootstrapTest.cs b/test/CoreApi/BootstrapTest.cs index 702a801..1d16a13 100644 --- a/test/CoreApi/BootstrapTest.cs +++ b/test/CoreApi/BootstrapTest.cs @@ -7,8 +7,8 @@ namespace Ipfs.Http [TestClass] public class BootstapApiTest { - IpfsClient ipfs = TestFixture.Ipfs; - MultiAddress somewhere = "/ip4/127.0.0.1/tcp/4009/ipfs/QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ"; + private readonly IpfsClient ipfs = TestFixture.Ipfs; + private readonly MultiAddress somewhere = "/ip4/127.0.0.1/tcp/4009/ipfs/QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ"; [TestMethod] public async Task Add_Remove() diff --git a/test/CoreApi/ConfigApiTest.cs b/test/CoreApi/ConfigApiTest.cs index 0fb20c6..869e8d6 100644 --- a/test/CoreApi/ConfigApiTest.cs +++ b/test/CoreApi/ConfigApiTest.cs @@ -16,7 +16,7 @@ public void Get_Entire_Config() { IpfsClient ipfs = TestFixture.Ipfs; var config = ipfs.Config.GetAsync().Result; - StringAssert.StartsWith(config["Addresses"]["API"].Value(), apiAddress); + StringAssert.StartsWith(config["Addresses"]!["API"]!.Value(), apiAddress); } [TestMethod] @@ -32,8 +32,8 @@ public void Get_Object_Key_Value() { IpfsClient ipfs = TestFixture.Ipfs; var addresses = ipfs.Config.GetAsync("Addresses").Result; - StringAssert.StartsWith(addresses["API"].Value(), apiAddress); - StringAssert.StartsWith(addresses["Gateway"].Value(), gatewayAddress); + StringAssert.StartsWith(addresses["API"]!.Value(), apiAddress); + StringAssert.StartsWith(addresses["Gateway"]!.Value(), gatewayAddress); } [TestMethod] diff --git a/test/CoreApi/DagApiTest.cs b/test/CoreApi/DagApiTest.cs index 817c217..ad430f0 100644 --- a/test/CoreApi/DagApiTest.cs +++ b/test/CoreApi/DagApiTest.cs @@ -9,9 +9,9 @@ public class DagApiTest { class Name { - public string First { get; set; } + public string? First { get; set; } - public string Last { get; set; } + public string? Last { get; set; } } [TestMethod] @@ -29,7 +29,7 @@ public async Task PutAndGet_JSON() Assert.IsNotNull(actual); Assert.AreEqual(expected["a"], actual["a"]); - var value = (string)await ipfs.Dag.GetAsync(expectedId + "/a"); + var value = (string?)await ipfs.Dag.GetAsync(expectedId + "/a"); Assert.AreEqual(expected["a"], value); } @@ -43,10 +43,10 @@ public async Task PutAndGet_POCO() var actual = await ipfs.Dag.GetAsync(id); Assert.IsNotNull(actual); - Assert.AreEqual(expected.First, actual.First); + Assert.AreEqual(expected.First, actual!.First); Assert.AreEqual(expected.Last, actual.Last); - var value = (string)await ipfs.Dag.GetAsync(id.Encode() + "/Last"); + var value = (string?)await ipfs.Dag.GetAsync(id.Encode() + "/Last"); Assert.AreEqual(expected.Last, value); } } diff --git a/test/CoreApi/KeyApiTest.cs b/test/CoreApi/KeyApiTest.cs index d01706d..3c2adfd 100644 --- a/test/CoreApi/KeyApiTest.cs +++ b/test/CoreApi/KeyApiTest.cs @@ -61,7 +61,7 @@ public async Task Remove_Key() var removed = await ipfs.Key.RemoveAsync(name); Assert.IsNotNull(removed); - Assert.AreEqual(key.Name, removed.Name); + Assert.AreEqual(key.Name, removed!.Name); Assert.AreEqual(key.Id, removed.Id); keys = await ipfs.Key.ListAsync(); diff --git a/test/CoreApi/SwarmApiTest.cs b/test/CoreApi/SwarmApiTest.cs index 42a19b3..014f902 100644 --- a/test/CoreApi/SwarmApiTest.cs +++ b/test/CoreApi/SwarmApiTest.cs @@ -60,9 +60,10 @@ public async Task Connection() // tests that a connection can be made to at least one peer. foreach (var peer in peers.Take(2)) { + Assert.IsNotNull(peer.ConnectedAddress); try { - await ipfs.Swarm.ConnectAsync(peer.ConnectedAddress); + await ipfs.Swarm.ConnectAsync(peer.ConnectedAddress!); return; } catch (Exception) diff --git a/test/ExceptionAssert.cs b/test/ExceptionAssert.cs index c34f370..21226c1 100644 --- a/test/ExceptionAssert.cs +++ b/test/ExceptionAssert.cs @@ -9,7 +9,7 @@ namespace Ipfs.Http /// public static class ExceptionAssert { - public static T Throws(Action action, string expectedMessage = null) where T : Exception + public static T Throws(Action action, string? expectedMessage = null) where T : Exception { try { @@ -18,9 +18,9 @@ public static T Throws(Action action, string expectedMessage = null) where T catch (AggregateException e) { var match = e.InnerExceptions.OfType().FirstOrDefault(); - if (match != null) + if (match is not null) { - if (expectedMessage != null) + if (expectedMessage is not null) Assert.AreEqual(expectedMessage, match.Message, "Wrong exception message."); return match; } @@ -29,17 +29,17 @@ public static T Throws(Action action, string expectedMessage = null) where T } catch (T e) { - if (expectedMessage != null) + if (expectedMessage is not null) Assert.AreEqual(expectedMessage, e.Message); return e; } Assert.Fail("Exception of type {0} should be thrown.", typeof(T)); // The compiler doesn't know that Assert.Fail will always throw an exception - return null; + throw new Exception(); } - public static Exception Throws(Action action, string expectedMessage = null) + public static Exception Throws(Action action, string? expectedMessage = null) { return Throws(action, expectedMessage); } diff --git a/test/FileSystemNodeTest.cs b/test/FileSystemNodeTest.cs index 29a414c..913f9a3 100644 --- a/test/FileSystemNodeTest.cs +++ b/test/FileSystemNodeTest.cs @@ -18,7 +18,8 @@ public async Task Serialization() var b = await ipfs.FileSystem.ListFileAsync(a.Id); var json = JsonConvert.SerializeObject(b); var c = JsonConvert.DeserializeObject(json); - Assert.AreEqual(b.Id, c.Id); + Assert.IsNotNull(c); + Assert.AreEqual(b.Id, c!.Id); Assert.AreEqual(b.IsDirectory, c.IsDirectory); Assert.AreEqual(b.Size, c.Size); CollectionAssert.AreEqual(b.Links.ToArray(), c.Links.ToArray()); diff --git a/test/IpfsHttpClientTests.csproj b/test/IpfsHttpClientTests.csproj index 5f7c576..48d52d5 100644 --- a/test/IpfsHttpClientTests.csproj +++ b/test/IpfsHttpClientTests.csproj @@ -1,7 +1,7 @@  - net6.0 + net6.0 false full @@ -9,9 +9,9 @@ - - - + + + diff --git a/test/MerkleNodeTest.cs b/test/MerkleNodeTest.cs index 63d36b3..0eb2910 100644 --- a/test/MerkleNodeTest.cs +++ b/test/MerkleNodeTest.cs @@ -35,9 +35,8 @@ public void FromString() [TestMethod] public void NullHash() { - ExceptionAssert.Throws(() => new MerkleNode((string)null)); - ExceptionAssert.Throws(() => new MerkleNode("")); - ExceptionAssert.Throws(() => new MerkleNode((Cid)null)); + ExceptionAssert.Throws(() => new MerkleNode((string)null!)); + ExceptionAssert.Throws(() => new MerkleNode(string.Empty)); } [TestMethod] @@ -67,19 +66,19 @@ public void Value_Equality() var a0 = new MerkleNode("QmStfpa7ppKPSsdnazBy3Q5QH4zNzGLcpWV88otjVSV7SY"); var a1 = new MerkleNode("QmStfpa7ppKPSsdnazBy3Q5QH4zNzGLcpWV88otjVSV7SY"); var b = new MerkleNode("QmagNHT6twJRBZcGeviiGzHVTMbNnJZameLyL6T14GUHCS"); - MerkleNode nullNode = null; + MerkleNode? nullNode = null; #pragma warning disable 1718 Assert.IsTrue(a0 == a0); Assert.IsTrue(a0 == a1); Assert.IsFalse(a0 == b); - Assert.IsFalse(a0 == null); + Assert.IsNotNull(a0); #pragma warning disable 1718 Assert.IsFalse(a0 != a0); Assert.IsFalse(a0 != a1); Assert.IsTrue(a0 != b); - Assert.IsTrue(a0 != null); + Assert.IsNotNull(a0); Assert.IsTrue(a0.Equals(a0)); Assert.IsTrue(a0.Equals(a1)); @@ -89,21 +88,21 @@ public void Value_Equality() Assert.AreEqual(a0, a0); Assert.AreEqual(a0, a1); Assert.AreNotEqual(a0, b); - Assert.AreNotEqual(a0, null); + Assert.IsNotNull(a0); - Assert.AreEqual(a0, a0); - Assert.AreEqual(a0, a1); - Assert.AreNotEqual(a0, b); - Assert.AreNotEqual(a0, null); + Assert.AreEqual(a0!, a0!); + Assert.AreEqual(a0!, a1); + Assert.AreNotEqual(a0!, b); + Assert.AreNotEqual(a0!, null!); - Assert.AreEqual(a0.GetHashCode(), a0.GetHashCode()); + Assert.AreEqual(a0!.GetHashCode(), a0.GetHashCode()); Assert.AreEqual(a0.GetHashCode(), a1.GetHashCode()); Assert.AreNotEqual(a0.GetHashCode(), b.GetHashCode()); - Assert.IsTrue(nullNode == null); - Assert.IsFalse(null == a0); - Assert.IsFalse(nullNode != null); - Assert.IsTrue(null != a0); + Assert.IsNull(nullNode); + Assert.IsNotNull(a0); + Assert.IsNull(nullNode); + Assert.IsNotNull(a0); } [TestMethod] diff --git a/test/PublishedMessageTest.cs b/test/PublishedMessageTest.cs index d59bfe6..e68e1b4 100644 --- a/test/PublishedMessageTest.cs +++ b/test/PublishedMessageTest.cs @@ -40,6 +40,5 @@ public void Id_NotSupported() var _ = msg.Id; }); } - } }