diff --git a/Iroh.xcframework/ios-arm64/Iroh.framework/Headers/iroh_ffiFFI.h b/Iroh.xcframework/ios-arm64/Iroh.framework/Headers/iroh_ffiFFI.h index 19b0202a..b2b7e04b 100644 --- a/Iroh.xcframework/ios-arm64/Iroh.framework/Headers/iroh_ffiFFI.h +++ b/Iroh.xcframework/ios-arm64/Iroh.framework/Headers/iroh_ffiFFI.h @@ -650,6 +650,16 @@ void uniffi_iroh_ffi_fn_init_callback_vtable_blobprovideeventcallback(UniffiVTab uint64_t uniffi_iroh_ffi_fn_method_blobprovideeventcallback_blob_event(void*_Nonnull ptr, void*_Nonnull event ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBSTATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBSTATUS +void*_Nonnull uniffi_iroh_ffi_fn_clone_blobstatus(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_FREE_BLOBSTATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_FREE_BLOBSTATUS +void uniffi_iroh_ffi_fn_free_blobstatus(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBTICKET #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBTICKET void*_Nonnull uniffi_iroh_ffi_fn_clone_blobticket(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status @@ -745,6 +755,11 @@ uint64_t uniffi_iroh_ffi_fn_method_blobs_export(void*_Nonnull ptr, void*_Nonnull uint64_t uniffi_iroh_ffi_fn_method_blobs_get_collection(void*_Nonnull ptr, void*_Nonnull hash ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_HAS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_HAS +uint64_t uniffi_iroh_ffi_fn_method_blobs_has(void*_Nonnull ptr, void*_Nonnull hash +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_LIST #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_LIST uint64_t uniffi_iroh_ffi_fn_method_blobs_list(void*_Nonnull ptr @@ -780,6 +795,11 @@ uint64_t uniffi_iroh_ffi_fn_method_blobs_share(void*_Nonnull ptr, void*_Nonnull uint64_t uniffi_iroh_ffi_fn_method_blobs_size(void*_Nonnull ptr, void*_Nonnull hash ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_STATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_STATUS +uint64_t uniffi_iroh_ffi_fn_method_blobs_status(void*_Nonnull ptr, void*_Nonnull hash +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_WRITE_TO_PATH #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_WRITE_TO_PATH uint64_t uniffi_iroh_ffi_fn_method_blobs_write_to_path(void*_Nonnull ptr, void*_Nonnull hash, RustBuffer path @@ -2861,6 +2881,12 @@ uint16_t uniffi_iroh_ffi_checksum_method_blobs_export(void #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_GET_COLLECTION uint16_t uniffi_iroh_ffi_checksum_method_blobs_get_collection(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_HAS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_HAS +uint16_t uniffi_iroh_ffi_checksum_method_blobs_has(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_LIST @@ -2903,6 +2929,12 @@ uint16_t uniffi_iroh_ffi_checksum_method_blobs_share(void #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_SIZE uint16_t uniffi_iroh_ffi_checksum_method_blobs_size(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_STATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_STATUS +uint16_t uniffi_iroh_ffi_checksum_method_blobs_status(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_WRITE_TO_PATH diff --git a/Iroh.xcframework/ios-arm64_x86_64-simulator/Iroh.framework/Headers/iroh_ffiFFI.h b/Iroh.xcframework/ios-arm64_x86_64-simulator/Iroh.framework/Headers/iroh_ffiFFI.h index 19b0202a..b2b7e04b 100644 --- a/Iroh.xcframework/ios-arm64_x86_64-simulator/Iroh.framework/Headers/iroh_ffiFFI.h +++ b/Iroh.xcframework/ios-arm64_x86_64-simulator/Iroh.framework/Headers/iroh_ffiFFI.h @@ -650,6 +650,16 @@ void uniffi_iroh_ffi_fn_init_callback_vtable_blobprovideeventcallback(UniffiVTab uint64_t uniffi_iroh_ffi_fn_method_blobprovideeventcallback_blob_event(void*_Nonnull ptr, void*_Nonnull event ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBSTATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBSTATUS +void*_Nonnull uniffi_iroh_ffi_fn_clone_blobstatus(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_FREE_BLOBSTATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_FREE_BLOBSTATUS +void uniffi_iroh_ffi_fn_free_blobstatus(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBTICKET #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBTICKET void*_Nonnull uniffi_iroh_ffi_fn_clone_blobticket(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status @@ -745,6 +755,11 @@ uint64_t uniffi_iroh_ffi_fn_method_blobs_export(void*_Nonnull ptr, void*_Nonnull uint64_t uniffi_iroh_ffi_fn_method_blobs_get_collection(void*_Nonnull ptr, void*_Nonnull hash ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_HAS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_HAS +uint64_t uniffi_iroh_ffi_fn_method_blobs_has(void*_Nonnull ptr, void*_Nonnull hash +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_LIST #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_LIST uint64_t uniffi_iroh_ffi_fn_method_blobs_list(void*_Nonnull ptr @@ -780,6 +795,11 @@ uint64_t uniffi_iroh_ffi_fn_method_blobs_share(void*_Nonnull ptr, void*_Nonnull uint64_t uniffi_iroh_ffi_fn_method_blobs_size(void*_Nonnull ptr, void*_Nonnull hash ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_STATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_STATUS +uint64_t uniffi_iroh_ffi_fn_method_blobs_status(void*_Nonnull ptr, void*_Nonnull hash +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_WRITE_TO_PATH #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_WRITE_TO_PATH uint64_t uniffi_iroh_ffi_fn_method_blobs_write_to_path(void*_Nonnull ptr, void*_Nonnull hash, RustBuffer path @@ -2861,6 +2881,12 @@ uint16_t uniffi_iroh_ffi_checksum_method_blobs_export(void #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_GET_COLLECTION uint16_t uniffi_iroh_ffi_checksum_method_blobs_get_collection(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_HAS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_HAS +uint16_t uniffi_iroh_ffi_checksum_method_blobs_has(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_LIST @@ -2903,6 +2929,12 @@ uint16_t uniffi_iroh_ffi_checksum_method_blobs_share(void #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_SIZE uint16_t uniffi_iroh_ffi_checksum_method_blobs_size(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_STATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_STATUS +uint16_t uniffi_iroh_ffi_checksum_method_blobs_status(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_WRITE_TO_PATH diff --git a/Iroh.xcframework/macos-arm64/Iroh.framework/Headers/iroh_ffiFFI.h b/Iroh.xcframework/macos-arm64/Iroh.framework/Headers/iroh_ffiFFI.h index 19b0202a..b2b7e04b 100644 --- a/Iroh.xcframework/macos-arm64/Iroh.framework/Headers/iroh_ffiFFI.h +++ b/Iroh.xcframework/macos-arm64/Iroh.framework/Headers/iroh_ffiFFI.h @@ -650,6 +650,16 @@ void uniffi_iroh_ffi_fn_init_callback_vtable_blobprovideeventcallback(UniffiVTab uint64_t uniffi_iroh_ffi_fn_method_blobprovideeventcallback_blob_event(void*_Nonnull ptr, void*_Nonnull event ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBSTATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBSTATUS +void*_Nonnull uniffi_iroh_ffi_fn_clone_blobstatus(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_FREE_BLOBSTATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_FREE_BLOBSTATUS +void uniffi_iroh_ffi_fn_free_blobstatus(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBTICKET #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_CLONE_BLOBTICKET void*_Nonnull uniffi_iroh_ffi_fn_clone_blobticket(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status @@ -745,6 +755,11 @@ uint64_t uniffi_iroh_ffi_fn_method_blobs_export(void*_Nonnull ptr, void*_Nonnull uint64_t uniffi_iroh_ffi_fn_method_blobs_get_collection(void*_Nonnull ptr, void*_Nonnull hash ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_HAS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_HAS +uint64_t uniffi_iroh_ffi_fn_method_blobs_has(void*_Nonnull ptr, void*_Nonnull hash +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_LIST #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_LIST uint64_t uniffi_iroh_ffi_fn_method_blobs_list(void*_Nonnull ptr @@ -780,6 +795,11 @@ uint64_t uniffi_iroh_ffi_fn_method_blobs_share(void*_Nonnull ptr, void*_Nonnull uint64_t uniffi_iroh_ffi_fn_method_blobs_size(void*_Nonnull ptr, void*_Nonnull hash ); #endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_STATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_STATUS +uint64_t uniffi_iroh_ffi_fn_method_blobs_status(void*_Nonnull ptr, void*_Nonnull hash +); +#endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_WRITE_TO_PATH #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_FN_METHOD_BLOBS_WRITE_TO_PATH uint64_t uniffi_iroh_ffi_fn_method_blobs_write_to_path(void*_Nonnull ptr, void*_Nonnull hash, RustBuffer path @@ -2861,6 +2881,12 @@ uint16_t uniffi_iroh_ffi_checksum_method_blobs_export(void #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_GET_COLLECTION uint16_t uniffi_iroh_ffi_checksum_method_blobs_get_collection(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_HAS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_HAS +uint16_t uniffi_iroh_ffi_checksum_method_blobs_has(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_LIST @@ -2903,6 +2929,12 @@ uint16_t uniffi_iroh_ffi_checksum_method_blobs_share(void #define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_SIZE uint16_t uniffi_iroh_ffi_checksum_method_blobs_size(void +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_STATUS +#define UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_STATUS +uint16_t uniffi_iroh_ffi_checksum_method_blobs_status(void + ); #endif #ifndef UNIFFI_FFIDEF_UNIFFI_IROH_FFI_CHECKSUM_METHOD_BLOBS_WRITE_TO_PATH diff --git a/IrohLib/Sources/IrohLib/IrohLib.swift b/IrohLib/Sources/IrohLib/IrohLib.swift index f8ffff46..fe5b4214 100644 --- a/IrohLib/Sources/IrohLib/IrohLib.swift +++ b/IrohLib/Sources/IrohLib/IrohLib.swift @@ -2235,6 +2235,111 @@ public func FfiConverterTypeBlobProvideEventCallback_lower(_ value: BlobProvideE return FfiConverterTypeBlobProvideEventCallback.lower(value) } +/** + * Status information about a blob. + */ +public protocol BlobStatusProtocol: AnyObject {} + +/** + * Status information about a blob. + */ +open class BlobStatus: + BlobStatusProtocol +{ + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. + #if swift(>=5.8) + @_documentation(visibility: private) + #endif + public struct NoPointer { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + public required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + // This constructor can be used to instantiate a fake object. + // - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + // + // - Warning: + // Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. + #if swift(>=5.8) + @_documentation(visibility: private) + #endif + public init(noPointer _: NoPointer) { + pointer = nil + } + + #if swift(>=5.8) + @_documentation(visibility: private) + #endif + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_iroh_ffi_fn_clone_blobstatus(self.pointer, $0) } + } + + // No primary constructor declared for this class. + + deinit { + guard let pointer = pointer else { + return + } + + try! rustCall { uniffi_iroh_ffi_fn_free_blobstatus(pointer, $0) } + } +} + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif +public struct FfiConverterTypeBlobStatus: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = BlobStatus + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> BlobStatus { + return BlobStatus(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: BlobStatus) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> BlobStatus { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if ptr == nil { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: BlobStatus, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif +public func FfiConverterTypeBlobStatus_lift(_ pointer: UnsafeMutableRawPointer) throws -> BlobStatus { + return try FfiConverterTypeBlobStatus.lift(pointer) +} + +#if swift(>=5.8) + @_documentation(visibility: private) +#endif +public func FfiConverterTypeBlobStatus_lower(_ value: BlobStatus) -> UnsafeMutableRawPointer { + return FfiConverterTypeBlobStatus.lower(value) +} + /** * A token containing everything to get a file from the provider. * @@ -2491,6 +2596,13 @@ public protocol BlobsProtocol: AnyObject { */ func getCollection(hash: Hash) async throws -> Collection + /** + * Check if a blob is completely stored on the node. + * + * This is just a convenience wrapper around `status` that returns a boolean. + */ + func has(hash: Hash) async throws -> Bool + /** * List all complete blobs. * @@ -2545,6 +2657,11 @@ public protocol BlobsProtocol: AnyObject { */ func size(hash: Hash) async throws -> UInt64 + /** + * Check the storage status of a blob on this node. + */ + func status(hash: Hash) async throws -> BlobStatus + /** * Export the blob contents to a file path * The `path` field is expected to be the absolute path. @@ -2780,6 +2897,28 @@ open class Blobs: ) } + /** + * Check if a blob is completely stored on the node. + * + * This is just a convenience wrapper around `status` that returns a boolean. + */ + open func has(hash: Hash) async throws -> Bool { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_iroh_ffi_fn_method_blobs_has( + self.uniffiClonePointer(), + FfiConverterTypeHash.lower(hash) + ) + }, + pollFunc: ffi_iroh_ffi_rust_future_poll_i8, + completeFunc: ffi_iroh_ffi_rust_future_complete_i8, + freeFunc: ffi_iroh_ffi_rust_future_free_i8, + liftFunc: FfiConverterBool.lift, + errorHandler: FfiConverterTypeIrohError__as_error.lift + ) + } + /** * List all complete blobs. * @@ -2936,6 +3075,26 @@ open class Blobs: ) } + /** + * Check the storage status of a blob on this node. + */ + open func status(hash: Hash) async throws -> BlobStatus { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_iroh_ffi_fn_method_blobs_status( + self.uniffiClonePointer(), + FfiConverterTypeHash.lower(hash) + ) + }, + pollFunc: ffi_iroh_ffi_rust_future_poll_pointer, + completeFunc: ffi_iroh_ffi_rust_future_complete_pointer, + freeFunc: ffi_iroh_ffi_rust_future_free_pointer, + liftFunc: FfiConverterTypeBlobStatus.lift, + errorHandler: FfiConverterTypeIrohError__as_error.lift + ) + } + /** * Export the blob contents to a file path * The `path` field is expected to be the absolute path. @@ -17831,6 +17990,9 @@ private var initializationResult: InitializationResult = { if uniffi_iroh_ffi_checksum_method_blobs_get_collection() != 57130 { return InitializationResult.apiChecksumMismatch } + if uniffi_iroh_ffi_checksum_method_blobs_has() != 1301 { + return InitializationResult.apiChecksumMismatch + } if uniffi_iroh_ffi_checksum_method_blobs_list() != 9714 { return InitializationResult.apiChecksumMismatch } @@ -17852,6 +18014,9 @@ private var initializationResult: InitializationResult = { if uniffi_iroh_ffi_checksum_method_blobs_size() != 20254 { return InitializationResult.apiChecksumMismatch } + if uniffi_iroh_ffi_checksum_method_blobs_status() != 34093 { + return InitializationResult.apiChecksumMismatch + } if uniffi_iroh_ffi_checksum_method_blobs_write_to_path() != 47517 { return InitializationResult.apiChecksumMismatch } diff --git a/iroh-js/index.d.ts b/iroh-js/index.d.ts index ae3eea1f..720a0311 100644 --- a/iroh-js/index.d.ts +++ b/iroh-js/index.d.ts @@ -90,6 +90,14 @@ export declare class Blobs { * Method only exists in FFI */ size(hash: string): Promise + /** + * Check if a blob is completely stored on the node. + * + * This is just a convenience wrapper around `status` that returns a boolean. + */ + has(hash: string): Promise + /** Check the storage status of a blob on this node. */ + status(hash: string): Promise /** * Read all bytes of single blob. * @@ -787,6 +795,15 @@ export interface BlobProvideEvent { transferAborted?: TransferAborted } +/** Status information about a blob. */ +export type BlobStatus = + | { type: 'NotFound' } + | { type: 'Partial', /** The size of the currently stored partial blob. */ + size: bigint, /** If the size is verified. */ +sizeIsVerified: boolean } +| { type: 'Complete', /** The size of the blob. */ +size: bigint } + export declare const enum CapabilityKind { /** A writable replica. */ Write = 'Write', diff --git a/iroh-js/src/blob.rs b/iroh-js/src/blob.rs index ef3040e7..fe5dae54 100644 --- a/iroh-js/src/blob.rs +++ b/iroh-js/src/blob.rs @@ -1,6 +1,7 @@ use std::{path::PathBuf, str::FromStr, sync::RwLock}; use futures::{StreamExt, TryStreamExt}; +use iroh_blobs::store::BaoBlobSize; use napi::bindgen_prelude::*; use napi::threadsafe_function::ThreadsafeFunction; use napi_derive::napi; @@ -54,6 +55,25 @@ impl Blobs { Ok(r.size()) } + /// Check if a blob is completely stored on the node. + /// + /// This is just a convenience wrapper around `status` that returns a boolean. + #[napi] + pub async fn has(&self, hash: String) -> Result { + let hash = hash.parse().map_err(anyhow::Error::from)?; + let has_blob = self.client.has(hash).await?; + Ok(has_blob) + } + + /// Check the storage status of a blob on this node. + #[napi] + pub async fn status(&self, hash: String) -> Result { + let hash = hash.parse().map_err(anyhow::Error::from)?; + let status = self.client.status(hash).await?; + + Ok(status.into()) + } + /// Read all bytes of single blob. /// /// This allocates a buffer for the full blob. Use only if you know that the blob you're @@ -619,6 +639,47 @@ impl AddProgress { } } +/// Status information about a blob. +#[derive(Debug, Clone)] +#[napi(string_enum)] +pub enum BlobStatus { + /// The blob is not stored at all. + NotFound, + /// The blob is only stored partially. + Partial { + /// The size of the currently stored partial blob. + size: BigInt, + /// If the size is verified. + size_is_verified: bool, + }, + /// The blob is stored completely. + Complete { + /// The size of the blob. + size: BigInt, + }, +} + +impl From for BlobStatus { + fn from(value: iroh_blobs::rpc::client::blobs::BlobStatus) -> Self { + match value { + iroh_blobs::rpc::client::blobs::BlobStatus::NotFound => Self::NotFound, + iroh_blobs::rpc::client::blobs::BlobStatus::Partial { size } => match size { + BaoBlobSize::Unverified(size) => Self::Partial { + size: size.into(), + size_is_verified: false, + }, + BaoBlobSize::Verified(size) => Self::Partial { + size: size.into(), + size_is_verified: true, + }, + }, + iroh_blobs::rpc::client::blobs::BlobStatus::Complete { size } => { + Self::Complete { size: size.into() } + } + } + } +} + /// Defines the way to read bytes. #[derive(Debug, Default, Clone, Copy)] #[napi(string_enum)] diff --git a/iroh-js/test/blob.mjs b/iroh-js/test/blob.mjs index c25dd684..2d119180 100644 --- a/iroh-js/test/blob.mjs +++ b/iroh-js/test/blob.mjs @@ -39,6 +39,10 @@ suite('blob', () => { assert.ok(allDone.tag) assert.ok(allDone.hash) + // check for existence + const blobExists = await node.blobs.has(allDone.hash) + assert.ok(blobExists) + await node.node.shutdown() }) diff --git a/kotlin/lib/src/main/kotlin/computer/iroh/iroh_ffi.kt b/kotlin/lib/src/main/kotlin/computer/iroh/iroh_ffi.kt index 513e45e8..3eee5405 100644 --- a/kotlin/lib/src/main/kotlin/computer/iroh/iroh_ffi.kt +++ b/kotlin/lib/src/main/kotlin/computer/iroh/iroh_ffi.kt @@ -1294,6 +1294,16 @@ internal interface UniffiLib : Library { `event`: Pointer, ): Long + fun uniffi_iroh_ffi_fn_clone_blobstatus( + `ptr`: Pointer, + uniffi_out_err: UniffiRustCallStatus, + ): Pointer + + fun uniffi_iroh_ffi_fn_free_blobstatus( + `ptr`: Pointer, + uniffi_out_err: UniffiRustCallStatus, + ): Unit + fun uniffi_iroh_ffi_fn_clone_blobticket( `ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus, @@ -1401,6 +1411,11 @@ internal interface UniffiLib : Library { `hash`: Pointer, ): Long + fun uniffi_iroh_ffi_fn_method_blobs_has( + `ptr`: Pointer, + `hash`: Pointer, + ): Long + fun uniffi_iroh_ffi_fn_method_blobs_list(`ptr`: Pointer): Long fun uniffi_iroh_ffi_fn_method_blobs_list_collections(`ptr`: Pointer): Long @@ -1431,6 +1446,11 @@ internal interface UniffiLib : Library { `hash`: Pointer, ): Long + fun uniffi_iroh_ffi_fn_method_blobs_status( + `ptr`: Pointer, + `hash`: Pointer, + ): Long + fun uniffi_iroh_ffi_fn_method_blobs_write_to_path( `ptr`: Pointer, `hash`: Pointer, @@ -3158,6 +3178,8 @@ internal interface UniffiLib : Library { fun uniffi_iroh_ffi_checksum_method_blobs_get_collection(): Short + fun uniffi_iroh_ffi_checksum_method_blobs_has(): Short + fun uniffi_iroh_ffi_checksum_method_blobs_list(): Short fun uniffi_iroh_ffi_checksum_method_blobs_list_collections(): Short @@ -3172,6 +3194,8 @@ internal interface UniffiLib : Library { fun uniffi_iroh_ffi_checksum_method_blobs_size(): Short + fun uniffi_iroh_ffi_checksum_method_blobs_status(): Short + fun uniffi_iroh_ffi_checksum_method_blobs_write_to_path(): Short fun uniffi_iroh_ffi_checksum_method_collection_blobs(): Short @@ -3736,6 +3760,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_iroh_ffi_checksum_method_blobs_get_collection() != 57130.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_iroh_ffi_checksum_method_blobs_has() != 1301.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_iroh_ffi_checksum_method_blobs_list() != 9714.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -3757,6 +3784,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_iroh_ffi_checksum_method_blobs_size() != 20254.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_iroh_ffi_checksum_method_blobs_status() != 34093.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_iroh_ffi_checksum_method_blobs_write_to_path() != 47517.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -6502,9 +6532,9 @@ open class Authors : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -7829,6 +7859,226 @@ public object FfiConverterTypeBlobProvideEventCallback : FfiConverter callWithPointer(block: (ptr: Pointer) -> R): R { + // Check and increment the call counter, to keep the object alive. + // This needs a compare-and-set retry loop in case of concurrent updates. + do { + val c = this.callCounter.get() + if (c == 0L) { + throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") + } + if (c == Long.MAX_VALUE) { + throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow") + } + } while (!this.callCounter.compareAndSet(c, c + 1L)) + // Now we can safely do the method call without the pointer being freed concurrently. + try { + return block(this.uniffiClonePointer()) + } finally { + // This decrement always matches the increment we performed above. + if (this.callCounter.decrementAndGet() == 0L) { + cleanable.clean() + } + } + } + + // Use a static inner class instead of a closure so as not to accidentally + // capture `this` as part of the cleanable's action. + private class UniffiCleanAction( + private val pointer: Pointer?, + ) : Runnable { + override fun run() { + pointer?.let { ptr -> + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_iroh_ffi_fn_free_blobstatus(ptr, status) + } + } + } + } + + fun uniffiClonePointer(): Pointer = + uniffiRustCall { status -> + UniffiLib.INSTANCE.uniffi_iroh_ffi_fn_clone_blobstatus(pointer!!, status) + } + + companion object +} + +/** + * @suppress + */ +public object FfiConverterTypeBlobStatus : FfiConverter { + override fun lower(value: BlobStatus): Pointer = value.uniffiClonePointer() + + override fun lift(value: Pointer): BlobStatus = BlobStatus(value) + + override fun read(buf: ByteBuffer): BlobStatus { + // The Rust code always writes pointers as 8 bytes, and will + // fail to compile if they don't fit. + return lift(Pointer(buf.getLong())) + } + + override fun allocationSize(value: BlobStatus) = 8UL + + override fun write( + value: BlobStatus, + buf: ByteBuffer, + ) { + // The Rust code always expects pointers written as 8 bytes, + // and will fail to compile if they don't fit. + buf.putLong(Pointer.nativeValue(lower(value))) + } +} + +// This template implements a class for working with a Rust struct via a Pointer/Arc +// to the live Rust struct on the other side of the FFI. +// +// Each instance implements core operations for working with the Rust `Arc` and the +// Kotlin Pointer to work with the live Rust struct on the other side of the FFI. +// +// There's some subtlety here, because we have to be careful not to operate on a Rust +// struct after it has been dropped, and because we must expose a public API for freeing +// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are: +// +// * Each instance holds an opaque pointer to the underlying Rust struct. +// Method calls need to read this pointer from the object's state and pass it in to +// the Rust FFI. +// +// * When an instance is no longer needed, its pointer should be passed to a +// special destructor function provided by the Rust FFI, which will drop the +// underlying Rust struct. +// +// * Given an instance, calling code is expected to call the special +// `destroy` method in order to free it after use, either by calling it explicitly +// or by using a higher-level helper like the `use` method. Failing to do so risks +// leaking the underlying Rust struct. +// +// * We can't assume that calling code will do the right thing, and must be prepared +// to handle Kotlin method calls executing concurrently with or even after a call to +// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`. +// +// * We must never allow Rust code to operate on the underlying Rust struct after +// the destructor has been called, and must never call the destructor more than once. +// Doing so may trigger memory unsafety. +// +// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner` +// is implemented to call the destructor when the Kotlin object becomes unreachable. +// This is done in a background thread. This is not a panacea, and client code should be aware that +// 1. the thread may starve if some there are objects that have poorly performing +// `drop` methods or do significant work in their `drop` methods. +// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`, +// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html). +// +// If we try to implement this with mutual exclusion on access to the pointer, there is the +// possibility of a race between a method call and a concurrent call to `destroy`: +// +// * Thread A starts a method call, reads the value of the pointer, but is interrupted +// before it can pass the pointer over the FFI to Rust. +// * Thread B calls `destroy` and frees the underlying Rust struct. +// * Thread A resumes, passing the already-read pointer value to Rust and triggering +// a use-after-free. +// +// One possible solution would be to use a `ReadWriteLock`, with each method call taking +// a read lock (and thus allowed to run concurrently) and the special `destroy` method +// taking a write lock (and thus blocking on live method calls). However, we aim not to +// generate methods with any hidden blocking semantics, and a `destroy` method that might +// block if called incorrectly seems to meet that bar. +// +// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track +// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy` +// has been called. These are updated according to the following rules: +// +// * The initial value of the counter is 1, indicating a live object with no in-flight calls. +// The initial value for the flag is false. +// +// * At the start of each method call, we atomically check the counter. +// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted. +// If it is nonzero them we atomically increment it by 1 and proceed with the method call. +// +// * At the end of each method call, we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// * When `destroy` is called, we atomically flip the flag from false to true. +// If the flag was already true we silently fail. +// Otherwise we atomically decrement and check the counter. +// If it has reached zero then we destroy the underlying Rust struct. +// +// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc` works, +// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`. +// +// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been +// called *and* all in-flight method calls have completed, avoiding violating any of the expectations +// of the underlying Rust code. +// +// This makes a cleaner a better alternative to _not_ calling `destroy()` as +// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop` +// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner +// thread may be starved, and the app will leak memory. +// +// In this case, `destroy`ing manually may be a better solution. +// +// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects +// with Rust peers are reclaimed: +// +// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen: +// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then: +// 3. The memory is reclaimed when the process terminates. +// +// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219 +// + /** * A token containing everything to get a file from the provider. * @@ -8256,6 +8506,13 @@ public interface BlobsInterface { */ suspend fun `getCollection`(`hash`: Hash): Collection + /** + * Check if a blob is completely stored on the node. + * + * This is just a convenience wrapper around `status` that returns a boolean. + */ + suspend fun `has`(`hash`: Hash): kotlin.Boolean + /** * List all complete blobs. * @@ -8318,6 +8575,11 @@ public interface BlobsInterface { */ suspend fun `size`(`hash`: Hash): kotlin.ULong + /** + * Check the storage status of a blob on this node. + */ + suspend fun `status`(`hash`: Hash): BlobStatus + /** * Export the blob contents to a file path * The `path` field is expected to be the absolute path. @@ -8431,9 +8693,9 @@ open class Blobs : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -8463,9 +8725,9 @@ open class Blobs : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -8536,9 +8798,9 @@ open class Blobs : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -8658,6 +8920,30 @@ open class Blobs : IrohException.ErrorHandler, ) + /** + * Check if a blob is completely stored on the node. + * + * This is just a convenience wrapper around `status` that returns a boolean. + */ + @Throws(IrohException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `has`(`hash`: Hash): kotlin.Boolean = + uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_iroh_ffi_fn_method_blobs_has( + thisPtr, + FfiConverterTypeHash.lower(`hash`), + ) + }, + { future, callback, continuation -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_i8(future, callback, continuation) }, + { future, continuation -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_complete_i8(future, continuation) }, + { future -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_free_i8(future) }, + // lift function + { FfiConverterBoolean.lift(it) }, + // Error FFI converter + IrohException.ErrorHandler, + ) + /** * List all complete blobs. * @@ -8674,9 +8960,9 @@ open class Blobs : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -8704,9 +8990,9 @@ open class Blobs : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -8734,9 +9020,9 @@ open class Blobs : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -8772,9 +9058,9 @@ open class Blobs : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -8804,9 +9090,9 @@ open class Blobs : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -8870,6 +9156,28 @@ open class Blobs : IrohException.ErrorHandler, ) + /** + * Check the storage status of a blob on this node. + */ + @Throws(IrohException::class) + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + override suspend fun `status`(`hash`: Hash): BlobStatus = + uniffiRustCallAsync( + callWithPointer { thisPtr -> + UniffiLib.INSTANCE.uniffi_iroh_ffi_fn_method_blobs_status( + thisPtr, + FfiConverterTypeHash.lower(`hash`), + ) + }, + { future, callback, continuation -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_pointer(future, callback, continuation) }, + { future, continuation -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_complete_pointer(future, continuation) }, + { future -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_free_pointer(future) }, + // lift function + { FfiConverterTypeBlobStatus.lift(it) }, + // Error FFI converter + IrohException.ErrorHandler, + ) + /** * Export the blob contents to a file path * The `path` field is expected to be the absolute path. @@ -9491,9 +9799,9 @@ open class Connecting : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -9533,9 +9841,9 @@ open class Connecting : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -9557,9 +9865,9 @@ open class Connecting : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -9900,9 +10208,9 @@ open class Connection : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -10009,9 +10317,9 @@ open class Connection : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -11193,9 +11501,9 @@ open class Doc : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -11224,9 +11532,9 @@ open class Doc : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -11252,9 +11560,9 @@ open class Doc : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -11279,9 +11587,9 @@ open class Doc : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -11500,9 +11808,9 @@ open class Doc : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -13404,9 +13712,9 @@ open class Docs : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -13434,9 +13742,9 @@ open class Docs : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -16545,9 +16853,9 @@ open class Iroh : uniffiRustCallAsync( UniffiLib.INSTANCE.uniffi_iroh_ffi_fn_constructor_iroh_memory(), { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_pointer(future, callback, continuation) }, @@ -16568,9 +16876,9 @@ open class Iroh : uniffiRustCallAsync( UniffiLib.INSTANCE.uniffi_iroh_ffi_fn_constructor_iroh_memory_with_options(FfiConverterTypeNodeOptions.lower(`options`)), { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_pointer(future, callback, continuation) }, @@ -16594,9 +16902,9 @@ open class Iroh : uniffiRustCallAsync( UniffiLib.INSTANCE.uniffi_iroh_ffi_fn_constructor_iroh_persistent(FfiConverterString.lower(`path`)), { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_pointer(future, callback, continuation) }, @@ -16623,9 +16931,9 @@ open class Iroh : FfiConverterTypeNodeOptions.lower(`options`), ), { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_pointer(future, callback, continuation) }, @@ -17830,9 +18138,9 @@ open class Net : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -17878,9 +18186,9 @@ open class Net : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -17906,9 +18214,9 @@ open class Net : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -17933,9 +18241,9 @@ open class Net : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -18230,9 +18538,9 @@ open class Node : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -21301,9 +21609,9 @@ open class RecvStream : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -21326,9 +21634,9 @@ open class RecvStream : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -21351,9 +21659,9 @@ open class RecvStream : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -21376,9 +21684,9 @@ open class RecvStream : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -21400,9 +21708,9 @@ open class RecvStream : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -21690,9 +21998,9 @@ open class SendStream : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -21770,9 +22078,9 @@ open class SendStream : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, @@ -22948,9 +23256,9 @@ open class Tags : ) }, { - future, - callback, - continuation, + future, + callback, + continuation, -> UniffiLib.INSTANCE.ffi_iroh_ffi_rust_future_poll_rust_buffer(future, callback, continuation) }, diff --git a/kotlin/lib/src/test/kotlin/computer/iroh/BlobTest.kt b/kotlin/lib/src/test/kotlin/computer/iroh/BlobTest.kt index 1004cf7a..00cc47ac 100644 --- a/kotlin/lib/src/test/kotlin/computer/iroh/BlobTest.kt +++ b/kotlin/lib/src/test/kotlin/computer/iroh/BlobTest.kt @@ -116,6 +116,10 @@ class BlobTest { assert(gotBytes.size == blobSize) assert(gotBytes contentEquals bytes) + // check the blob exists + val blobExists = node.blobs().has(hash) + assert(blobExists) + node.node().shutdown() } diff --git a/python/blob_test.py b/python/blob_test.py index 68259936..9214cf9e 100644 --- a/python/blob_test.py +++ b/python/blob_test.py @@ -66,6 +66,10 @@ async def test_blob_add_get_bytes(): assert len(got_bytes) == blob_size assert got_bytes == bytes + # check that it exists + blob_exists = await node.blobs().has(hash) + assert blob_exists + # test functionality between reading bytes from a path and writing bytes to # a path @pytest.mark.asyncio diff --git a/src/blob.rs b/src/blob.rs index 3a0a6805..648cbb4a 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -6,6 +6,7 @@ use std::{ }; use futures::{StreamExt, TryStreamExt}; +use iroh_blobs::store::BaoBlobSize; use serde::{Deserialize, Serialize}; use crate::{node::Iroh, BlobsClient, CallbackError, NetClient}; @@ -57,6 +58,23 @@ impl Blobs { Ok(r.size()) } + /// Check if a blob is completely stored on the node. + /// + /// This is just a convenience wrapper around `status` that returns a boolean. + #[uniffi::method(async_runtime = "tokio")] + pub async fn has(&self, hash: &Hash) -> Result { + let has_blob = self.client.has(hash.0).await?; + Ok(has_blob) + } + + /// Check the storage status of a blob on this node. + #[uniffi::method(async_runtime = "tokio")] + pub async fn status(&self, hash: &Hash) -> Result { + let status = self.client.status(hash.0).await?; + + Ok(status.into()) + } + /// Read all bytes of single blob. /// /// This allocates a buffer for the full blob. Use only if you know that the blob you're @@ -356,6 +374,46 @@ impl From for BlobAddOutcome { } } +/// Status information about a blob. +#[derive(Debug, uniffi::Object, Clone, Copy)] +pub enum BlobStatus { + /// The blob is not stored at all. + NotFound, + /// The blob is only stored partially. + Partial { + /// The size of the currently stored partial blob. + size: u64, + /// If the size is verified. + size_is_verified: bool, + }, + /// The blob is stored completely. + Complete { + /// The size of the blob. + size: u64, + }, +} + +impl From for BlobStatus { + fn from(value: iroh_blobs::rpc::client::blobs::BlobStatus) -> Self { + match value { + iroh_blobs::rpc::client::blobs::BlobStatus::NotFound => Self::NotFound, + iroh_blobs::rpc::client::blobs::BlobStatus::Partial { size } => match size { + BaoBlobSize::Unverified(size) => Self::Partial { + size, + size_is_verified: false, + }, + BaoBlobSize::Verified(size) => Self::Partial { + size, + size_is_verified: true, + }, + }, + iroh_blobs::rpc::client::blobs::BlobStatus::Complete { size } => { + Self::Complete { size } + } + } + } +} + /// Defines the way to read bytes. #[derive(Debug, uniffi::Object, Default, Clone, Copy)] pub enum ReadAtLen { @@ -1919,6 +1977,10 @@ mod tests { assert_eq!(num_blobs, got_hashes.len()); hashes_exist(&hashes, &got_hashes); + for hash in &got_hashes { + assert!(node.blobs().has(hash).await.unwrap()); + } + let remove_hash = hashes.pop().unwrap(); let remove_tag = tags.pop().unwrap(); // delete the tag for the first blob @@ -1935,5 +1997,7 @@ mod tests { panic!("blob {} should have been removed", remove_hash); } } + + assert!(!node.blobs().has(&remove_hash).await.unwrap()); } }