mirror of
https://codeberg.org/Toasterson/ips.git
synced 2026-04-10 13:20:42 +00:00
- Eliminated LZ4-based compression and related decoding utilities for manifest handling. - Removed unused private helper and legacy methods in `catalog.rs`. - Standardized database path handling, replacing `catalog_db_path` and `obsoleted_db_path` with `active_db_path` and `obsolete_db_path`. - Added `#[allow(dead_code)]` annotations for unused methods in `file_backend.rs` to reduce warnings.
1478 lines
53 KiB
Rust
1478 lines
53 KiB
Rust
mod error;
|
|
use error::{Pkg6Error, Result};
|
|
|
|
use clap::{Parser, Subcommand};
|
|
use serde::Serialize;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use tracing::{debug, error, info};
|
|
use tracing_subscriber::filter::LevelFilter;
|
|
use tracing_subscriber::{EnvFilter, fmt};
|
|
|
|
/// Wrapper struct for publisher output in JSON format
|
|
#[derive(Serialize)]
|
|
struct PublishersOutput {
|
|
publishers: Vec<PublisherOutput>,
|
|
}
|
|
|
|
/// Serializable struct for publisher information
|
|
#[derive(Serialize)]
|
|
struct PublisherOutput {
|
|
name: String,
|
|
origin: String,
|
|
mirrors: Vec<String>,
|
|
is_default: bool,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
catalog_dir: Option<String>,
|
|
}
|
|
|
|
/// pkg6 - Image Packaging System client
|
|
///
|
|
/// The pkg command is used to manage the software installed on an image.
|
|
/// An image can be a boot environment, a zone, or a non-global zone.
|
|
///
|
|
/// The pkg command manages the retrieval, installation, update, and removal
|
|
/// of software packages for the OpenIndiana operating system.
|
|
#[derive(Parser, Debug)]
|
|
#[clap(author, version, about, long_about = None)]
|
|
#[clap(propagate_version = true)]
|
|
struct App {
|
|
/// Path to the image to operate on
|
|
///
|
|
/// If not specified, the default image is determined as follows:
|
|
/// - If $HOME/.pkg exists, that directory is used
|
|
/// - Otherwise, the root directory (/) is used
|
|
#[clap(short = 'R', global = true)]
|
|
image_path: Option<PathBuf>,
|
|
|
|
#[clap(subcommand)]
|
|
command: Commands,
|
|
}
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
enum Commands {
|
|
/// Update the list of available packages and patches
|
|
///
|
|
/// The refresh command updates the local package catalog, retrieving
|
|
/// the latest list of available packages from the configured publishers.
|
|
Refresh {
|
|
/// Perform a full refresh, retrieving all package metadata
|
|
#[clap(long)]
|
|
full: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// Publishers to refresh (default: all)
|
|
publishers: Vec<String>,
|
|
},
|
|
|
|
/// Install or update packages
|
|
///
|
|
/// The install command installs or updates packages from the configured
|
|
/// publishers. If a package is already installed, it will be updated to
|
|
/// the newest version available.
|
|
Install {
|
|
/// Dry run, don't make actual changes
|
|
#[clap(short)]
|
|
dry_run: bool,
|
|
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// Number of concurrent operations
|
|
#[clap(short = 'C')]
|
|
concurrency: Option<usize>,
|
|
|
|
/// Additional package repository to use
|
|
#[clap(short = 'g')]
|
|
repo: Vec<String>,
|
|
|
|
/// Accept all licenses
|
|
#[clap(long)]
|
|
accept: bool,
|
|
|
|
/// Show all licenses
|
|
#[clap(long)]
|
|
licenses: bool,
|
|
|
|
/// Don't update the search index
|
|
#[clap(long)]
|
|
no_index: bool,
|
|
|
|
/// Don't refresh the catalog
|
|
#[clap(long)]
|
|
no_refresh: bool,
|
|
|
|
/// Packages to install
|
|
pkg_fmri_patterns: Vec<String>,
|
|
},
|
|
|
|
/// Install packages while removing all other packages
|
|
///
|
|
/// The exact-install command installs the specified packages and removes
|
|
/// all other packages. This is useful for creating a clean installation
|
|
/// with only the specified packages.
|
|
ExactInstall {
|
|
/// Dry run, don't make actual changes
|
|
#[clap(short)]
|
|
dry_run: bool,
|
|
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// Number of concurrent operations
|
|
#[clap(short = 'C')]
|
|
concurrency: Option<usize>,
|
|
|
|
/// Additional package repository to use
|
|
#[clap(short = 'g')]
|
|
repo: Vec<String>,
|
|
|
|
/// Accept all licenses
|
|
#[clap(long)]
|
|
accept: bool,
|
|
|
|
/// Show all licenses
|
|
#[clap(long)]
|
|
licenses: bool,
|
|
|
|
/// Don't update the search index
|
|
#[clap(long)]
|
|
no_index: bool,
|
|
|
|
/// Don't refresh the catalog
|
|
#[clap(long)]
|
|
no_refresh: bool,
|
|
|
|
/// Packages to install
|
|
pkg_fmri_patterns: Vec<String>,
|
|
},
|
|
|
|
/// Remove packages
|
|
///
|
|
/// The uninstall command removes installed packages from the system.
|
|
Uninstall {
|
|
/// Dry run, don't make actual changes
|
|
#[clap(short)]
|
|
dry_run: bool,
|
|
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// Packages to remove
|
|
pkg_fmri_patterns: Vec<String>,
|
|
},
|
|
|
|
/// Update packages to newer versions
|
|
///
|
|
/// The update command updates installed packages to the newest versions
|
|
/// available from the configured publishers.
|
|
Update {
|
|
/// Dry run, don't make actual changes
|
|
#[clap(short)]
|
|
dry_run: bool,
|
|
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// Number of concurrent operations
|
|
#[clap(short = 'C')]
|
|
concurrency: Option<usize>,
|
|
|
|
/// Additional package repository to use
|
|
#[clap(short = 'g')]
|
|
repo: Vec<String>,
|
|
|
|
/// Accept all licenses
|
|
#[clap(long)]
|
|
accept: bool,
|
|
|
|
/// Show all licenses
|
|
#[clap(long)]
|
|
licenses: bool,
|
|
|
|
/// Don't update the search index
|
|
#[clap(long)]
|
|
no_index: bool,
|
|
|
|
/// Don't refresh the catalog
|
|
#[clap(long)]
|
|
no_refresh: bool,
|
|
|
|
/// Packages to update (default: all)
|
|
pkg_fmri_patterns: Vec<String>,
|
|
},
|
|
|
|
/// List installed packages
|
|
///
|
|
/// The list command displays information about installed packages.
|
|
/// By default, it lists only installed packages. Use the -a flag to list all available packages.
|
|
List {
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// List all available packages, not just installed ones
|
|
#[clap(short)]
|
|
all: bool,
|
|
|
|
/// Output format (default: table)
|
|
#[clap(short = 'o')]
|
|
output_format: Option<String>,
|
|
|
|
/// Packages to list (default: all)
|
|
pkg_fmri_patterns: Vec<String>,
|
|
},
|
|
|
|
/// Display information about packages
|
|
///
|
|
/// The info command displays detailed information about packages.
|
|
Info {
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// Output format (default: table)
|
|
#[clap(short = 'o')]
|
|
output_format: Option<String>,
|
|
|
|
/// Packages to show information about
|
|
pkg_fmri_patterns: Vec<String>,
|
|
},
|
|
|
|
/// Search for packages
|
|
///
|
|
/// The search command searches for packages matching the specified query.
|
|
Search {
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// Output format (default: table)
|
|
#[clap(short = 'o')]
|
|
output_format: Option<String>,
|
|
|
|
/// Search query
|
|
query: String,
|
|
},
|
|
|
|
/// Verify installation of packages
|
|
///
|
|
/// The verify command verifies that installed packages match their
|
|
/// manifest and that all files are present and have the correct
|
|
/// permissions and checksums.
|
|
Verify {
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// Packages to verify (default: all)
|
|
pkg_fmri_patterns: Vec<String>,
|
|
},
|
|
|
|
/// Fix package installation problems
|
|
///
|
|
/// The fix command repairs packages with missing or corrupt files.
|
|
Fix {
|
|
/// Dry run, don't make actual changes
|
|
#[clap(short)]
|
|
dry_run: bool,
|
|
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// Packages to fix (default: all)
|
|
pkg_fmri_patterns: Vec<String>,
|
|
},
|
|
|
|
/// Show history of package operations
|
|
///
|
|
/// The history command displays the history of package operations.
|
|
History {
|
|
/// Number of entries to show
|
|
#[clap(short = 'n')]
|
|
count: Option<usize>,
|
|
|
|
/// Show full details
|
|
#[clap(short)]
|
|
full: bool,
|
|
|
|
/// Output format (default: table)
|
|
#[clap(short = 'o')]
|
|
output_format: Option<String>,
|
|
},
|
|
|
|
/// List contents of packages
|
|
///
|
|
/// The contents command lists the contents of packages.
|
|
Contents {
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Quiet mode, show less output
|
|
#[clap(short)]
|
|
quiet: bool,
|
|
|
|
/// Output format (default: table)
|
|
#[clap(short = 'o')]
|
|
output_format: Option<String>,
|
|
|
|
/// Packages to list contents of
|
|
pkg_fmri_patterns: Vec<String>,
|
|
},
|
|
|
|
/// Set publisher properties
|
|
///
|
|
/// The set-publisher command sets properties for publishers.
|
|
SetPublisher {
|
|
/// Publisher name
|
|
#[clap(short = 'p')]
|
|
publisher: String,
|
|
|
|
/// Publisher origin URL
|
|
#[clap(short = 'O')]
|
|
origin: Option<String>,
|
|
|
|
/// Publisher mirror URL
|
|
#[clap(short = 'M')]
|
|
mirror: Option<Vec<String>>,
|
|
},
|
|
|
|
/// Remove a publisher
|
|
///
|
|
/// The unset-publisher command removes a publisher.
|
|
UnsetPublisher {
|
|
/// Publisher name
|
|
publisher: String,
|
|
},
|
|
|
|
/// Display publisher information
|
|
///
|
|
/// The publisher command displays information about publishers.
|
|
Publisher {
|
|
/// Verbose output
|
|
#[clap(short)]
|
|
verbose: bool,
|
|
|
|
/// Output format (default: table)
|
|
#[clap(short = 'o')]
|
|
output_format: Option<String>,
|
|
|
|
/// Publishers to show information about (default: all)
|
|
publishers: Vec<String>,
|
|
},
|
|
|
|
/// Create an image
|
|
///
|
|
/// The image-create command creates a new image.
|
|
/// If publisher and origin are provided, the publisher will be added to the image.
|
|
ImageCreate {
|
|
/// Full path to the image to create
|
|
#[clap(short = 'F')]
|
|
full_path: PathBuf,
|
|
|
|
/// Publisher to use (optional)
|
|
#[clap(short = 'p')]
|
|
publisher: Option<String>,
|
|
|
|
/// Publisher origin URL (required if publisher is specified)
|
|
#[clap(short = 'g', requires = "publisher")]
|
|
origin: Option<String>,
|
|
|
|
/// Type of image to create (full or partial, default: full)
|
|
#[clap(short = 't', long = "type", default_value = "full")]
|
|
image_type: String,
|
|
},
|
|
|
|
/// Debug database commands (hidden)
|
|
///
|
|
/// These commands are for debugging purposes only and are not part of the public API.
|
|
/// They are used to inspect the contents of the redb databases for debugging purposes.
|
|
///
|
|
/// Usage examples:
|
|
/// - Show database statistics: pkg6 debug-db --stats
|
|
/// - Dump all tables: pkg6 debug-db --dump-all
|
|
/// - Dump a specific table: pkg6 debug-db --dump-table catalog
|
|
///
|
|
/// Available tables:
|
|
/// - catalog: Contains non-obsolete packages (in catalog.redb)
|
|
/// - obsoleted: Contains obsolete packages (in catalog.redb)
|
|
/// - installed: Contains installed packages (in installed.redb)
|
|
#[clap(hide = true)]
|
|
DebugDb {
|
|
/// Show database statistics
|
|
#[clap(long)]
|
|
stats: bool,
|
|
|
|
/// Dump all tables
|
|
#[clap(long)]
|
|
dump_all: bool,
|
|
|
|
/// Dump a specific table (catalog, obsoleted, installed)
|
|
#[clap(long)]
|
|
dump_table: Option<String>,
|
|
},
|
|
}
|
|
|
|
/// Determines the image path to use based on the provided argument and default rules
|
|
///
|
|
/// If the image_path argument is provided, that path is used.
|
|
/// Otherwise, if $HOME/.pkg exists, that path is used.
|
|
/// Otherwise, the root directory (/) is used.
|
|
fn determine_image_path(image_path: Option<PathBuf>) -> PathBuf {
|
|
if let Some(path) = image_path {
|
|
// Use the explicitly provided path
|
|
debug!("Using explicitly provided image path: {}", path.display());
|
|
path
|
|
} else {
|
|
// Check if $HOME/.pkg exists
|
|
if let Ok(home_dir) = std::env::var("HOME") {
|
|
let home_pkg = PathBuf::from(home_dir).join(".pkg");
|
|
if home_pkg.exists() {
|
|
debug!("Using user home image path: {}", home_pkg.display());
|
|
return PathBuf::from(home_pkg);
|
|
}
|
|
}
|
|
|
|
// Default to root directory
|
|
debug!("Using root directory as image path");
|
|
PathBuf::from("/")
|
|
}
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
// Initialize the tracing subscriber with the default log level as debug and no decorations
|
|
// Parse the environment filter first, handling any errors with our custom error type
|
|
let env_filter = EnvFilter::builder()
|
|
.with_default_directive(LevelFilter::WARN.into())
|
|
.from_env()
|
|
.map_err(|e| {
|
|
Pkg6Error::LoggingEnvError(format!("Failed to parse environment filter: {}", e))
|
|
})?;
|
|
|
|
fmt::Subscriber::builder()
|
|
.with_max_level(tracing::Level::DEBUG)
|
|
.with_env_filter(env_filter)
|
|
.without_time()
|
|
.with_target(false)
|
|
.with_ansi(false)
|
|
.with_writer(std::io::stderr)
|
|
.init();
|
|
|
|
let cli = App::parse();
|
|
|
|
match &cli.command {
|
|
Commands::Refresh {
|
|
full,
|
|
quiet,
|
|
publishers,
|
|
} => {
|
|
info!("Refreshing package catalog");
|
|
debug!("Full refresh: {}", full);
|
|
debug!("Quiet mode: {}", quiet);
|
|
debug!("Publishers: {:?}", publishers);
|
|
|
|
// Determine the image path using the -R argument or default rules
|
|
let image_path = determine_image_path(cli.image_path.clone());
|
|
if !quiet {
|
|
println!("Using image at: {}", image_path.display());
|
|
}
|
|
|
|
// Try to load the image from the determined path
|
|
let image = match libips::image::Image::load(&image_path) {
|
|
Ok(img) => img,
|
|
Err(e) => {
|
|
error!("Failed to load image from {}: {}", image_path.display(), e);
|
|
if !quiet {
|
|
eprintln!("Failed to load image from {}: {}", image_path.display(), e);
|
|
eprintln!(
|
|
"Make sure the path points to a valid image or use pkg6 image-create first"
|
|
);
|
|
}
|
|
return Err(e.into());
|
|
}
|
|
};
|
|
|
|
// Refresh the catalogs
|
|
if let Err(e) = image.refresh_catalogs(publishers, *full) {
|
|
error!("Failed to refresh catalog: {}", e);
|
|
if !quiet {
|
|
eprintln!("Failed to refresh catalog: {}", e);
|
|
}
|
|
return Err(e.into());
|
|
}
|
|
|
|
info!("Refresh completed successfully");
|
|
if !quiet {
|
|
println!("Refresh completed successfully");
|
|
}
|
|
Ok(())
|
|
}
|
|
Commands::Install {
|
|
dry_run,
|
|
verbose,
|
|
quiet,
|
|
concurrency,
|
|
repo,
|
|
accept,
|
|
licenses,
|
|
no_index,
|
|
no_refresh,
|
|
pkg_fmri_patterns,
|
|
} => {
|
|
info!("Installing packages: {:?}", pkg_fmri_patterns);
|
|
debug!("Dry run: {}", dry_run);
|
|
debug!("Verbose: {}", verbose);
|
|
debug!("Quiet: {}", quiet);
|
|
debug!("Concurrency: {:?}", concurrency);
|
|
debug!("Additional repos: {:?}", repo);
|
|
debug!("Accept licenses: {}", accept);
|
|
debug!("Show licenses: {}", licenses);
|
|
debug!("No index update: {}", no_index);
|
|
debug!("No refresh: {}", no_refresh);
|
|
|
|
// Determine the image path using the -R argument or default rules
|
|
let image_path = determine_image_path(cli.image_path.clone());
|
|
if !quiet {
|
|
println!("Using image at: {}", image_path.display());
|
|
}
|
|
|
|
// Load the image
|
|
let image = match libips::image::Image::load(&image_path) {
|
|
Ok(img) => img,
|
|
Err(e) => {
|
|
error!("Failed to load image from {}: {}", image_path.display(), e);
|
|
return Err(e.into());
|
|
}
|
|
};
|
|
|
|
// Note: Install now relies on existing redb databases and does not perform
|
|
// a full import or refresh automatically. Run `pkg6 refresh` explicitly
|
|
// to update catalogs before installing if needed.
|
|
if !*quiet {
|
|
eprintln!(
|
|
"Install uses existing catalogs in redb; run 'pkg6 refresh' to update catalogs if needed."
|
|
);
|
|
}
|
|
|
|
// Build solver constraints from the provided pkg specs
|
|
if pkg_fmri_patterns.is_empty() {
|
|
if !quiet {
|
|
eprintln!("No packages specified to install");
|
|
}
|
|
return Err(Pkg6Error::Other("no packages specified".to_string()));
|
|
}
|
|
let mut constraints: Vec<libips::solver::Constraint> = Vec::new();
|
|
for spec in pkg_fmri_patterns {
|
|
let mut preferred_publishers: Vec<String> = Vec::new();
|
|
let mut name_part = spec.as_str();
|
|
// parse optional publisher prefix pkg://<pub>/
|
|
if let Some(rest) = name_part.strip_prefix("pkg://") {
|
|
if let Some((pubr, rest2)) = rest.split_once('/') {
|
|
preferred_publishers.push(pubr.to_string());
|
|
name_part = rest2;
|
|
}
|
|
}
|
|
// split version requirement after '@'
|
|
let (stem, version_req) = if let Some((s, v)) = name_part.split_once('@') {
|
|
(s.to_string(), Some(v.to_string()))
|
|
} else {
|
|
(name_part.to_string(), None)
|
|
};
|
|
constraints.push(libips::solver::Constraint {
|
|
stem,
|
|
version_req,
|
|
preferred_publishers,
|
|
branch: None,
|
|
});
|
|
}
|
|
|
|
// Resolve install plan
|
|
if !quiet {
|
|
println!("Resolving dependencies...");
|
|
}
|
|
let plan = match libips::solver::resolve_install(&image, &constraints) {
|
|
Ok(p) => p,
|
|
Err(e) => {
|
|
let mut printed_advice = false;
|
|
if !*quiet {
|
|
// Attempt to provide user-focused advice on how to resolve dependency issues
|
|
let opts = libips::solver::advice::AdviceOptions {
|
|
max_depth: 3,
|
|
dependency_cap: 400,
|
|
};
|
|
match libips::solver::advice::advise_from_error(&image, &e, opts) {
|
|
Ok(report) => {
|
|
if !report.issues.is_empty() {
|
|
printed_advice = true;
|
|
eprintln!(
|
|
"\nAdvice: detected {} issue(s) preventing installation:",
|
|
report.issues.len()
|
|
);
|
|
for (i, iss) in report.issues.iter().enumerate() {
|
|
let constraint_str = {
|
|
let mut s = String::new();
|
|
if let Some(r) = &iss.constraint_release {
|
|
s.push_str(&format!("release={} ", r));
|
|
}
|
|
if let Some(b) = &iss.constraint_branch {
|
|
s.push_str(&format!("branch={}", b));
|
|
}
|
|
s.trim().to_string()
|
|
};
|
|
eprintln!(
|
|
" {}. Missing viable candidates for '{}'\n - Path: {}\n - Constraint: {}\n - Details: {}",
|
|
i + 1,
|
|
iss.stem,
|
|
if iss.path.is_empty() {
|
|
iss.stem.clone()
|
|
} else {
|
|
iss.path.join(" -> ")
|
|
},
|
|
if constraint_str.is_empty() {
|
|
"<none>".to_string()
|
|
} else {
|
|
constraint_str
|
|
},
|
|
iss.details
|
|
);
|
|
}
|
|
eprintln!("\nWhat you can try as a user:");
|
|
eprintln!(
|
|
" • Ensure your catalogs are up to date: 'pkg6 refresh'."
|
|
);
|
|
eprintln!(
|
|
" • Verify that the required publishers are configured: 'pkg6 publisher'."
|
|
);
|
|
eprintln!(
|
|
" • Some versions may be constrained by image incorporations; updating the image or selecting a compatible package set may help."
|
|
);
|
|
eprintln!(
|
|
" • If the problem persists, report this to the repository maintainers with the above details."
|
|
);
|
|
}
|
|
}
|
|
Err(advice_err) => {
|
|
eprintln!("(Note) Unable to compute advice: {}", advice_err);
|
|
}
|
|
}
|
|
}
|
|
if printed_advice {
|
|
// We've printed actionable advice; exit with a non-zero code without printing further errors.
|
|
std::process::exit(1);
|
|
} else {
|
|
// No advice printed; fall back to standard error reporting
|
|
error!("Failed to resolve install plan: {}", e);
|
|
return Err(e.into());
|
|
}
|
|
}
|
|
};
|
|
|
|
if !quiet {
|
|
println!("Resolved {} package(s) to install", plan.add.len());
|
|
}
|
|
|
|
// Build and apply action plan
|
|
if !quiet {
|
|
println!("Building action plan...");
|
|
}
|
|
let ap = libips::image::action_plan::ActionPlan::from_install_plan(&plan);
|
|
let quiet_mode = *quiet;
|
|
let progress_cb: libips::actions::executors::ProgressCallback = Arc::new(move |evt| {
|
|
if quiet_mode {
|
|
return;
|
|
}
|
|
match evt {
|
|
libips::actions::executors::ProgressEvent::StartingPhase { phase, total } => {
|
|
println!("Applying: {} (total {})...", phase, total);
|
|
}
|
|
libips::actions::executors::ProgressEvent::Progress {
|
|
phase,
|
|
current,
|
|
total,
|
|
} => {
|
|
println!("Applying: {} {}/{}", phase, current, total);
|
|
}
|
|
libips::actions::executors::ProgressEvent::FinishedPhase { phase, total } => {
|
|
println!("Done: {} (total {})", phase, total);
|
|
}
|
|
}
|
|
});
|
|
let apply_opts = libips::actions::executors::ApplyOptions {
|
|
dry_run: *dry_run,
|
|
progress: Some(progress_cb),
|
|
progress_interval: 10,
|
|
};
|
|
if !quiet {
|
|
println!("Applying action plan (dry-run: {})", dry_run);
|
|
}
|
|
ap.apply(image.path(), &apply_opts)?;
|
|
|
|
// Update installed DB after success (skip on dry-run)
|
|
if !*dry_run {
|
|
if !quiet {
|
|
println!("Recording installation in image database...");
|
|
}
|
|
let total_pkgs = plan.add.len();
|
|
let mut idx = 0usize;
|
|
for rp in &plan.add {
|
|
image.install_package(&rp.fmri, &rp.manifest)?;
|
|
idx += 1;
|
|
if !quiet && (idx % 5 == 0 || idx == total_pkgs) {
|
|
println!("Recorded {}/{} packages", idx, total_pkgs);
|
|
}
|
|
// Save full manifest into manifests directory for reproducibility
|
|
match image.save_manifest(&rp.fmri, &rp.manifest) {
|
|
Ok(path) => {
|
|
if *verbose && !*quiet {
|
|
eprintln!("Saved manifest for {} to {}", rp.fmri, path.display());
|
|
}
|
|
}
|
|
Err(e) => {
|
|
// Non-fatal: log error but continue install
|
|
error!("Failed to save manifest for {}: {}", rp.fmri, e);
|
|
}
|
|
}
|
|
}
|
|
if !quiet {
|
|
println!("Installed {} package(s)", plan.add.len());
|
|
}
|
|
|
|
// Dump installed database to make changes visible
|
|
let installed =
|
|
libips::image::installed::InstalledPackages::new(image.installed_db_path());
|
|
if let Err(e) = installed.dump_installed_table() {
|
|
error!("Failed to dump installed database: {}", e);
|
|
}
|
|
} else if !quiet {
|
|
println!(
|
|
"Dry-run completed: {} package(s) would be installed",
|
|
plan.add.len()
|
|
);
|
|
}
|
|
|
|
info!("Installation completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::ExactInstall {
|
|
dry_run,
|
|
verbose,
|
|
quiet,
|
|
concurrency,
|
|
repo,
|
|
accept,
|
|
licenses,
|
|
no_index,
|
|
no_refresh,
|
|
pkg_fmri_patterns,
|
|
} => {
|
|
info!("Exact-installing packages: {:?}", pkg_fmri_patterns);
|
|
debug!("Dry run: {}", dry_run);
|
|
debug!("Verbose: {}", verbose);
|
|
debug!("Quiet: {}", quiet);
|
|
debug!("Concurrency: {:?}", concurrency);
|
|
debug!("Additional repos: {:?}", repo);
|
|
debug!("Accept licenses: {}", accept);
|
|
debug!("Show licenses: {}", licenses);
|
|
debug!("No index update: {}", no_index);
|
|
debug!("No refresh: {}", no_refresh);
|
|
|
|
// Stub implementation
|
|
info!("Exact-installation completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::Uninstall {
|
|
dry_run,
|
|
verbose,
|
|
quiet,
|
|
pkg_fmri_patterns,
|
|
} => {
|
|
info!("Uninstalling packages: {:?}", pkg_fmri_patterns);
|
|
debug!("Dry run: {}", dry_run);
|
|
debug!("Verbose: {}", verbose);
|
|
debug!("Quiet: {}", quiet);
|
|
|
|
// Stub implementation
|
|
info!("Uninstallation completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::Update {
|
|
dry_run,
|
|
verbose,
|
|
quiet,
|
|
concurrency,
|
|
repo,
|
|
accept,
|
|
licenses,
|
|
no_index,
|
|
no_refresh,
|
|
pkg_fmri_patterns,
|
|
} => {
|
|
info!("Updating packages: {:?}", pkg_fmri_patterns);
|
|
debug!("Dry run: {}", dry_run);
|
|
debug!("Verbose: {}", verbose);
|
|
debug!("Quiet: {}", quiet);
|
|
debug!("Concurrency: {:?}", concurrency);
|
|
debug!("Additional repos: {:?}", repo);
|
|
debug!("Accept licenses: {}", accept);
|
|
debug!("Show licenses: {}", licenses);
|
|
debug!("No index update: {}", no_index);
|
|
debug!("No refresh: {}", no_refresh);
|
|
|
|
// Stub implementation
|
|
info!("Update completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::List {
|
|
verbose,
|
|
quiet,
|
|
all,
|
|
output_format,
|
|
pkg_fmri_patterns,
|
|
} => {
|
|
info!("Listing packages: {:?}", pkg_fmri_patterns);
|
|
debug!("Verbose: {}", verbose);
|
|
debug!("Quiet: {}", quiet);
|
|
debug!("All packages: {}", all);
|
|
debug!("Output format: {:?}", output_format);
|
|
|
|
// Determine the image path using the -R argument or default rules
|
|
let image_path = determine_image_path(cli.image_path.clone());
|
|
info!("Using image at: {}", image_path.display());
|
|
|
|
// Try to load the image from the determined path
|
|
let image = match libips::image::Image::load(&image_path) {
|
|
Ok(img) => img,
|
|
Err(e) => {
|
|
error!("Failed to load image from {}: {}", image_path.display(), e);
|
|
error!(
|
|
"Make sure the path points to a valid image or use pkg6 image-create first"
|
|
);
|
|
return Err(e.into());
|
|
}
|
|
};
|
|
|
|
// Convert pkg_fmri_patterns to a single pattern if provided
|
|
let pattern = if pkg_fmri_patterns.is_empty() {
|
|
None
|
|
} else {
|
|
// For simplicity, we'll just use the first pattern
|
|
// In a more complete implementation, we would handle multiple patterns
|
|
Some(pkg_fmri_patterns[0].as_str())
|
|
};
|
|
|
|
if *all {
|
|
// List all available packages
|
|
info!("Listing all available packages");
|
|
|
|
// Build the catalog before querying it
|
|
info!("Building catalog...");
|
|
if let Err(e) = image.build_catalog() {
|
|
error!("Failed to build catalog: {}", e);
|
|
return Err(e.into());
|
|
}
|
|
|
|
match image.query_catalog(pattern) {
|
|
Ok(packages) => {
|
|
println!(
|
|
"PUBLISHER NAME VERSION STATE"
|
|
);
|
|
println!(
|
|
"------------------------------------------------------------------------------------------------------------------------------------------------------"
|
|
);
|
|
for pkg in packages {
|
|
let state = if image.is_package_installed(&pkg.fmri).unwrap_or(false) {
|
|
"installed"
|
|
} else {
|
|
"known"
|
|
};
|
|
println!(
|
|
"{:<40} {:<40} {:<30} {}",
|
|
pkg.fmri.publisher.as_deref().unwrap_or("unknown"),
|
|
pkg.fmri.name,
|
|
pkg.fmri.version(),
|
|
state
|
|
);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
error!("Failed to query catalog: {}", e);
|
|
return Err(e.into());
|
|
}
|
|
}
|
|
} else {
|
|
// List only installed packages
|
|
info!("Listing installed packages");
|
|
match image.query_installed_packages(pattern) {
|
|
Ok(packages) => {
|
|
println!(
|
|
"PUBLISHER NAME VERSION STATE"
|
|
);
|
|
println!(
|
|
"------------------------------------------------------------------------------------------------------------------------------------------------------"
|
|
);
|
|
for pkg in packages {
|
|
println!(
|
|
"{:<40} {:<40} {:<30} {}",
|
|
pkg.fmri.publisher.as_deref().unwrap_or("unknown"),
|
|
pkg.fmri.name,
|
|
pkg.fmri.version(),
|
|
"installed"
|
|
);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
error!("Failed to query installed packages: {}", e);
|
|
return Err(e.into());
|
|
}
|
|
}
|
|
}
|
|
|
|
info!("List completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::Info {
|
|
verbose,
|
|
quiet,
|
|
output_format,
|
|
pkg_fmri_patterns,
|
|
} => {
|
|
info!("Showing info for packages: {:?}", pkg_fmri_patterns);
|
|
debug!("Verbose: {}", verbose);
|
|
debug!("Quiet: {}", quiet);
|
|
debug!("Output format: {:?}", output_format);
|
|
|
|
// Stub implementation
|
|
info!("Info completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::Search {
|
|
verbose,
|
|
quiet,
|
|
output_format,
|
|
query,
|
|
} => {
|
|
info!("Searching for packages matching: {}", query);
|
|
debug!("Verbose: {}", verbose);
|
|
debug!("Quiet: {}", quiet);
|
|
debug!("Output format: {:?}", output_format);
|
|
|
|
// Stub implementation
|
|
info!("Search completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::Verify {
|
|
verbose,
|
|
quiet,
|
|
pkg_fmri_patterns,
|
|
} => {
|
|
info!("Verifying packages: {:?}", pkg_fmri_patterns);
|
|
debug!("Verbose: {}", verbose);
|
|
debug!("Quiet: {}", quiet);
|
|
|
|
// Stub implementation
|
|
info!("Verification completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::Fix {
|
|
dry_run,
|
|
verbose,
|
|
quiet,
|
|
pkg_fmri_patterns,
|
|
} => {
|
|
info!("Fixing packages: {:?}", pkg_fmri_patterns);
|
|
debug!("Dry run: {}", dry_run);
|
|
debug!("Verbose: {}", verbose);
|
|
debug!("Quiet: {}", quiet);
|
|
|
|
// Stub implementation
|
|
info!("Fix completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::History {
|
|
count,
|
|
full,
|
|
output_format,
|
|
} => {
|
|
info!("Showing history");
|
|
debug!("Count: {:?}", count);
|
|
debug!("Full: {}", full);
|
|
debug!("Output format: {:?}", output_format);
|
|
|
|
// Stub implementation
|
|
info!("History completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::Contents {
|
|
verbose,
|
|
quiet,
|
|
output_format,
|
|
pkg_fmri_patterns,
|
|
} => {
|
|
info!("Showing contents for packages: {:?}", pkg_fmri_patterns);
|
|
debug!("Verbose: {}", verbose);
|
|
debug!("Quiet: {}", quiet);
|
|
debug!("Output format: {:?}", output_format);
|
|
|
|
// Stub implementation
|
|
info!("Contents completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::SetPublisher {
|
|
publisher,
|
|
origin,
|
|
mirror,
|
|
} => {
|
|
info!("Setting publisher: {}", publisher);
|
|
debug!("Origin: {:?}", origin);
|
|
debug!("Mirror: {:?}", mirror);
|
|
|
|
// Determine the image path using the -R argument or default rules
|
|
let image_path = determine_image_path(cli.image_path.clone());
|
|
info!("Using image at: {}", image_path.display());
|
|
|
|
// Try to load the image from the determined path
|
|
let mut image = match libips::image::Image::load(&image_path) {
|
|
Ok(img) => img,
|
|
Err(e) => {
|
|
error!("Failed to load image from {}: {}", image_path.display(), e);
|
|
error!(
|
|
"Make sure the path points to a valid image or use pkg6 image-create first"
|
|
);
|
|
return Err(e.into());
|
|
}
|
|
};
|
|
|
|
// Convert mirror to Vec<String> if provided
|
|
let mirrors = match mirror {
|
|
Some(m) => m.clone(),
|
|
None => vec![],
|
|
};
|
|
|
|
// If origin is provided, update the publisher
|
|
if let Some(origin_url) = origin {
|
|
// Add or update the publisher
|
|
image.add_publisher(&publisher, &origin_url, mirrors, true)?;
|
|
info!(
|
|
"Publisher {} configured with origin: {}",
|
|
publisher, origin_url
|
|
);
|
|
|
|
// Download the catalog
|
|
image.download_publisher_catalog(&publisher)?;
|
|
info!("Catalog downloaded from publisher: {}", publisher);
|
|
} else {
|
|
// If no origin is provided, just set the publisher as default if it exists
|
|
let pub_result = image.get_publisher(&publisher);
|
|
if let Ok(pub_info) = pub_result {
|
|
// Store the necessary information
|
|
let origin = pub_info.origin.clone();
|
|
let mirrors = pub_info.mirrors.clone();
|
|
|
|
// Add the publisher again with is_default=true to make it the default
|
|
image.add_publisher(&publisher, &origin, mirrors, true)?;
|
|
info!("Publisher {} set as default", publisher);
|
|
} else {
|
|
error!("Publisher {} not found and no origin provided", publisher);
|
|
return Err(
|
|
libips::image::ImageError::PublisherNotFound(publisher.clone()).into(),
|
|
);
|
|
}
|
|
}
|
|
|
|
info!("Set-publisher completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::UnsetPublisher { publisher } => {
|
|
info!("Unsetting publisher: {}", publisher);
|
|
|
|
// Determine the image path using the -R argument or default rules
|
|
let image_path = determine_image_path(cli.image_path.clone());
|
|
info!("Using image at: {}", image_path.display());
|
|
|
|
// Try to load the image from the determined path
|
|
let mut image = match libips::image::Image::load(&image_path) {
|
|
Ok(img) => img,
|
|
Err(e) => {
|
|
error!("Failed to load image from {}: {}", image_path.display(), e);
|
|
error!(
|
|
"Make sure the path points to a valid image or use pkg6 image-create first"
|
|
);
|
|
return Err(e.into());
|
|
}
|
|
};
|
|
|
|
// Remove the publisher
|
|
image.remove_publisher(&publisher)?;
|
|
|
|
// Refresh the catalog to reflect the current state of all available packages
|
|
if let Err(e) = image.download_catalogs() {
|
|
error!("Failed to refresh catalog after removing publisher: {}", e);
|
|
// Continue execution even if catalog refresh fails
|
|
} else {
|
|
info!("Catalog refreshed successfully");
|
|
}
|
|
|
|
info!("Publisher {} removed successfully", publisher);
|
|
info!("Unset-publisher completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::Publisher {
|
|
verbose,
|
|
output_format,
|
|
publishers,
|
|
} => {
|
|
info!("Showing publisher information");
|
|
|
|
// Determine the image path using the -R argument or default rules
|
|
let image_path = determine_image_path(cli.image_path.clone());
|
|
info!("Using image at: {}", image_path.display());
|
|
|
|
// Try to load the image from the determined path
|
|
let image = match libips::image::Image::load(&image_path) {
|
|
Ok(img) => img,
|
|
Err(e) => {
|
|
error!("Failed to load image from {}: {}", image_path.display(), e);
|
|
error!(
|
|
"Make sure the path points to a valid image or use pkg6 image-create first"
|
|
);
|
|
return Err(e.into());
|
|
}
|
|
};
|
|
|
|
// Get all publishers
|
|
let all_publishers = image.publishers();
|
|
|
|
// Filter publishers if specified
|
|
let filtered_publishers: Vec<_> = if publishers.is_empty() {
|
|
all_publishers.to_vec()
|
|
} else {
|
|
all_publishers
|
|
.iter()
|
|
.filter(|p| publishers.contains(&p.name))
|
|
.cloned()
|
|
.collect()
|
|
};
|
|
|
|
// Handle case where no publishers are found
|
|
if filtered_publishers.is_empty() {
|
|
if publishers.is_empty() {
|
|
println!("No publishers configured");
|
|
} else {
|
|
println!("No matching publishers found");
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
// Determine the output format, defaulting to "table" if not specified
|
|
let output_format_str = output_format.as_deref().unwrap_or("table");
|
|
|
|
// Create a vector of PublisherOutput structs for serialization and display
|
|
let publisher_outputs: Vec<PublisherOutput> = filtered_publishers
|
|
.iter()
|
|
.map(|p| {
|
|
let catalog_dir = if *verbose {
|
|
let dir = match image.image_type() {
|
|
libips::image::ImageType::Full => image_path.join("var/pkg/catalog"),
|
|
libips::image::ImageType::Partial => image_path.join(".pkg/catalog"),
|
|
};
|
|
Some(dir.join(&p.name).display().to_string())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
PublisherOutput {
|
|
name: p.name.clone(),
|
|
origin: p.origin.clone(),
|
|
mirrors: p.mirrors.clone(),
|
|
is_default: p.is_default,
|
|
catalog_dir,
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
// Display publisher information based on the output format
|
|
match output_format_str {
|
|
"table" => {
|
|
// Display in table format (human-readable)
|
|
// This is the default format and displays the information in a user-friendly way
|
|
for publisher in &publisher_outputs {
|
|
println!("Publisher: {}", publisher.name);
|
|
println!(" Origin: {}", publisher.origin);
|
|
if !publisher.mirrors.is_empty() {
|
|
println!(" Mirrors:");
|
|
for mirror in &publisher.mirrors {
|
|
println!(" {}", mirror);
|
|
}
|
|
}
|
|
println!(
|
|
" Default: {}",
|
|
if publisher.is_default { "Yes" } else { "No" }
|
|
);
|
|
if let Some(catalog_dir) = &publisher.catalog_dir {
|
|
println!(" Catalog directory: {}", catalog_dir);
|
|
}
|
|
println!();
|
|
// Explicitly flush stdout after each publisher to ensure output is displayed
|
|
let _ = std::io::stdout().flush();
|
|
}
|
|
}
|
|
"json" => {
|
|
// Display in JSON format
|
|
// This format is useful for programmatic access to the publisher information
|
|
let output = PublishersOutput {
|
|
publishers: publisher_outputs,
|
|
};
|
|
let json = serde_json::to_string_pretty(&output)
|
|
.unwrap_or_else(|e| format!("{{\"error\": \"{}\"}}", e));
|
|
println!("{}", json);
|
|
let _ = std::io::stdout().flush();
|
|
}
|
|
"tsv" => {
|
|
// Display in TSV format (tab-separated values)
|
|
// This format is useful for importing into spreadsheets or other data processing tools
|
|
// Print header
|
|
println!("NAME\tORIGIN\tMIRRORS\tDEFAULT\tCATALOG_DIR");
|
|
|
|
// Print each publisher
|
|
for publisher in &publisher_outputs {
|
|
let mirrors = publisher.mirrors.join(",");
|
|
let default = if publisher.is_default { "Yes" } else { "No" };
|
|
let catalog_dir = publisher.catalog_dir.as_deref().unwrap_or("");
|
|
|
|
println!(
|
|
"{}\t{}\t{}\t{}\t{}",
|
|
publisher.name, publisher.origin, mirrors, default, catalog_dir
|
|
);
|
|
let _ = std::io::stdout().flush();
|
|
}
|
|
}
|
|
_ => {
|
|
// Unsupported format
|
|
return Err(Pkg6Error::UnsupportedOutputFormat(
|
|
output_format_str.to_string(),
|
|
));
|
|
}
|
|
}
|
|
|
|
info!("Publisher completed successfully");
|
|
Ok(())
|
|
}
|
|
Commands::ImageCreate {
|
|
full_path,
|
|
publisher,
|
|
origin,
|
|
image_type,
|
|
} => {
|
|
info!("Creating image at: {}", full_path.display());
|
|
debug!("Publisher: {:?}", publisher);
|
|
debug!("Origin: {:?}", origin);
|
|
debug!("Image type: {}", image_type);
|
|
|
|
// Convert the image type string to the ImageType enum
|
|
let image_type = match image_type.to_lowercase().as_str() {
|
|
"full" => libips::image::ImageType::Full,
|
|
"partial" => libips::image::ImageType::Partial,
|
|
_ => {
|
|
error!("Invalid image type: {}. Using default (full)", image_type);
|
|
libips::image::ImageType::Full
|
|
}
|
|
};
|
|
|
|
// Create the image (only creates the basic structure)
|
|
let mut image = libips::image::Image::create_image(&full_path, image_type)?;
|
|
info!("Image created successfully at: {}", full_path.display());
|
|
|
|
// If publisher and origin are provided, only add the publisher; do not download/open catalogs here.
|
|
if let (Some(publisher_name), Some(origin_url)) = (publisher.as_ref(), origin.as_ref())
|
|
{
|
|
info!(
|
|
"Adding publisher {} with origin {}",
|
|
publisher_name, origin_url
|
|
);
|
|
|
|
// Add the publisher
|
|
image.add_publisher(publisher_name, origin_url, vec![], true)?;
|
|
|
|
info!(
|
|
"Publisher {} configured with origin: {}",
|
|
publisher_name, origin_url
|
|
);
|
|
info!(
|
|
"Catalogs are not downloaded during image creation. Use 'pkg6 -R {} refresh {}' to download and open catalogs.",
|
|
full_path.display(),
|
|
publisher_name
|
|
);
|
|
} else {
|
|
info!("No publisher configured. Use 'pkg6 set-publisher' to add a publisher.");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
Commands::DebugDb {
|
|
stats,
|
|
dump_all,
|
|
dump_table,
|
|
} => {
|
|
info!("Debug database command");
|
|
debug!("Stats: {}", stats);
|
|
debug!("Dump all: {}", dump_all);
|
|
debug!("Dump table: {:?}", dump_table);
|
|
|
|
// Determine the image path using the -R argument or default rules
|
|
let image_path = determine_image_path(cli.image_path.clone());
|
|
info!("Using image at: {}", image_path.display());
|
|
|
|
// Try to load the image from the determined path
|
|
let image = match libips::image::Image::load(&image_path) {
|
|
Ok(img) => img,
|
|
Err(e) => {
|
|
error!("Failed to load image from {}: {}", image_path.display(), e);
|
|
error!(
|
|
"Make sure the path points to a valid image or use pkg6 image-create first"
|
|
);
|
|
return Err(e.into());
|
|
}
|
|
};
|
|
|
|
// Create a catalog object for the catalog.redb database
|
|
let catalog = libips::image::catalog::ImageCatalog::new(
|
|
image.catalog_dir(),
|
|
image.active_db_path(),
|
|
image.obsolete_db_path(),
|
|
);
|
|
|
|
// Create an installed packages object for the installed.redb database
|
|
let installed =
|
|
libips::image::installed::InstalledPackages::new(image.installed_db_path());
|
|
|
|
// Execute the requested debug command
|
|
if *stats {
|
|
info!("Showing database statistics");
|
|
println!("=== CATALOG DATABASE ===");
|
|
if let Err(e) = catalog.get_db_stats() {
|
|
error!("Failed to get catalog database statistics: {}", e);
|
|
return Err(Pkg6Error::Other(format!(
|
|
"Failed to get catalog database statistics: {}",
|
|
e
|
|
)));
|
|
}
|
|
|
|
println!("\n=== INSTALLED DATABASE ===");
|
|
if let Err(e) = installed.get_db_stats() {
|
|
error!("Failed to get installed database statistics: {}", e);
|
|
return Err(Pkg6Error::Other(format!(
|
|
"Failed to get installed database statistics: {}",
|
|
e
|
|
)));
|
|
}
|
|
}
|
|
|
|
if *dump_all {
|
|
info!("Dumping all tables");
|
|
println!("=== CATALOG DATABASE ===");
|
|
if let Err(e) = catalog.dump_all_tables() {
|
|
error!("Failed to dump catalog database tables: {}", e);
|
|
return Err(Pkg6Error::Other(format!(
|
|
"Failed to dump catalog database tables: {}",
|
|
e
|
|
)));
|
|
}
|
|
|
|
println!("\n=== INSTALLED DATABASE ===");
|
|
if let Err(e) = installed.dump_installed_table() {
|
|
error!("Failed to dump installed database table: {}", e);
|
|
return Err(Pkg6Error::Other(format!(
|
|
"Failed to dump installed database table: {}",
|
|
e
|
|
)));
|
|
}
|
|
}
|
|
|
|
if let Some(table_name) = dump_table {
|
|
info!("Dumping table: {}", table_name);
|
|
|
|
// Determine which database to use based on the table name
|
|
match table_name.as_str() {
|
|
"installed" => {
|
|
// Use the installed packages database
|
|
println!("=== INSTALLED DATABASE ===");
|
|
if let Err(e) = installed.dump_installed_table() {
|
|
error!("Failed to dump installed table: {}", e);
|
|
return Err(Pkg6Error::Other(format!(
|
|
"Failed to dump installed table: {}",
|
|
e
|
|
)));
|
|
}
|
|
}
|
|
"catalog" | "obsoleted" => {
|
|
// Use the catalog database
|
|
println!("=== CATALOG DATABASE ===");
|
|
if let Err(e) = catalog.dump_table(table_name) {
|
|
error!("Failed to dump table {}: {}", table_name, e);
|
|
return Err(Pkg6Error::Other(format!(
|
|
"Failed to dump table {}: {}",
|
|
table_name, e
|
|
)));
|
|
}
|
|
}
|
|
_ => {
|
|
error!("Unknown table: {}", table_name);
|
|
return Err(Pkg6Error::Other(format!(
|
|
"Unknown table: {}. Available tables: catalog, obsoleted, installed",
|
|
table_name
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
|
|
info!("Debug database command completed successfully");
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|