forest/cli/subcommands/
index_cmd.rs1use crate::ipld::ChainExportState;
5use crate::rpc::chain::{
6 ApiIndexBackfillStatus, IndexBackfill, IndexBackfillCancel, IndexBackfillParams,
7 IndexBackfillStatus,
8};
9use crate::rpc::{self, prelude::*};
10use crate::shim::clock::ChainEpoch;
11use clap::Subcommand;
12use indicatif::{ProgressBar, ProgressStyle};
13use std::time::Duration;
14
15#[derive(Debug, Subcommand)]
16pub enum IndexCommands {
17 #[command(group(clap::ArgGroup::new("range").required(true).args(["to", "n_tipsets"])))]
22 Backfill {
23 #[arg(long)]
26 from: Option<ChainEpoch>,
27 #[arg(long)]
29 to: Option<ChainEpoch>,
30 #[arg(long, conflicts_with = "to")]
32 n_tipsets: Option<u64>,
33 #[arg(long)]
36 recompute: bool,
37 #[arg(long)]
40 allow_near_head: bool,
41 #[arg(long)]
44 resume: bool,
45 #[arg(long)]
47 no_wait: bool,
48 },
49 BackfillStatus {
51 #[arg(long)]
53 wait: bool,
54 },
55 BackfillCancel {},
57}
58
59impl IndexCommands {
60 pub async fn run(self, client: rpc::Client) -> anyhow::Result<()> {
61 match self {
62 Self::Backfill {
63 from,
64 to,
65 n_tipsets,
66 recompute,
67 allow_near_head,
68 resume,
69 no_wait,
70 } => {
71 let params = IndexBackfillParams {
72 from,
73 to,
74 n_tipsets,
75 recompute,
76 allow_near_head,
77 resume,
78 };
79 client
80 .call(IndexBackfill::request((params,))?.with_timeout(Duration::from_secs(30)))
81 .await?;
82 println!("Index backfill started.");
83 if no_wait {
84 println!("Use `forest-cli index backfill-status` to monitor progress.");
85 return Ok(());
86 }
87 wait_for_backfill(&client).await
88 }
89 Self::BackfillStatus { wait } => {
90 let status = client
91 .call(IndexBackfillStatus::request(())?.with_timeout(Duration::from_secs(30)))
92 .await?;
93 if !wait || status.state != ChainExportState::Running {
94 println!("{status}");
95 return Ok(());
96 }
97 wait_for_backfill(&client).await
98 }
99 Self::BackfillCancel {} => {
100 let cancelled = client
101 .call(IndexBackfillCancel::request(())?.with_timeout(Duration::from_secs(30)))
102 .await?;
103 if cancelled {
104 println!("Index backfill cancelled.");
105 } else {
106 println!("No index backfill in progress to cancel.");
107 }
108 Ok(())
109 }
110 }
111 }
112}
113
114async fn wait_for_backfill(client: &rpc::Client) -> anyhow::Result<()> {
117 let pb = ProgressBar::new(10000).with_message("Backfilling index");
118 pb.set_style(
119 ProgressStyle::with_template("[{elapsed_precise}] [{wide_bar}] {percent}% {msg}")
120 .expect("indicatif template must be valid")
121 .progress_chars("#>-"),
122 );
123 let last: ApiIndexBackfillStatus = loop {
124 let status = client
125 .call(IndexBackfillStatus::request(())?.with_timeout(Duration::from_secs(30)))
126 .await?;
127 let position = (status.progress.clamp(0.0, 1.0) * 10000.0).trunc() as u64;
128 pb.set_position(position);
129 if status.state != ChainExportState::Running {
130 break status;
131 }
132 tokio::time::sleep(Duration::from_millis(500)).await;
133 };
134 match last.state {
135 ChainExportState::Succeeded => pb.finish_with_message(format!(
136 "Backfill completed (indexed {}, skipped {})",
137 last.indexed, last.skipped
138 )),
139 ChainExportState::Cancelled => pb.abandon_with_message(format!(
140 "Backfill cancelled (indexed {}, skipped {})",
141 last.indexed, last.skipped
142 )),
143 _ => {
144 pb.abandon_with_message("Backfill failed");
145 anyhow::bail!(
146 "index backfill failed: {}",
147 last.error.as_deref().unwrap_or("unknown error")
148 );
149 }
150 }
151 Ok(())
152}