Skip to content

Commit

Permalink
Release 0.19.0. See release notes or expand full commit message.
Browse files Browse the repository at this point in the history
[New]
Added IAddFileToGetCid interface. This is functionally identical to IGetCid, but instead of simply returning a CID already in ipfs, it computes the CID by providing data to ipfs using preferences in the AddFileOptions parameter.

[Breaking]
StorableKuboExtensions.GetCidAsync now takes an AddFileOptions parameter.
ContentAddressedSystemFile and ContentAddressedSystemFolder now implement IAddFileToGetCid instead of IGetCid.

[Fixes]
Inherited fixes from OwlCore.ComponentModel 0.9.1.
Fixed issues with MfsStream where it would return before the task was complete.
MfsStream.ReadAsync and MfsStream.WriteAsync now respect the requested offset when operating on the provided buffer.

[Improvement]
Updated to IpfsShipyard.Ipfs.Http.Client 0.5.1.
MfsStream.WriteAsync now supplies Flush = false when writing to mfs, instead of the default of flushing after every write. This improves performance when writing large files, but requires a manual call to FlushAsync to persist the changes.
  • Loading branch information
Arlodotexe committed Sep 14, 2024
1 parent 734035c commit 4240bcc
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 57 deletions.
19 changes: 19 additions & 0 deletions src/Extensions/IAddFileToGetCid.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Ipfs;
using Ipfs.CoreApi;
using OwlCore.Storage;

namespace OwlCore.Kubo;

/// <summary>
/// Implementations are capable of providing a CID for the current content by adding it ipfs.
/// </summary>
public partial interface IAddFileToGetCid : IStorable
{
/// <summary>
/// Gets the CID of the storable item.
/// </summary>
/// <param name="addFileOptions">The add file options to use when computing the cid for this storable.</param>
/// <param name="cancellationToken">A token that can be used to cancel the ongoing operation.</param>
/// <returns></returns>
public Task<Cid> GetCidAsync(AddFileOptions addFileOptions, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace OwlCore.Storage.System.IO;
/// <summary>
/// An implementation of <see cref="SystemFile"/> with added support for <see cref="IGetCid"/>.
/// </summary>
public class ContentAddressedSystemFile : SystemFile, IGetCid
public class ContentAddressedSystemFile : SystemFile, IAddFileToGetCid
{
/// <summary>
/// Creates a new instance of <see cref="SystemFile"/>.
Expand All @@ -27,13 +27,9 @@ public ContentAddressedSystemFile(string path, ICoreApi client)
public ICoreApi Client { get; }

/// <inheritdoc/>
public async Task<Cid> GetCidAsync(CancellationToken cancellationToken)
public async Task<Cid> GetCidAsync(AddFileOptions addFileOptions, CancellationToken cancellationToken)
{
var res = await Client.FileSystem.AddFileAsync(Id, new()
{
OnlyHash = true,
Pin = false
}, cancellationToken);
var res = await Client.FileSystem.AddFileAsync(Id, addFileOptions, cancellationToken);

Guard.IsFalse(res.IsDirectory);
return res.ToLink().Id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace OwlCore.Storage.System.IO;
/// <summary>
/// An implementation of <see cref="SystemFolder"/> with added support for <see cref="IGetCid"/>.
/// </summary>
public class ContentAddressedSystemFolder : SystemFolder, IGetCid
public class ContentAddressedSystemFolder : SystemFolder, IAddFileToGetCid
{
/// <summary>
/// Creates a new instance of <see cref="SystemFolder"/>.
Expand All @@ -27,13 +27,9 @@ public ContentAddressedSystemFolder(string path, ICoreApi client)
public ICoreApi Client { get; }

/// <inheritdoc/>
public async Task<Cid> GetCidAsync(CancellationToken cancellationToken)
public async Task<Cid> GetCidAsync(AddFileOptions addFileOptions, CancellationToken cancellationToken)
{
var res = await Client.FileSystem.AddDirectoryAsync(Id, recursive: true, new()
{
OnlyHash = true,
Pin = false,
}, cancellationToken);
var res = await Client.FileSystem.AddDirectoryAsync(Id, recursive: true, addFileOptions, cancellationToken);

Guard.IsTrue(res.IsDirectory);
return res.ToLink().Id;
Expand Down
33 changes: 21 additions & 12 deletions src/Extensions/StorableKuboExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ namespace OwlCore.Kubo;
/// </summary>
public static partial class StorableKuboExtensions
{
/// <inheritdoc cref="IGetCid.GetCidAsync(CancellationToken)"/>
public static async Task<Cid> GetCidAsync(this IStorable item, ICoreApi client, CancellationToken cancellationToken)
/// <summary>
/// Gets a CID for the provided <paramref name="item"/>. If possible, a CID will be provided without adding the item to ipfs, otherwise the <paramref name="addFileOptions"/> will be used to add content to ipfs and compute the cid.
/// </summary>
/// <param name="item">The storable to get the cid for.</param>
/// <param name="client">The client to use for communicating with ipfs.</param>
/// <param name="addFileOptions">The options to use when adding content from the <paramref name="item"/> to ipfs.</param>
/// <param name="cancellationToken">A token that can be used to cancel the ongoing operation.</param>
/// <returns>A task containing the cid of the <paramref name="item"/>.</returns>
/// <exception cref="NotSupportedException">An unsupported implementation of <see cref="IStorable"/> was provided for <paramref name="item"/>.</exception>
public static async Task<Cid> GetCidAsync(this IStorable item, ICoreApi client, AddFileOptions addFileOptions, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

Expand All @@ -24,28 +32,29 @@ public static async Task<Cid> GetCidAsync(this IStorable item, ICoreApi client,
// You'd typically only do this if you can interact your Kubo node faster than a manually piped stream,
// or if your implementation of IGetCid has enhanced capabilities (e.g. folder support).
// ---
if (item is not IGetCid && item is SystemFile systemFile)

// Get cid without adding to ipfs, if possible
if (item is IGetCid getCid)
return await getCid.GetCidAsync(cancellationToken);

// Get cid by adding content to ipfs.
if (item is not IAddFileToGetCid && item is SystemFile systemFile)
item = new ContentAddressedSystemFile(systemFile.Path, client);

if (item is not IGetCid && item is SystemFolder systemFolder)
if (item is not IAddFileToGetCid && item is SystemFolder systemFolder)
item = new ContentAddressedSystemFolder(systemFolder.Path, client);

// If the implementation can handle content addressing directly, use that.
if (item is IGetCid contentAddressedStorable)
return await contentAddressedStorable.GetCidAsync(cancellationToken);
if (item is IAddFileToGetCid contentAddressedStorable)
return await contentAddressedStorable.GetCidAsync(addFileOptions, cancellationToken);

// Otherwise, a fallback approach that manually connects the streams together.
// The Kubo API doesn't support this scenario for folders, without assuming that the Id is a local path,
// a scenario that's already handled where supported via the IGetCid interface.
if (item is IFile file)
{
using var stream = await file.OpenStreamAsync(FileAccess.Read, cancellationToken);

var res = await client.FileSystem.AddAsync(stream, file.Name, new()
{
OnlyHash = true,
Pin = false,
}, cancellationToken);
var res = await client.FileSystem.AddAsync(stream, file.Name, addFileOptions, cancellationToken);

Guard.IsFalse(res.IsDirectory);
return res.ToLink().Id;
Expand Down
2 changes: 1 addition & 1 deletion src/IpnsFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public IpnsFolder(string ipnsAddress, ICoreApi client)
/// <inheritdoc />
public virtual async IAsyncEnumerable<IStorableChild> GetItemsAsync(StorableType type = StorableType.All, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var cid = await GetCidAsync(cancellationToken);
var cid = await GetCidAsync(Id, cancellationToken);
var itemInfo = await Client.FileSystem.ListAsync(cid, cancellationToken);
Guard.IsTrue(itemInfo.IsDirectory);

Expand Down
58 changes: 42 additions & 16 deletions src/MfsFolder.Modifiable.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CommunityToolkit.Diagnostics;
using Ipfs.CoreApi;
using OwlCore.Kubo.FolderWatchers;
using OwlCore.Storage;

Expand All @@ -11,6 +12,11 @@ public partial class MfsFolder : IModifiableFolder, IMoveFrom, ICreateCopyOf
/// </summary>
public TimeSpan UpdateCheckInterval { get; } = TimeSpan.FromSeconds(10);

/// <summary>
/// The options to use when adding content to this folder on ipfs.
/// </summary>
public AddFileOptions AddFileOptions { get; set; } = new();

/// <inheritdoc/>
public virtual async Task DeleteAsync(IStorableChild item, CancellationToken cancellationToken = default)
{
Expand All @@ -21,24 +27,40 @@ public virtual async Task DeleteAsync(IStorableChild item, CancellationToken can
}

/// <inheritdoc/>
public Task<IChildFile> CreateCopyOfAsync(IFile fileToCopy, bool overwrite, CancellationToken cancellationToken,
CreateCopyOfDelegate fallback)
public async Task<IChildFile> CreateCopyOfAsync(IFile fileToCopy, bool overwrite, CancellationToken cancellationToken, CreateCopyOfDelegate fallback)
{
if (fileToCopy is MfsFile mfsFile)
return CreateCopyOfAsync(mfsFile, overwrite, cancellationToken);
return await CreateCopyOfAsync(mfsFile, overwrite, cancellationToken);

if (fileToCopy is IpfsFile ipfsFile)
return CreateCopyOfAsync(ipfsFile, overwrite, cancellationToken);
return await CreateCopyOfAsync(ipfsFile, overwrite, cancellationToken);

if (fileToCopy is IpnsFile ipnsFile)
return CreateCopyOfAsync(ipnsFile, overwrite, cancellationToken);
return await CreateCopyOfAsync(ipnsFile, overwrite, cancellationToken);

if (fileToCopy is IGetCid getCid)
{
var cid = await getCid.GetCidAsync(cancellationToken);

var newPath = $"{Path}{fileToCopy.Name}";
await Client.Mfs.CopyAsync($"/ipfs/{cid}", newPath, cancel: cancellationToken);
return new MfsFile(newPath, Client);
}

return fallback(this, fileToCopy, overwrite, cancellationToken);
if (fileToCopy is IAddFileToGetCid addFileToGetCid)
{
var cid = await addFileToGetCid.GetCidAsync(AddFileOptions, cancellationToken);

var newPath = $"{Path}{fileToCopy.Name}";
await Client.Mfs.CopyAsync($"/ipfs/{cid}", newPath, cancel: cancellationToken);
return new MfsFile(newPath, Client);
}

return await fallback(this, fileToCopy, overwrite, cancellationToken);
}

/// <inheritdoc/>
public Task<IChildFile> MoveFromAsync(IChildFile fileToMove, IModifiableFolder source, bool overwrite, CancellationToken cancellationToken,
MoveFromDelegate fallback)
public Task<IChildFile> MoveFromAsync(IChildFile fileToMove, IModifiableFolder source, bool overwrite, CancellationToken cancellationToken, MoveFromDelegate fallback)
{
if (fileToMove is MfsFile mfsFile)
return MoveFromAsync(mfsFile, source, overwrite, cancellationToken);
Expand All @@ -51,37 +73,41 @@ public virtual async Task<IChildFile> CreateCopyOfAsync(MfsFile fileToCopy, bool
{
cancellationToken.ThrowIfCancellationRequested();

await Client.Mfs.CopyAsync(fileToCopy.Path, Path, cancel: cancellationToken);
return new MfsFile($"{Path}{fileToCopy.Name}", Client);
var newPath = $"{Path}{fileToCopy.Name}";
await Client.Mfs.CopyAsync(fileToCopy.Path, newPath, cancel: cancellationToken);
return new MfsFile(newPath, Client);
}

/// <inheritdoc cref="CreateCopyOfAsync(IFile,bool,CancellationToken,CreateCopyOfDelegate)"/>
public virtual async Task<IChildFile> CreateCopyOfAsync(IpfsFile fileToCopy, bool overwrite = false, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

await Client.Mfs.CopyAsync($"/ipfs/{fileToCopy.Id}", Path, cancel: cancellationToken);
return new MfsFile($"{Path}{fileToCopy.Name}", Client);
var newPath = $"{Path}{fileToCopy.Name}";
await Client.Mfs.CopyAsync($"/ipfs/{fileToCopy.Id}", newPath, cancel: cancellationToken);
return new MfsFile(newPath, Client);
}

/// <inheritdoc cref="CreateCopyOfAsync(IFile,bool,CancellationToken,CreateCopyOfDelegate)"/>
public virtual async Task<IChildFile> CreateCopyOfAsync(IpnsFile fileToCopy, bool overwrite = false, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

var newPath = $"{Path}{fileToCopy.Name}";
var cid = await fileToCopy.GetCidAsync(cancellationToken);
await Client.Mfs.CopyAsync($"/ipfs/{cid}", Path, cancel: cancellationToken);
await Client.Mfs.CopyAsync($"/ipfs/{cid}", newPath, cancel: cancellationToken);

return new MfsFile($"{Path}{fileToCopy.Name}", Client);
return new MfsFile(newPath, Client);
}

/// <inheritdoc cref="MoveFromAsync(IChildFile,IModifiableFolder,bool,CancellationToken,MoveFromDelegate)"/>
public virtual async Task<IChildFile> MoveFromAsync(MfsFile fileToMove, IModifiableFolder source, bool overwrite = false, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

await Client.Mfs.MoveAsync(fileToMove.Path, $"{Path}{fileToMove.Name}", cancellationToken);
return new MfsFile($"{Path}{fileToMove.Name}", Client);
var newPath = $"{Path}{fileToMove.Name}";
await Client.Mfs.MoveAsync(fileToMove.Path, newPath, cancellationToken);
return new MfsFile(newPath, Client);
}

/// <inheritdoc cref="MoveFromAsync(IChildFile,IModifiableFolder,bool,CancellationToken,MoveFromDelegate)"/>
Expand Down
12 changes: 6 additions & 6 deletions src/MfsStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public MfsStream(string path, long length, ICoreApi client)
/// <inheritdoc/>
public override void Flush()
{
_ = Client.Mfs.FlushAsync(Path).Result;
Client.Mfs.FlushAsync(Path).Wait();
}

/// <inheritdoc/>
Expand All @@ -80,8 +80,7 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
var result = await Client.Mfs.ReadFileStreamAsync(Path, offset: Position, count: count, cancellationToken);
var bytes = await result.ToBytesAsync(cancellationToken);

for (var i = 0; i < bytes.Length; i++)
buffer[i] = bytes[i];
bytes.CopyTo(buffer, offset);

Position += bytes.Length;

Expand All @@ -108,7 +107,7 @@ public override long Seek(long offset, SeekOrigin origin)
if (origin == SeekOrigin.Current)
{
Guard.IsLessThanOrEqualTo(Position + offset, Length);
Position = Position + offset;
Position += offset;
}

return Position;
Expand All @@ -124,7 +123,7 @@ public override void SetLength(long value)
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count, CancellationToken.None).GetResultOrDefault();
WriteAsync(buffer, offset, count, CancellationToken.None).Wait();
}

/// <inheritdoc/>
Expand Down Expand Up @@ -153,7 +152,8 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc
SetLength(Position + count);
}

await Client.Mfs.WriteAsync(Path, buffer, new() { Offset = Position, Count = count, Create = true }, cancellationToken);
await Client.Mfs.WriteAsync(Path, buffer.Skip(offset).ToArray(), new() { Offset = Position, Count = count, Create = true, Flush = false }, cancellationToken);

Position += count;
}

Expand Down
31 changes: 24 additions & 7 deletions src/OwlCore.Kubo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,30 @@
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>

<Author>Arlo Godfrey</Author>
<Version>0.18.0</Version>
<Version>0.19.0</Version>
<Product>OwlCore</Product>
<Description>
An essential toolkit for Kubo, IPFS and the distributed web.
</Description>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageReleaseNotes>
--- 0.19.0 ---
[New]
Added IAddFileToGetCid interface. This is functionally identical to IGetCid, but instead of simply returning a CID already in ipfs, it computes the CID by providing data to ipfs using preferences in the AddFileOptions parameter.

[Breaking]
StorableKuboExtensions.GetCidAsync now takes an AddFileOptions parameter.
ContentAddressedSystemFile and ContentAddressedSystemFolder now implement IAddFileToGetCid instead of IGetCid.

[Fixes]
Inherited fixes from OwlCore.ComponentModel 0.9.1.
Fixed issues with MfsStream where it would return before the task was complete.
MfsStream.ReadAsync and MfsStream.WriteAsync now respect the requested offset when operating on the provided buffer.

[Improvement]
Updated to IpfsShipyard.Ipfs.Http.Client 0.5.1.
MfsStream.WriteAsync now supplies Flush = false when writing to mfs, instead of the default of flushing after every write. This improves performance when writing large files, but requires a manual call to FlushAsync to persist the changes.

--- 0.18.0 ---
[Breaking]
Inherited breaking changes from OwlCore.Storage 0.12.0 and OwlCore.ComponentModel 0.9.0.
Expand Down Expand Up @@ -443,15 +460,15 @@ Added unit tests.
</ItemGroup>

<ItemGroup>
<PackageReference Include="CommunityToolkit.Common" Version="8.3.0" />
<PackageReference Include="CommunityToolkit.Diagnostics" Version="8.3.0" />
<PackageReference Include="IpfsShipyard.Ipfs.Http.Client" Version="0.5.0" />
<PackageReference Include="CommunityToolkit.Common" Version="8.3.1" />
<PackageReference Include="CommunityToolkit.Diagnostics" Version="8.3.1" />
<PackageReference Include="IpfsShipyard.Ipfs.Http.Client" Version="0.5.1" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="OwlCore.ComponentModel.Settings" Version="0.1.0" />
<PackageReference Include="OwlCore.ComponentModel.Settings" Version="0.1.1" />
<PackageReference Include="OwlCore.Diagnostics" Version="0.0.0" />
<PackageReference Include="OwlCore.ComponentModel" Version="0.9.0" />
<PackageReference Include="OwlCore.Extensions" Version="0.9.0" />
<PackageReference Include="OwlCore.ComponentModel" Version="0.9.1" />
<PackageReference Include="OwlCore.Extensions" Version="0.9.1" />
<PackageReference Include="OwlCore.Storage" Version="0.12.0" />
<PackageReference Include="OwlCore.Storage.SharpCompress" Version="0.1.0" />
<PackageReference Include="PolySharp" Version="1.14.1">
Expand Down
2 changes: 1 addition & 1 deletion tests/OwlCore.Kubo.Tests/OwlCore.Kubo.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="OwlCore.Extensions" Version="0.9.0" />
<PackageReference Include="OwlCore.Extensions" Version="0.9.1" />
<PackageReference Include="PolySharp" Version="1.14.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down

0 comments on commit 4240bcc

Please sign in to comment.