summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock25
-rw-r--r--Cargo.toml1
-rw-r--r--src/downloader.rs30
-rw-r--r--src/error.rs63
-rw-r--r--src/index.rs24
-rw-r--r--src/lib.rs2
-rw-r--r--src/main.rs13
-rw-r--r--src/manager.rs21
-rw-r--r--src/paths.rs16
-rw-r--r--src/symlink.rs6
-rw-r--r--src/tui.rs9
11 files changed, 152 insertions, 58 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9bb441d..17f89f8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -209,6 +209,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
+name = "derive_more"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -247,7 +267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
- "windows-sys 0.59.0",
+ "windows-sys 0.60.2",
]
[[package]]
@@ -1023,7 +1043,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.59.0",
+ "windows-sys 0.60.2",
]
[[package]]
@@ -1838,6 +1858,7 @@ name = "zap-rs"
version = "0.1.0"
dependencies = [
"clap",
+ "derive_more",
"futures-util",
"indicatif",
"reqwest",
diff --git a/Cargo.toml b/Cargo.toml
index 9aaad1f..1f384cf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2024"
[dependencies]
clap = { version = "4.5.41", features = ["derive"] }
+derive_more = { version = "2.0.1", features = ["from"] }
futures-util = "0.3.31"
indicatif = { version = "0.18.0", features = ["improved_unicode"] }
reqwest = { version = "0.12.22", features = ["blocking", "json", "stream"] }
diff --git a/src/downloader.rs b/src/downloader.rs
index 2196e25..0141424 100644
--- a/src/downloader.rs
+++ b/src/downloader.rs
@@ -2,7 +2,7 @@ use futures_util::StreamExt;
use std::path::PathBuf;
use tokio::{fs, io::AsyncWriteExt};
-use crate::{appimages_dir, make_progress_bar};
+use crate::{Error, Result, appimages_dir, make_progress_bar};
#[derive(Debug, Default)]
pub struct Downloader {}
@@ -11,32 +11,36 @@ impl Downloader {
pub fn new() -> Self {
Self {}
}
- pub fn prepare_path(&self, url: &str, executable: &str) -> PathBuf {
+ pub fn prepare_path(&self, url: &str, executable: &str) -> Result<PathBuf> {
// Try to extract filename from URL or use default
let filename = match url.split('/').next_back() {
Some(name) => name.to_string(),
None => format!("{executable}.AppImage"),
};
- appimages_dir().join(filename)
+ Ok(appimages_dir()?.join(filename))
}
- pub async fn download_with_progress(
- &self,
- url: &str,
- path: &PathBuf,
- ) -> Result<(), Box<dyn std::error::Error>> {
- fs::create_dir_all(&appimages_dir()).await?;
-
- let resp = reqwest::get(&url.to_string()).await?;
+ pub async fn download_with_progress(&self, url: &str, path: &PathBuf) -> Result<()> {
+ fs::create_dir_all(&appimages_dir()?).await?;
+
+ let resp = reqwest::get(&url.to_string())
+ .await
+ .map_err(|source| Error::Download {
+ url: url.to_string(),
+ source,
+ })?;
let total_size = resp.content_length().unwrap_or(0);
- let bar = make_progress_bar(total_size);
+ let bar = make_progress_bar(total_size)?;
let mut out = tokio::fs::File::create(&path).await?;
// Stream download with progress updates
let mut stream = resp.bytes_stream();
while let Some(chunk) = stream.next().await {
- let chunk = chunk?;
+ let chunk = chunk.map_err(|source| Error::Download {
+ url: url.to_string(),
+ source,
+ })?;
let len = chunk.len() as u64;
out.write_all(&chunk).await?;
bar.inc(len);
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..b473469
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,63 @@
+use derive_more::From;
+
+pub type Result<T> = core::result::Result<T, Error>;
+
+#[derive(Debug, From)]
+pub enum Error {
+ InvalidPath,
+ NotFound(String),
+ Download {
+ url: String,
+ source: reqwest::Error,
+ },
+
+ #[from]
+ Io(std::io::Error),
+
+ #[from]
+ Json(serde_json::Error),
+
+ #[from]
+ Http(reqwest::Error),
+
+ #[from]
+ EnvVar(std::env::VarError),
+
+ #[from]
+ IndicatifTemplate(indicatif::style::TemplateError),
+}
+
+impl core::fmt::Display for Error {
+ fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::result::Result<(), std::fmt::Error> {
+ match self {
+ Error::Io(e) => match e.kind() {
+ std::io::ErrorKind::NotFound => write!(fmt, "File or directory not found"),
+ _ => write!(fmt, "IO error: {e}"),
+ },
+ Error::NotFound(name) => write!(fmt, "Application '{name}' not found"),
+ Error::Json(e) => write!(fmt, "JSON error: {e}"),
+ Error::Http(e) => write!(fmt, "HTTP error: {e}"),
+ Error::EnvVar(e) => write!(fmt, "Environment variable error: {e}"),
+ Error::InvalidPath => write!(fmt, "Invalid path provided"),
+ Error::IndicatifTemplate(e) => write!(fmt, "Progress bar template error: {e}"),
+ Error::Download { url, source } => {
+ if source.is_timeout() {
+ write!(fmt, "Download timed out from: {url}")
+ } else if source.is_connect() {
+ write!(fmt, "Failed to connect to: {url}")
+ } else if let Some(status) = source.status() {
+ match status.as_u16() {
+ 404 => write!(fmt, "AppImage not found at: {url}"),
+ 403 => write!(fmt, "Access denied at: {url}"),
+ 500..=599 => write!(fmt, "Server error when downloading from: {url}"),
+ _ => write!(fmt, "HTTP {status} error downloading from: {url}"),
+ }
+ } else {
+ write!(fmt, "Failed to download from {url}: {source}")
+ }
+ }
+ }
+ }
+}
+
+impl std::error::Error for Error {}
diff --git a/src/index.rs b/src/index.rs
index 069068a..8f2521a 100644
--- a/src/index.rs
+++ b/src/index.rs
@@ -1,6 +1,6 @@
use tokio::fs;
-use crate::{AppImage, index_dir};
+use crate::{AppImage, Result, index_dir};
#[derive(Debug, Default)]
pub struct Index {}
@@ -9,32 +9,28 @@ impl Index {
pub fn new() -> Self {
Self {}
}
- pub async fn get(&self, appname: &str) -> Result<AppImage, Box<dyn std::error::Error>> {
- let index_file_path = index_dir().join(format!("{appname}.json"));
+ pub async fn get(&self, appname: &str) -> Result<AppImage> {
+ let index_file_path = index_dir()?.join(format!("{appname}.json"));
let index_file_content = fs::read_to_string(&index_file_path).await?;
let appimage: AppImage = serde_json::from_str(&index_file_content)?;
Ok(appimage)
}
- pub fn exists(&self, executable: &str) -> bool {
- index_dir().join(format!("{}.json", &executable)).exists()
+ pub fn exists(&self, executable: &str) -> Result<bool> {
+ Ok(index_dir()?.join(format!("{}.json", &executable)).exists())
}
- pub async fn add(
- &self,
- appimage: &AppImage,
- appname: &str,
- ) -> Result<(), Box<dyn std::error::Error>> {
- fs::create_dir_all(&index_dir()).await?;
+ pub async fn add(&self, appimage: &AppImage, appname: &str) -> Result<()> {
+ fs::create_dir_all(&index_dir()?).await?;
- let index_file = &index_dir().join(format!("{appname}.json"));
+ let index_file = &index_dir()?.join(format!("{appname}.json"));
let json = serde_json::to_string_pretty(appimage)?;
fs::write(index_file, json).await?;
Ok(())
}
- pub async fn remove(&self, appname: &str) -> Result<(), Box<dyn std::error::Error>> {
- let index_file_path = index_dir().join(format!("{appname}.json"));
+ pub async fn remove(&self, appname: &str) -> Result<()> {
+ let index_file_path = index_dir()?.join(format!("{appname}.json"));
fs::remove_file(index_file_path).await?;
Ok(())
diff --git a/src/lib.rs b/src/lib.rs
index c0db44a..3e20bde 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,6 +6,7 @@ mod manager;
mod paths;
mod symlink;
mod tui;
+mod error;
pub use crate::appimage::*;
pub use crate::args::*;
@@ -15,3 +16,4 @@ pub use crate::manager::*;
pub use crate::paths::*;
pub use crate::symlink::*;
pub use crate::tui::*;
+pub use crate::error::*;
diff --git a/src/main.rs b/src/main.rs
index 1d9505b..7840fae 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,10 +2,9 @@ use std::path::PathBuf;
use clap::Parser;
-use zap_rs::{AppImage, Cli, Command, PackageManager, Source, SourceMetadata};
+use zap_rs::{AppImage, Cli, Command, PackageManager, Result, Source, SourceMetadata};
-#[tokio::main]
-async fn main() -> Result<(), Box<dyn std::error::Error>> {
+async fn run() -> Result<()> {
let args = Cli::parse();
let pm = PackageManager::new();
@@ -32,3 +31,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
+
+#[tokio::main]
+async fn main() {
+ if let Err(e) = run().await {
+ eprintln!("Error: {e}");
+ std::process::exit(1);
+ }
+}
diff --git a/src/manager.rs b/src/manager.rs
index 66ed6f6..6f3cfac 100644
--- a/src/manager.rs
+++ b/src/manager.rs
@@ -1,6 +1,6 @@
use tokio::fs;
-use crate::{AppImage, Downloader, Index, SymlinkManager, index_dir};
+use crate::{AppImage, Downloader, Index, Result, SymlinkManager, index_dir};
#[derive(Debug, Default)]
pub struct PackageManager {
@@ -17,18 +17,15 @@ impl PackageManager {
symlink_manager: SymlinkManager::new(),
}
}
- pub async fn install(
- &self,
- appimage: &mut AppImage,
- appname: &str,
- ) -> Result<(), Box<dyn std::error::Error>> {
- if self.index.exists(&appimage.executable) {
- return Err(format!("{} is already installed.", &appimage.executable).into());
+ pub async fn install(&self, appimage: &mut AppImage, appname: &str) -> Result<()> {
+ if self.index.exists(&appimage.executable)? {
+ println!("{} is already installed.", appimage.executable);
+ return Ok(());
}
appimage.file_path = self
.downloader
- .prepare_path(&appimage.source.meta.url, &appimage.executable);
+ .prepare_path(&appimage.source.meta.url, &appimage.executable)?;
self.downloader
.download_with_progress(&appimage.source.meta.url, &appimage.file_path)
.await?;
@@ -37,7 +34,7 @@ impl PackageManager {
self.symlink_manager.create(appimage).await?;
Ok(())
}
- pub async fn remove(&self, appname: &str) -> Result<(), Box<dyn std::error::Error>> {
+ pub async fn remove(&self, appname: &str) -> Result<()> {
let appimage = self.index.get(appname).await?;
fs::remove_file(&appimage.file_path).await?;
@@ -46,8 +43,8 @@ impl PackageManager {
Ok(())
}
- pub async fn list(&self) -> Result<(), Box<dyn std::error::Error>> {
- let mut appimages = fs::read_dir(index_dir()).await?;
+ pub async fn list(&self) -> Result<()> {
+ let mut appimages = fs::read_dir(index_dir()?).await?;
while let Some(appimage) = appimages.next_entry().await? {
if let Some(stem) = appimage.path().file_stem().and_then(|s| s.to_str()) {
diff --git a/src/paths.rs b/src/paths.rs
index 172cae6..7bbc8f8 100644
--- a/src/paths.rs
+++ b/src/paths.rs
@@ -1,14 +1,16 @@
use std::path::PathBuf;
-pub fn zap_rs_home() -> PathBuf {
- let home = std::env::var("HOME").expect("HOME not set");
- PathBuf::from(home).join(".local/share/zap-rs")
+use crate::Result;
+
+pub fn zap_rs_home() -> Result<PathBuf> {
+ let home = std::env::var("HOME")?;
+ Ok(PathBuf::from(home).join(".local/share/zap-rs"))
}
-pub fn index_dir() -> PathBuf {
- zap_rs_home().join("index")
+pub fn index_dir() -> Result<PathBuf> {
+ Ok(zap_rs_home()?.join("index"))
}
-pub fn appimages_dir() -> PathBuf {
- zap_rs_home().join("appimages")
+pub fn appimages_dir() -> Result<PathBuf> {
+ Ok(zap_rs_home()?.join("appimages"))
}
diff --git a/src/symlink.rs b/src/symlink.rs
index 843115c..68a804f 100644
--- a/src/symlink.rs
+++ b/src/symlink.rs
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use tokio::fs;
-use crate::AppImage;
+use crate::{AppImage, Result};
#[derive(Debug, Default)]
pub struct SymlinkManager {}
@@ -10,7 +10,7 @@ impl SymlinkManager {
pub fn new() -> Self {
Self {}
}
- pub async fn remove(&self, executable: &str) -> Result<(), Box<dyn std::error::Error>> {
+ pub async fn remove(&self, executable: &str) -> Result<()> {
let home = std::env::var("HOME")?;
let symlink_path = PathBuf::from(home).join(".local/bin").join(executable);
@@ -18,7 +18,7 @@ impl SymlinkManager {
Ok(())
}
- pub async fn create(&self, appimage: &AppImage) -> Result<(), Box<dyn std::error::Error>> {
+ pub async fn create(&self, appimage: &AppImage) -> Result<()> {
let home = std::env::var("HOME")?;
let local_bin = PathBuf::from(home).join(".local/bin");
diff --git a/src/tui.rs b/src/tui.rs
index df3504d..2b0a5fd 100644
--- a/src/tui.rs
+++ b/src/tui.rs
@@ -1,14 +1,15 @@
use indicatif::{ProgressBar, ProgressStyle};
-pub fn make_progress_bar(size: u64) -> ProgressBar {
+use crate::Result;
+
+pub fn make_progress_bar(size: u64) -> Result<ProgressBar> {
let bar = ProgressBar::new(size);
bar.set_style(
ProgressStyle::with_template(
"{elapsed_precise:.white.dim} {wide_bar:.cyan} {bytes}/{total_bytes} ({bytes_per_sec}, {eta})",
- )
- .unwrap()
+ )?
.progress_chars("█▉▊▋▌▍▎▏ "),
);
- bar
+ Ok(bar)
}