pcli/
lib.rs

1#![deny(clippy::unwrap_used)]
2#![allow(clippy::clone_on_copy)]
3
4use {
5    crate::{command::*, config::PcliConfig},
6    anyhow::Result,
7    camino::Utf8PathBuf,
8    directories::ProjectDirs,
9    futures::StreamExt,
10    penumbra_sdk_proto::{
11        box_grpc_svc::BoxGrpcService, custody::v1::custody_service_client::CustodyServiceClient,
12        view::v1::view_service_client::ViewServiceClient,
13    },
14    penumbra_sdk_view::ViewClient,
15    std::path::PathBuf,
16};
17
18pub mod command;
19pub mod config;
20pub mod opt;
21pub mod warning;
22
23mod dex_utils;
24mod network;
25mod terminal;
26mod transaction_view_ext;
27
28const CONFIG_FILE_NAME: &str = "config.toml";
29const VIEW_FILE_NAME: &str = "pcli-view.sqlite";
30
31#[derive(Debug)]
32pub struct App {
33    /// view will be `None` when a command indicates that it can be run offline via
34    /// `.offline()` and Some(_) otherwise. Assuming `.offline()` has been implemenented
35    /// correctly, this can be unwrapped safely.
36    pub view: Option<ViewServiceClient<BoxGrpcService>>,
37    pub custody: CustodyServiceClient<BoxGrpcService>,
38    pub governance_custody: CustodyServiceClient<BoxGrpcService>,
39    pub config: PcliConfig,
40    /// If present, save the transaction here instead of broadcasting it.
41    pub save_transaction_here_instead: Option<PathBuf>,
42}
43
44impl App {
45    pub fn view(&mut self) -> &mut impl ViewClient {
46        self.view.as_mut().expect("view service initialized")
47    }
48
49    pub async fn sync(&mut self) -> Result<()> {
50        let mut status_stream =
51            ViewClient::status_stream(self.view.as_mut().expect("view service initialized"))
52                .await?;
53
54        // Pull out the first message from the stream, which has the current state, and use
55        // it to set up a progress bar.
56        let initial_status = status_stream
57            .next()
58            .await
59            .transpose()?
60            .ok_or_else(|| anyhow::anyhow!("view service did not report sync status"))?;
61
62        eprintln!(
63            "Scanning blocks from last sync height {} to latest height {}",
64            initial_status.full_sync_height, initial_status.latest_known_block_height,
65        );
66
67        use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle};
68        let progress_bar = ProgressBar::with_draw_target(
69            initial_status.latest_known_block_height - initial_status.full_sync_height,
70            ProgressDrawTarget::stdout(),
71        )
72        .with_style(
73            ProgressStyle::default_bar()
74                .template("[{elapsed}] {bar:50.cyan/blue} {pos:>7}/{len:7} {per_sec} ETA: {eta}"),
75        );
76        progress_bar.set_position(0);
77
78        while let Some(status) = status_stream.next().await.transpose()? {
79            progress_bar.set_position(status.full_sync_height - initial_status.full_sync_height);
80        }
81        progress_bar.finish();
82
83        Ok(())
84    }
85}
86
87pub fn default_home() -> Utf8PathBuf {
88    let path = ProjectDirs::from("zone", "penumbra", "pcli")
89        .expect("Failed to get platform data dir")
90        .data_dir()
91        .to_path_buf();
92    Utf8PathBuf::from_path_buf(path).expect("Platform default data dir was not UTF-8")
93}