Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
radicle-desktop crates radicle-tauri src commands cob patch.rs
use std::ops::ControlFlow;

use radicle::patch::cache::Patches as _;
use radicle::patch::{ReviewId, TYPENAME};
use radicle::storage::ReadStorage;
use radicle::{cob, git, identity};

use radicle_types as types;
use radicle_types::cobs;
use radicle_types::domain::patch::models;
use radicle_types::domain::patch::service::Service;
use radicle_types::domain::patch::traits::PatchService;
use radicle_types::error::Error;
use radicle_types::outbound::sqlite::Sqlite;
use radicle_types::traits::cobs::Cobs;
use radicle_types::traits::patch::Patches;
use radicle_types::traits::patch::PatchesMut;
use radicle_types::traits::Profile;

use crate::AppState;

use either::Either;

#[tauri::command]
pub async fn list_patches(
    ctx: tauri::State<'_, AppState>,
    sqlite_service: tauri::State<'_, Service<Sqlite>>,
    rid: identity::RepoId,
    status: Option<types::cobs::query::PatchStatus>,
    skip: Option<usize>,
    // None: return all patches, `skip` is ignored.
    take: Option<usize>,
) -> Result<types::cobs::PaginatedQuery<Vec<models::patch::Patch>>, Error> {
    let profile = ctx.profile();
    let cursor = skip.unwrap_or(0);
    let aliases = profile.aliases();
    let counts = sqlite_service.counts(rid)?;
    let patches = match status.clone() {
        None => Either::Left(sqlite_service.list(rid)?),
        Some(s) => Either::Right(sqlite_service.list_by_status(rid, s.into())?),
    };

    match take {
        None => {
            let content = patches
                .map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases))
                .collect::<Vec<_>>();

            Ok::<_, Error>(cobs::PaginatedQuery {
                cursor: 0,
                more: false,
                content,
            })
        }
        Some(take) => {
            let total = status.map_or_else(|| counts.total(), |status| counts[status.into()]);
            let more = cursor + take < total;

            let content = patches
                .map(|(id, patch)| models::patch::Patch::new(id, &patch, &aliases))
                .skip(cursor)
                .take(take)
                .collect::<Vec<_>>();

            Ok::<_, Error>(cobs::PaginatedQuery {
                cursor,
                more,
                content,
            })
        }
    }
}

#[tauri::command]
pub fn patch_by_id(
    ctx: tauri::State<AppState>,
    rid: identity::RepoId,
    id: git::Oid,
) -> Result<Option<models::patch::Patch>, Error> {
    ctx.get_patch(rid, id)
}

#[tauri::command]
pub fn revisions_by_patch(
    ctx: tauri::State<AppState>,
    rid: identity::RepoId,
    id: git::Oid,
) -> Result<Option<Vec<models::patch::Revision>>, Error> {
    ctx.revisions_by_patch(rid, id)
}

#[tauri::command]
pub fn revision_by_patch_and_id(
    ctx: tauri::State<AppState>,
    rid: identity::RepoId,
    id: git::Oid,
    revision_id: git::Oid,
) -> Result<Option<models::patch::Revision>, Error> {
    ctx.revision_by_id(rid, id, revision_id)
}

#[tauri::command]
pub fn review_by_patch_and_revision_and_id(
    ctx: tauri::State<AppState>,
    rid: identity::RepoId,
    id: git::Oid,
    revision_id: git::Oid,
    review_id: cob::patch::ReviewId,
) -> Result<Option<models::patch::Review>, Error> {
    ctx.review_by_id(rid, id, revision_id, review_id)
}

#[tauri::command]
pub fn edit_patch(
    ctx: tauri::State<AppState>,
    rid: identity::RepoId,
    cob_id: git::Oid,
    action: models::patch::Action,
    opts: cobs::CobOptions,
) -> Result<models::patch::Patch, Error> {
    ctx.edit_patch(rid, cob_id, action, opts)
}

#[tauri::command]
pub fn create_patch_review(
    ctx: tauri::State<AppState>,
    args: models::patch::CreateReviewArgs,
) -> Result<ReviewId, Error> {
    let repo = ctx.profile.storage.repository(args.rid)?;
    let signer = ctx.profile.signer()?;
    let mut patches = ctx.profile.patches_mut(&repo)?;
    let patch_id = match patches.find_by_revision(&args.revision)? {
        Some(found) => found.id,
        None => return Err(cob::patch::Error::RevisionNotFound(args.revision).into()),
    };
    let mut patch = patches.get_mut(&patch_id)?;
    let review_id = patch.review(
        args.revision,
        args.verdict.map(Into::into),
        args.summary,
        args.labels,
        &signer,
    )?;

    for comment in args.comments {
        patch.review_comment(
            review_id,
            comment.body,
            comment.location.map(Into::into),
            None,
            vec![],
            &signer,
        )?;
    }

    Ok(review_id)
}

#[tauri::command]
pub fn activity_by_patch(
    ctx: tauri::State<AppState>,
    rid: identity::RepoId,
    id: git::Oid,
) -> Result<Vec<types::cobs::Operation<models::patch::Action>>, Error> {
    ctx.activity_by_id(rid, &TYPENAME, id)
}

#[tauri::command]
pub async fn rebuild_patch_cache(
    ctx: tauri::State<'_, AppState>,
    rid: identity::RepoId,
    on_event: tauri::ipc::Channel<cobs::CacheEvent>,
) -> Result<(), Error> {
    let repo = ctx.profile.storage.repository(rid)?;
    let mut patches = ctx.profile.patches_mut(&repo)?;
    on_event.send(types::cobs::CacheEvent::Started { rid })?;
    patches.write_all(|result, progress| {
        match result {
            Ok((id, _)) => {
                if on_event
                    .send(cobs::CacheEvent::Progress {
                        rid,
                        oid: **id,
                        current: progress.current(),
                        total: progress.total(),
                    })
                    .is_err()
                {
                    log::error!("Failed to send progress");
                }
            }
            Err(err) => log::warn!("Failed to retrieve patch: {err}"),
        };
        ControlFlow::Continue(())
    })?;
    on_event.send(types::cobs::CacheEvent::Finished { rid })?;

    Ok(())
}