use std::{path::PathBuf, process::Command};
use crate::{
cli::DEFAULT_CANISTER,
dfx::{
process::{
call_unit_method, canister_id, canister_is_installed, run_external_command,
unreachable_daemon_hint,
},
project::{known_canisters, require_created_canister},
},
};
type CanisterListRow = (String, &'static str, &'static str, String);
pub(crate) fn list_canisters() -> Result<(), String> {
let canisters = known_canisters()?;
if canisters.is_empty() {
println!("No canisters were found in dfx.json.");
return Ok(());
}
let rows = canisters
.into_iter()
.map(|canister| canister_list_row(canister))
.collect::<Vec<_>>();
print_canister_table(rows.as_slice());
Ok(())
}
fn canister_list_row(canister: String) -> CanisterListRow {
let default = if canister == DEFAULT_CANISTER {
"yes"
} else {
"no"
};
match canister_id(canister.as_str()) {
Ok(Some(id)) => (canister, default, "created", id),
Err(err) if unreachable_daemon_hint(err.as_str()).is_some() => (
canister,
default,
"unknown",
"dfx local daemon is not reachable".to_string(),
),
Ok(None) | Err(_) => (canister, default, "not created", "-".to_string()),
}
}
fn print_canister_table(rows: &[CanisterListRow]) {
let canister_width = table_width(
"canister",
rows.iter().map(|(canister, _, _, _)| canister.as_str()),
);
let default_width = table_width("default", rows.iter().map(|(_, default, _, _)| *default));
let created_width = table_width("created", rows.iter().map(|(_, _, created, _)| *created));
let canister_heading = "canister";
let default_heading = "default";
let created_heading = "created";
let principal_heading = "principal";
println!("Known IcyDB canisters:");
println!(
" {canister_heading:<canister_width$} {default_heading:<default_width$} {created_heading:<created_width$} {principal_heading}"
);
for (canister, default, created, principal) in rows {
println!(
" {canister:<canister_width$} {default:<default_width$} {created:<created_width$} {principal}"
);
}
}
fn table_width<'a>(heading: &str, values: impl Iterator<Item = &'a str>) -> usize {
values.map(str::len).max().unwrap_or(0).max(heading.len())
}
pub(crate) fn deploy_canister(canister: &str) -> Result<(), String> {
eprintln!("[icydb] deploying canister '{canister}'");
let mut command = Command::new("dfx");
command.arg("deploy").arg(canister);
run_external_command(command, "dfx deploy")
}
pub(crate) fn reinstall_canister(canister: &str) -> Result<(), String> {
eprintln!("[icydb] reinstalling canister '{canister}' when already installed");
let mut command = Command::new("dfx");
command.arg("deploy").arg(canister);
if canister_is_installed(canister).unwrap_or(false) {
command.arg("--mode").arg("reinstall").arg("--yes");
}
run_external_command(command, "dfx deploy reinstall")
}
pub(crate) fn upgrade_canister(canister: &str, wasm: Option<&PathBuf>) -> Result<(), String> {
let wasm_path = wasm
.cloned()
.unwrap_or_else(|| default_canister_wasm_path(canister));
eprintln!("[icydb] building canister '{canister}' for stable-memory-preserving upgrade");
let mut build = Command::new("dfx");
build.arg("build").arg(canister);
run_external_command(build, "dfx build")?;
if !wasm_path.is_file() {
return Err(format!(
"expected wasm not found after build: {}",
wasm_path.display()
));
}
eprintln!("[icydb] upgrading canister '{canister}' without demo data reset");
let mut install = Command::new("dfx");
install
.arg("canister")
.arg("install")
.arg(canister)
.arg("--mode")
.arg("upgrade")
.arg("--wasm")
.arg(wasm_path);
run_external_command(install, "dfx canister install --mode upgrade")
}
pub(crate) fn status_canister(canister: &str) -> Result<(), String> {
eprintln!("[icydb] reading canister status for '{canister}'");
let mut command = Command::new("dfx");
command.arg("canister").arg("status").arg(canister);
run_external_command(command, "dfx canister status")
}
pub(crate) fn reset_demo_data(canister: &str) -> Result<(), String> {
eprintln!("[icydb] resetting demo data on '{canister}'");
call_canister_unit_method(canister, "fixtures_reset")
}
pub(crate) fn seed_demo_data(canister: &str) -> Result<(), String> {
eprintln!("[icydb] loading default demo data on '{canister}'");
call_canister_unit_method(canister, "fixtures_load_default")
}
pub(crate) fn reload_demo_data(canister: &str) -> Result<(), String> {
reset_demo_data(canister)?;
seed_demo_data(canister)
}
pub(crate) fn fresh_demo(canister: &str) -> Result<(), String> {
reinstall_canister(canister)?;
reload_demo_data(canister)
}
fn call_canister_unit_method(canister: &str, method: &str) -> Result<(), String> {
require_created_canister(canister)?;
call_unit_method(canister, method)
}
fn default_canister_wasm_path(canister: &str) -> PathBuf {
PathBuf::from(format!(".dfx/local/canisters/{canister}/{canister}.wasm"))
}