Radish alpha
r
Radicle desktop app
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add seed_artifact / unseed_artifact / is_seeding commands
Daniel Norman committed 7 days ago
commit 2545f4ee5ec5c88577b6432063289f1821f3a892
parent 02827e993ba139541ffcf7f38b3c8777dddd8ca3
3 files changed +95 -0
modified crates/radicle-tauri/src/commands/cob/release.rs
@@ -4,8 +4,10 @@ use radicle::git;
use radicle::identity::RepoId;
use radicle_types::cobs::release;
use radicle_types::error::Error;
+
use radicle_types::seeder;
use radicle_types::traits::release::Releases;
use radicle_types::traits::release_mut::ReleasesMut;
+
use radicle_types::IrohState;

use crate::AppState;

@@ -140,6 +142,63 @@ pub async fn redact_artifact(
    .map_err(|e| Error::Iroh(format!("join: {e}")))?
}

+
// Seed / unseed -------------------------------------------------------------
+

+
#[tauri::command]
+
pub async fn seed_artifact(
+
    ctx: tauri::State<'_, AppState>,
+
    iroh: tauri::State<'_, IrohState>,
+
    rid: RepoId,
+
    release_id: String,
+
    cid: String,
+
    source_path: PathBuf,
+
) -> Result<String, Error> {
+
    // 1. Import bytes into the store with ImportMode::Copy so the source
+
    //    file/dir can be moved or deleted later without breaking the seed.
+
    seeder::seed(&iroh.blobs, &cid, &source_path).await?;
+

+
    // 2. Register the location on the COB so peers can discover us. Form
+
    //    is iroh://{our_endpoint_id} — explicit because our iroh key is
+
    //    independent from the Radicle DID.
+
    let url = seeder::our_iroh_url(iroh.iroh_router.endpoint());
+
    let url_for_return = url.clone();
+
    let ctx_clone = ctx.inner().clone();
+
    tauri::async_runtime::spawn_blocking(move || ctx_clone.add_location(rid, release_id, cid, url))
+
        .await
+
        .map_err(|e| Error::Iroh(format!("join: {e}")))??;
+

+
    Ok(url_for_return)
+
}
+

+
#[tauri::command]
+
pub async fn unseed_artifact(
+
    ctx: tauri::State<'_, AppState>,
+
    iroh: tauri::State<'_, IrohState>,
+
    rid: RepoId,
+
    release_id: String,
+
    cid: String,
+
) -> Result<(), Error> {
+
    // Drop the COB location first so peers stop trying to reach us before
+
    // we untag the blob.
+
    let url = seeder::our_iroh_url(iroh.iroh_router.endpoint());
+
    let cid_for_remove = cid.clone();
+
    let release_id_for_remove = release_id.clone();
+
    let ctx_clone = ctx.inner().clone();
+
    tauri::async_runtime::spawn_blocking(move || {
+
        ctx_clone.remove_location(rid, release_id_for_remove, cid_for_remove, url)
+
    })
+
    .await
+
    .map_err(|e| Error::Iroh(format!("join: {e}")))??;
+

+
    seeder::unseed(&iroh.blobs, &cid).await?;
+
    Ok(())
+
}
+

+
#[tauri::command]
+
pub async fn is_seeding(iroh: tauri::State<'_, IrohState>, cid: String) -> Result<bool, Error> {
+
    seeder::is_seeded_str(&iroh.blobs, &cid).await
+
}
+

// Settings ------------------------------------------------------------------

#[tauri::command]
modified crates/radicle-tauri/src/lib.rs
@@ -54,6 +54,9 @@ pub fn run() {
            cob::release::remove_location,
            cob::release::attest_artifact,
            cob::release::redact_artifact,
+
            cob::release::seed_artifact,
+
            cob::release::unseed_artifact,
+
            cob::release::is_seeding,
            cob::release::get_auto_seed_artifacts,
            cob::release::set_auto_seed_artifacts,
            cob::save_embed_by_bytes,
modified crates/radicle-types/src/seeder.rs
@@ -182,6 +182,39 @@ pub async fn is_seeded(store: &Store, cid: &Cid) -> Result<bool, Error> {
    Ok(info.is_some())
}

+
/// String-taking wrapper for callers (Tauri commands) that don't pull in
+
/// the `cid` crate. Parses the CID, dispatches to the right import, sets
+
/// the seeded tag, and returns the import hash as a string for logging.
+
pub async fn seed(store: &Store, cid: &str, source: &Path) -> Result<(), Error> {
+
    let parsed_cid = Cid::from_str(cid).map_err(|e| Error::Iroh(format!("invalid cid: {e}")))?;
+
    let kind = cid_utils::artifact_kind(&parsed_cid)
+
        .map_err(|e| Error::Iroh(format!("cid kind: {e}")))?;
+
    let hash = match kind {
+
        ArtifactKind::Blob => import_blob(store, source, &parsed_cid).await?,
+
        ArtifactKind::Collection => import_collection(store, source, &parsed_cid).await?,
+
    };
+
    register_seeded(store, &parsed_cid, hash).await?;
+
    Ok(())
+
}
+

+
pub async fn unseed(store: &Store, cid: &str) -> Result<(), Error> {
+
    let parsed_cid = Cid::from_str(cid).map_err(|e| Error::Iroh(format!("invalid cid: {e}")))?;
+
    unregister_seeded(store, &parsed_cid).await
+
}
+

+
pub async fn is_seeded_str(store: &Store, cid: &str) -> Result<bool, Error> {
+
    let parsed_cid = Cid::from_str(cid).map_err(|e| Error::Iroh(format!("invalid cid: {e}")))?;
+
    is_seeded(store, &parsed_cid).await
+
}
+

+
/// Build the location URL we register on the COB when seeding. Form:
+
/// `iroh://{endpoint_id_z32}` — explicit because our iroh key is
+
/// independent from the Radicle DID, so the bare-`iroh://` derive-from-DID
+
/// convention used by radworks-app does not apply.
+
pub fn our_iroh_url(endpoint: &iroh::Endpoint) -> String {
+
    format!("iroh://{}", endpoint.id())
+
}
+

/// Return the set of CIDs we currently have seeded locally. Decoding
/// failures (unlikely — we wrote the tags ourselves) are skipped.
pub async fn seeded_cids(store: &Store) -> Result<HashSet<Cid>, Error> {