diff --git a/.gitignore b/.gitignore index f03d425..c8e9b2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +# Ide + +.idea +.env + # User-specific files *.suo *.user diff --git a/src/DreadIpfsHttpClient.nuspec b/src/DreadIpfsHttpClient.nuspec new file mode 100644 index 0000000..aab5931 --- /dev/null +++ b/src/DreadIpfsHttpClient.nuspec @@ -0,0 +1,31 @@ + + + + DreadIpfsHttpClient + 0.5.0 + IpfsHttpClient + docs\README.md + DreadfulBot + false + MIT + + https://github.com/DreadfulBot/net-ipfs-http-client + IpfsClient with some improvements + Folder upload function fixed + DreadfulBot + ipfs files nft blockchain liberation + + + + + + + + + + + + + + + diff --git a/src/IpfsClient.cs b/src/IpfsClient.cs index 62b1c1c..08c65b5 100644 --- a/src/IpfsClient.cs +++ b/src/IpfsClient.cs @@ -1,8 +1,7 @@ -using Ipfs.CoreApi; -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -10,6 +9,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Ipfs.CoreApi; +using Newtonsoft.Json; namespace Ipfs.Http { @@ -59,7 +60,8 @@ public IpfsClient() var assembly = typeof(IpfsClient).GetTypeInfo().Assembly; var version = assembly.GetName().Version; - UserAgent = string.Format("{0}/{1}.{2}.{3}", assembly.GetName().Name, version.Major, version.Minor, version.Revision); + UserAgent = string.Format("{0}/{1}.{2}.{3}", assembly.GetName().Name, version.Major, version.Minor, + version.Revision); TrustedPeers = new TrustedPeerCollection(this); Bootstrap = new BootstrapApi(this); @@ -229,7 +231,7 @@ HttpClient Api() if (HttpMessageHandler is HttpClientHandler handler && handler.SupportsAutomaticDecompression) { handler.AutomaticDecompression = DecompressionMethods.GZip - | DecompressionMethods.Deflate; + | DecompressionMethods.Deflate; } api = new HttpClient(HttpMessageHandler) @@ -241,6 +243,7 @@ HttpClient Api() } } } + return api; } @@ -271,7 +274,8 @@ 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); @@ -284,6 +288,74 @@ public async Task DoCommandAsync(string command, CancellationToken cance } } + /// + /// Executes /api/v0/add command + /// See details here - https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-add + /// + /// FileSystem path + /// Cancellation token + /// Args + /// Options + /// Response + public async Task> DoAddCommand( + string path, + CancellationToken cancel, + string arg = null, + params string[] options) + { + var url = BuildCommand("add", arg, options); + + var dirInfo = new DirectoryInfo(Path.GetFullPath(path)); + + if (!dirInfo.Exists) + { + throw new Exception("Directory does not exists"); + } + + var upperLevelFolder = dirInfo.Parent?.FullName ?? Path.GetFullPath(path); + + string GetFileName(string cPath) + { + // var relativePath = Path.GetRelativePath(upperLevelFolder, cPath); + // var relativePath = Path.join(upperLevelFolder, cPath); + + var relativePath = cPath.Replace(upperLevelFolder, ""); + return $"{Uri.EscapeDataString(relativePath)}"; + } + + ByteArrayContent addFile(FileInfo cFile) + { + var content = new ByteArrayContent(File.ReadAllBytes(cFile.FullName)); + content.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") + { + Name = "file", + FileName = GetFileName(cFile.FullName) + }; + content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + return content; + } + + var content = new MultipartFormDataContent(); + + foreach (var file in dirInfo.EnumerateFiles()) + { + content.Add(addFile(file)); + } + + using var response = await Api().PostAsync(url, content, cancel); + await ThrowOnErrorAsync(response); + + var strBody = await response.Content.ReadAsStringAsync(); + + var result = strBody + .Split('\n') + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(JsonConvert.DeserializeObject) + .ToList(); + + return result; + } + internal Task DoCommandAsync(Uri url, byte[] bytes, CancellationToken cancel) { return DoCommandAsync(url, new ByteArrayContent(bytes), cancel); @@ -299,13 +371,14 @@ internal Task DoCommandAsync(Uri url, string str, CancellationToken cancel) return DoCommandAsync(url, new StringContent(str), cancel); } - internal async Task DoCommandAsync(Uri url, HttpContent content, CancellationToken cancel) + internal async Task DoCommandAsync(Uri url, HttpContent content, CancellationToken cancel) { - using (var response = await Api().PostAsync(url, new MultipartFormDataContent { { content, "\"file\"" } }, cancel)) - { - await ThrowOnErrorAsync(response); - var body = await response.Content.ReadAsStringAsync(); - } + using var response = + await Api().PostAsync(url, new MultipartFormDataContent {{content, "\"file\""}}, cancel); + + await ThrowOnErrorAsync(response); + var body = await response.Content.ReadAsStringAsync(); + return body; } @@ -339,7 +412,8 @@ 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) + 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); @@ -367,7 +441,8 @@ 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); @@ -402,7 +477,8 @@ 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); @@ -435,7 +511,8 @@ 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); @@ -473,7 +550,8 @@ 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); @@ -495,6 +573,7 @@ public async Task UploadAsync(string command, CancellationToken cancel, return json; } } + /// /// Perform an IPFS API command that /// requires uploading of a "file". @@ -523,7 +602,8 @@ 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); @@ -543,7 +623,8 @@ public async Task Upload2Async(string command, CancellationToken cancel, /// /// TODO /// - public async Task UploadAsync(string command, CancellationToken cancel, byte[] data, params string[] options) + public async Task UploadAsync(string command, CancellationToken cancel, byte[] data, + params string[] options) { var content = new MultipartFormDataContent(); var streamContent = new ByteArrayContent(data); @@ -593,17 +674,21 @@ async Task ThrowOnErrorAsync(HttpResponseMessage response) try { var res = JsonConvert.DeserializeObject(body); - message = (string)res.Message; + message = (string) res.Message; + } + catch + { } - catch { } throw new HttpRequestException(message); } /// - public IAsyncEnumerable Ping(MultiHash peer, int count = 10, CancellationToken cancel = new CancellationToken()) => Generic.Ping(peer, count, cancel); + public IAsyncEnumerable Ping(MultiHash peer, int count = 10, + CancellationToken cancel = new CancellationToken()) => Generic.Ping(peer, count, cancel); /// - public IAsyncEnumerable Ping(MultiAddress address, int count = 10, CancellationToken cancel = new CancellationToken()) => Generic.Ping(address, count, cancel); + public IAsyncEnumerable Ping(MultiAddress address, int count = 10, + CancellationToken cancel = new CancellationToken()) => Generic.Ping(address, count, cancel); } -} +} \ No newline at end of file diff --git a/src/IpfsFIle.cs b/src/IpfsFIle.cs new file mode 100644 index 0000000..02ccf7e --- /dev/null +++ b/src/IpfsFIle.cs @@ -0,0 +1,28 @@ +using System.Runtime.Serialization; + +namespace Ipfs.Http; + +/// +/// IpfsFile +/// +[DataContract] +public class IpfsFile +{ + /// + /// Name + /// + [DataMember(Name = "Name")] + public string Name { get; set; } + + /// + /// Hash + /// + [DataMember(Name = "Hash")] + public string Hash { get; set; } + + /// + /// Size + /// + [DataMember(Name = "Size")] + public string Size { get; set; } +} \ No newline at end of file diff --git a/src/IpfsHttpClient.csproj b/src/IpfsHttpClient.csproj index 82bef48..00fe509 100644 --- a/src/IpfsHttpClient.csproj +++ b/src/IpfsHttpClient.csproj @@ -1,12 +1,12 @@  - netstandard2.0 IpfsShipyard.Ipfs.Http.Client Ipfs.Http bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml full true + README.md 0.5.0 @@ -27,6 +27,7 @@ True https://github.com/ipfs-shipyard/net-ipfs-http-client icon.png + netstandard2.0 @@ -71,6 +72,10 @@ The obsolete IFileSystemApi.ListFileAsync was removed due to prior deprecation a Added missing IFileSystemApi.ListAsync. Doesn't fully replace the removed IFileSystemApi.ListFileAsync, but is a step in the right direction. See https://github.com/ipfs/kubo/issues/7493#issuecomment-2016563729. + + + + diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..c2d06de --- /dev/null +++ b/src/Makefile @@ -0,0 +1,5 @@ +include .env +nuget-push: + nuget push bin/Release/$(PACKAGE_NAME).$(VERSION).nupkg -Source https://api.nuget.org/v3/index.json -ApiKey $(NUGET_API_KEY) +nuget-pack: + nuget pack $(PACKAGE_NAME).nuspec -Version $(VERSION) -Properties Configuration=Release -OutputDirectory bin/Release diff --git a/test/CoreApi/FileSystemApiTest.cs b/test/CoreApi/FileSystemApiTest.cs index 6df79cb..57a6762 100644 --- a/test/CoreApi/FileSystemApiTest.cs +++ b/test/CoreApi/FileSystemApiTest.cs @@ -3,7 +3,6 @@ using System; using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -17,7 +16,7 @@ public void AddText() { var ipfs = TestFixture.Ipfs; var result = ipfs.FileSystem.AddTextAsync("hello world").Result; - Assert.AreEqual("Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", (string)result.Id); + Assert.AreEqual("Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", (string) result.Id); } [TestMethod] @@ -38,7 +37,7 @@ public void AddFile() { var ipfs = TestFixture.Ipfs; var result = ipfs.FileSystem.AddFileAsync(path).Result; - Assert.AreEqual("Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", (string)result.Id); + Assert.AreEqual("Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", (string) result.Id); Assert.AreEqual(0, result.Links.Count()); } finally @@ -51,7 +50,7 @@ public void AddFile() public void Read_With_Offset() { var ipfs = TestFixture.Ipfs; - var indata = new MemoryStream(new byte[] { 10, 20, 30 }); + var indata = new MemoryStream(new byte[] {10, 20, 30}); var node = ipfs.FileSystem.AddAsync(indata).Result; using (var outdata = ipfs.FileSystem.ReadFileAsync(node.Id, offset: 1).Result) { @@ -65,7 +64,7 @@ public void Read_With_Offset() public void Read_With_Offset_Length_1() { var ipfs = TestFixture.Ipfs; - var indata = new MemoryStream(new byte[] { 10, 20, 30 }); + var indata = new MemoryStream(new byte[] {10, 20, 30}); var node = ipfs.FileSystem.AddAsync(indata).Result; using (var outdata = ipfs.FileSystem.ReadFileAsync(node.Id, offset: 1, count: 1).Result) { @@ -78,7 +77,7 @@ public void Read_With_Offset_Length_1() public void Read_With_Offset_Length_2() { var ipfs = TestFixture.Ipfs; - var indata = new MemoryStream(new byte[] { 10, 20, 30 }); + var indata = new MemoryStream(new byte[] {10, 20, 30}); var node = ipfs.FileSystem.AddAsync(indata).Result; using (var outdata = ipfs.FileSystem.ReadFileAsync(node.Id, offset: 1, count: 2).Result) { @@ -92,8 +91,8 @@ public void Read_With_Offset_Length_2() public void Add_NoPin() { var ipfs = TestFixture.Ipfs; - var data = new MemoryStream(new byte[] { 11, 22, 33 }); - var options = new AddFileOptions { Pin = false }; + var data = new MemoryStream(new byte[] {11, 22, 33}); + var options = new AddFileOptions {Pin = false}; var node = ipfs.FileSystem.AddAsync(data, "", options).Result; var pins = ipfs.Pin.ListAsync().Result; Assert.IsFalse(pins.Any(pin => pin == node.Id)); @@ -103,7 +102,7 @@ public void Add_NoPin() public async Task Add_Wrap() { var path = "hello.txt"; - File.WriteAllText(path, "hello world"); + await File.WriteAllTextAsync(path, "hello world"); try { var ipfs = TestFixture.Ipfs; @@ -112,11 +111,11 @@ public async Task Add_Wrap() Wrap = true }; var node = await ipfs.FileSystem.AddFileAsync(path, options); - Assert.AreEqual("QmNxvA5bwvPGgMXbmtyhxA1cKFdvQXnsGnZLCGor3AzYxJ", (string)node.Id); + Assert.AreEqual("QmNxvA5bwvPGgMXbmtyhxA1cKFdvQXnsGnZLCGor3AzYxJ", (string) node.Id); Assert.AreEqual(true, node.IsDirectory); Assert.AreEqual(1, node.Links.Count()); Assert.AreEqual("hello.txt", node.Links.First().Name); - Assert.AreEqual("Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", (string)node.Links.First().Id); + Assert.AreEqual("Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", (string) node.Links.First().Id); } finally { @@ -134,15 +133,15 @@ public async Task Add_SizeChunking() }; options.Pin = true; var node = await ipfs.FileSystem.AddTextAsync("hello world", options); - Assert.AreEqual("QmVVZXWrYzATQdsKWM4knbuH5dgHFmrRqW3nJfDgdWrBjn", (string)node.Id); + Assert.AreEqual("QmVVZXWrYzATQdsKWM4knbuH5dgHFmrRqW3nJfDgdWrBjn", (string) node.Id); Assert.AreEqual(false, node.IsDirectory); var links = (await ipfs.Object.LinksAsync(node.Id)).ToArray(); Assert.AreEqual(4, links.Length); - Assert.AreEqual("QmevnC4UDUWzJYAQtUSQw4ekUdqDqwcKothjcobE7byeb6", (string)links[0].Id); - Assert.AreEqual("QmTdBogNFkzUTSnEBQkWzJfQoiWbckLrTFVDHFRKFf6dcN", (string)links[1].Id); - Assert.AreEqual("QmPdmF1n4di6UwsLgW96qtTXUsPkCLN4LycjEUdH9977d6", (string)links[2].Id); - Assert.AreEqual("QmXh5UucsqF8XXM8UYQK9fHXsthSEfi78kewr8ttpPaLRE", (string)links[3].Id); + Assert.AreEqual("QmevnC4UDUWzJYAQtUSQw4ekUdqDqwcKothjcobE7byeb6", (string) links[0].Id); + Assert.AreEqual("QmTdBogNFkzUTSnEBQkWzJfQoiWbckLrTFVDHFRKFf6dcN", (string) links[1].Id); + Assert.AreEqual("QmPdmF1n4di6UwsLgW96qtTXUsPkCLN4LycjEUdH9977d6", (string) links[2].Id); + Assert.AreEqual("QmXh5UucsqF8XXM8UYQK9fHXsthSEfi78kewr8ttpPaLRE", (string) links[3].Id); var text = await ipfs.FileSystem.ReadAllTextAsync(node.Id); Assert.AreEqual("hello world", text); @@ -157,7 +156,7 @@ public async Task Add_Raw() RawLeaves = true }; var node = await ipfs.FileSystem.AddTextAsync("hello world", options); - Assert.AreEqual("bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", (string)node.Id); + Assert.AreEqual("bafkreifzjut3te2nhyekklss27nh3k72ysco7y32koao5eei66wof36n5e", (string) node.Id); Assert.AreEqual(11, node.Size); var text = await ipfs.FileSystem.ReadAllTextAsync(node.Id); @@ -174,20 +173,52 @@ public async Task Add_RawAndChunked() ChunkSize = 3 }; var node = await ipfs.FileSystem.AddTextAsync("hello world", options); - Assert.AreEqual("QmUuooB6zEhMmMaBvMhsMaUzar5gs5KwtVSFqG4C1Qhyhs", (string)node.Id); + Assert.AreEqual("QmUuooB6zEhMmMaBvMhsMaUzar5gs5KwtVSFqG4C1Qhyhs", (string) node.Id); Assert.AreEqual(false, node.IsDirectory); var links = (await ipfs.Object.LinksAsync(node.Id)).ToArray(); Assert.AreEqual(4, links.Length); - Assert.AreEqual("bafkreigwvapses57f56cfow5xvoua4yowigpwcz5otqqzk3bpcbbjswowe", (string)links[0].Id); - Assert.AreEqual("bafkreiew3cvfrp2ijn4qokcp5fqtoknnmr6azhzxovn6b3ruguhoubkm54", (string)links[1].Id); - Assert.AreEqual("bafkreibsybcn72tquh2l5zpim2bba4d2kfwcbpzuspdyv2breaq5efo7tq", (string)links[2].Id); - Assert.AreEqual("bafkreihfuch72plvbhdg46lef3n5zwhnrcjgtjywjryyv7ffieyedccchu", (string)links[3].Id); + Assert.AreEqual("bafkreigwvapses57f56cfow5xvoua4yowigpwcz5otqqzk3bpcbbjswowe", (string) links[0].Id); + Assert.AreEqual("bafkreiew3cvfrp2ijn4qokcp5fqtoknnmr6azhzxovn6b3ruguhoubkm54", (string) links[1].Id); + Assert.AreEqual("bafkreibsybcn72tquh2l5zpim2bba4d2kfwcbpzuspdyv2breaq5efo7tq", (string) links[2].Id); + Assert.AreEqual("bafkreihfuch72plvbhdg46lef3n5zwhnrcjgtjywjryyv7ffieyedccchu", (string) links[3].Id); var text = await ipfs.FileSystem.ReadAllTextAsync(node.Id); Assert.AreEqual("hello world", text); } + [TestMethod] + public void DoAddCommand() + { + var ipfs = TestFixture.Ipfs; + var temp = MakeTemp(); + + try + { + var response = ipfs.DoAddCommand( + temp, + CancellationToken.None + ).Result.ToList(); + + Assert.AreEqual(3, response.Count(), "Expected 3 files to be added"); + + var parentDir = new DirectoryInfo(temp); + + if (parentDir == null) + { + throw new Exception("Parent directory is null"); + } + + Assert.AreEqual(Path.Join(parentDir.Name, "alpha.txt"), response[0].Name); + Assert.AreEqual(Path.Join(parentDir.Name, "beta.txt"), response[1].Name); + Assert.AreEqual(parentDir.Name, response[2].Name); + } + finally + { + DeleteTemp(temp); + } + } + [TestMethod] public void AddDirectory() { @@ -261,17 +292,17 @@ public void AddDirectoryRecursive() } } - [TestMethod] - public async Task GetTar_EmptyDirectory() - { - var ipfs = TestFixture.Ipfs; - var temp = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(temp); - try - { - var dir = ipfs.FileSystem.AddDirectoryAsync(temp, true).Result; - var dirid = dir.Id.Encode(); - + [TestMethod] + public async Task GetTar_EmptyDirectory() + { + var ipfs = TestFixture.Ipfs; + var temp = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(temp); + try + { + var dir = ipfs.FileSystem.AddDirectoryAsync(temp, true).Result; + var dirid = dir.Id.Encode(); + using (var tar = await ipfs.FileSystem.GetAsync(dir.Id)) { var buffer = new byte[3 * 512]; @@ -282,13 +313,14 @@ public async Task GetTar_EmptyDirectory() Assert.IsTrue(n > 0); offset += n; } + Assert.AreEqual(-1, tar.ReadByte()); - } - } - finally - { - DeleteTemp(temp); - } + } + } + finally + { + DeleteTemp(temp); + } } @@ -303,13 +335,10 @@ public async Task AddFile_WithProgress() var bytesTransferred = 0UL; var options = new AddFileOptions { - Progress = new Progress(t => - { - bytesTransferred += t.Bytes; - }) + Progress = new Progress(t => { bytesTransferred += t.Bytes; }) }; var result = await ipfs.FileSystem.AddFileAsync(path, options); - Assert.AreEqual("Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", (string)result.Id); + Assert.AreEqual("Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD", (string) result.Id); // Progress reports get posted on another synchronisation context. var stop = DateTime.Now.AddSeconds(3); @@ -319,6 +348,7 @@ public async Task AddFile_WithProgress() break; await Task.Delay(10); } + Assert.AreEqual(11UL, bytesTransferred); } finally @@ -339,7 +369,7 @@ void DeleteTemp(string temp) catch (Exception) { Thread.Sleep(1); - continue; // most likely anti-virus is reading a file + continue; // most likely anti-virus is reading a file } } } @@ -360,4 +390,4 @@ string MakeTemp() return temp; } } -} +} \ No newline at end of file diff --git a/test/IpfsHttpClientTests.csproj b/test/IpfsHttpClientTests.csproj index 707f186..cb3eea0 100644 --- a/test/IpfsHttpClientTests.csproj +++ b/test/IpfsHttpClientTests.csproj @@ -1,11 +1,11 @@  - net6.0 false full Ipfs.Http + net8.0