From 61b6008db55f8aa6d9cc8d8998c3e28916e144cc Mon Sep 17 00:00:00 2001 From: Naz Date: Thu, 31 Jul 2025 14:50:21 +0100 Subject: =?UTF-8?q?=E2=9C=A8feat:=20add=20an=20error=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/error.rs | 21 +++++++++++++++++++++ src/lib.rs | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 src/error.rs (limited to 'src') diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..0183e38 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,21 @@ +use derive_more::From; + +pub type Result = core::result::Result; + +#[derive(Debug, From)] +pub enum Error { + InvalidPath, + NotFound(String), + + #[from] + Io(std::io::Error), + + #[from] + Json(serde_json::Error), + + #[from] + Http(reqwest::Error), + + #[from] + EnvVar(std::env::VarError), +} 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::*; -- cgit v1.2.3 From bd230c3d916be2af8f97e587f3f764800077cba4 Mon Sep 17 00:00:00 2001 From: Naz Date: Thu, 31 Jul 2025 14:54:01 +0100 Subject: =?UTF-8?q?=E2=9C=A8feat:=20implement=20Display=20and=20Error=20tr?= =?UTF-8?q?aits=20for=20custom=20Error=20enum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/error.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src') diff --git a/src/error.rs b/src/error.rs index 0183e38..3c51529 100644 --- a/src/error.rs +++ b/src/error.rs @@ -19,3 +19,21 @@ pub enum Error { #[from] EnvVar(std::env::VarError), } + +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"), + } + } +} + +impl std::error::Error for Error {} -- cgit v1.2.3 From 29c3640e5fea0f423357c28e3c221fcaca004ee8 Mon Sep 17 00:00:00 2001 From: Naz Date: Thu, 31 Jul 2025 15:46:40 +0100 Subject: =?UTF-8?q?=E2=9C=A8feat:=20use=20the=20custom=20Result=20type=20i?= =?UTF-8?q?nstead=20of=20the=20standard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/downloader.rs | 8 ++------ src/index.rs | 12 ++++-------- src/main.rs | 4 ++-- src/manager.rs | 15 ++++++--------- src/symlink.rs | 6 +++--- 5 files changed, 17 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/downloader.rs b/src/downloader.rs index 2196e25..be35bd8 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::{Result, appimages_dir, make_progress_bar}; #[derive(Debug, Default)] pub struct Downloader {} @@ -20,11 +20,7 @@ impl Downloader { appimages_dir().join(filename) } - pub async fn download_with_progress( - &self, - url: &str, - path: &PathBuf, - ) -> Result<(), Box> { + 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?; diff --git a/src/index.rs b/src/index.rs index 069068a..dff2f58 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,7 +9,7 @@ impl Index { pub fn new() -> Self { Self {} } - pub async fn get(&self, appname: &str) -> Result> { + pub async fn get(&self, appname: &str) -> Result { 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)?; @@ -19,11 +19,7 @@ impl Index { pub fn exists(&self, executable: &str) -> bool { index_dir().join(format!("{}.json", &executable)).exists() } - pub async fn add( - &self, - appimage: &AppImage, - appname: &str, - ) -> Result<(), Box> { + 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")); @@ -33,7 +29,7 @@ impl Index { Ok(()) } - pub async fn remove(&self, appname: &str) -> Result<(), Box> { + 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?; diff --git a/src/main.rs b/src/main.rs index 1d9505b..b6d538e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,10 @@ 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> { +async fn main() -> Result<()> { let args = Cli::parse(); let pm = PackageManager::new(); diff --git a/src/manager.rs b/src/manager.rs index 66ed6f6..6616e1a 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,13 +17,10 @@ impl PackageManager { symlink_manager: SymlinkManager::new(), } } - pub async fn install( - &self, - appimage: &mut AppImage, - appname: &str, - ) -> Result<(), Box> { + pub async fn install(&self, appimage: &mut AppImage, appname: &str) -> Result<()> { if self.index.exists(&appimage.executable) { - return Err(format!("{} is already installed.", &appimage.executable).into()); + println!("{} is already installed.", appimage.executable); + return Ok(()); } appimage.file_path = self @@ -37,7 +34,7 @@ impl PackageManager { self.symlink_manager.create(appimage).await?; Ok(()) } - pub async fn remove(&self, appname: &str) -> Result<(), Box> { + pub async fn remove(&self, appname: &str) -> Result<()> { let appimage = self.index.get(appname).await?; fs::remove_file(&appimage.file_path).await?; @@ -46,7 +43,7 @@ impl PackageManager { Ok(()) } - pub async fn list(&self) -> Result<(), Box> { + pub async fn list(&self) -> Result<()> { let mut appimages = fs::read_dir(index_dir()).await?; while let Some(appimage) = appimages.next_entry().await? { 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> { + 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> { + pub async fn create(&self, appimage: &AppImage) -> Result<()> { let home = std::env::var("HOME")?; let local_bin = PathBuf::from(home).join(".local/bin"); -- cgit v1.2.3 From 030eef4c9b4f82fe16ddd019c436e7065758d3dc Mon Sep 17 00:00:00 2001 From: Naz Date: Thu, 31 Jul 2025 15:53:19 +0100 Subject: =?UTF-8?q?=E2=9C=A8feat:=20use=20a=20separate=20run()=20function?= =?UTF-8?q?=20and=20handle=20error=20output=20in=20main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index b6d538e..1ea31f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,7 @@ use clap::Parser; use zap_rs::{AppImage, Cli, Command, PackageManager, Result, Source, SourceMetadata}; -#[tokio::main] -async fn main() -> Result<()> { +async fn run() -> Result<()> { let args = Cli::parse(); let pm = PackageManager::new(); @@ -32,3 +31,10 @@ async fn main() -> Result<()> { Ok(()) } + +#[tokio::main] +async fn main() { + if let Err(e) = run().await { + eprintln!("Error: {e}"); + } +} -- cgit v1.2.3 From 180fe06facf1f8b4796df69a42596643990b9d32 Mon Sep 17 00:00:00 2001 From: Naz Date: Thu, 31 Jul 2025 15:59:56 +0100 Subject: =?UTF-8?q?=E2=9C=A8feat:=20remove=20expect=20from=20path=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/downloader.rs | 6 +++--- src/index.rs | 12 ++++++------ src/manager.rs | 6 +++--- src/paths.rs | 16 +++++++++------- 4 files changed, 21 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/downloader.rs b/src/downloader.rs index be35bd8..cf78a49 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -11,17 +11,17 @@ 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 { // 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<()> { - fs::create_dir_all(&appimages_dir()).await?; + fs::create_dir_all(&appimages_dir()?).await?; let resp = reqwest::get(&url.to_string()).await?; let total_size = resp.content_length().unwrap_or(0); diff --git a/src/index.rs b/src/index.rs index dff2f58..8f2521a 100644 --- a/src/index.rs +++ b/src/index.rs @@ -10,19 +10,19 @@ impl Index { Self {} } pub async fn get(&self, appname: &str) -> Result { - let index_file_path = index_dir().join(format!("{appname}.json")); + 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 { + Ok(index_dir()?.join(format!("{}.json", &executable)).exists()) } pub async fn add(&self, appimage: &AppImage, appname: &str) -> Result<()> { - fs::create_dir_all(&index_dir()).await?; + 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?; @@ -30,7 +30,7 @@ impl Index { Ok(()) } pub async fn remove(&self, appname: &str) -> Result<()> { - let index_file_path = index_dir().join(format!("{appname}.json")); + let index_file_path = index_dir()?.join(format!("{appname}.json")); fs::remove_file(index_file_path).await?; Ok(()) diff --git a/src/manager.rs b/src/manager.rs index 6616e1a..6f3cfac 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -18,14 +18,14 @@ impl PackageManager { } } pub async fn install(&self, appimage: &mut AppImage, appname: &str) -> Result<()> { - if self.index.exists(&appimage.executable) { + 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?; @@ -44,7 +44,7 @@ impl PackageManager { Ok(()) } pub async fn list(&self) -> Result<()> { - let mut appimages = fs::read_dir(index_dir()).await?; + 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 { + 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 { + Ok(zap_rs_home()?.join("index")) } -pub fn appimages_dir() -> PathBuf { - zap_rs_home().join("appimages") +pub fn appimages_dir() -> Result { + Ok(zap_rs_home()?.join("appimages")) } -- cgit v1.2.3 From 1b50c5ce06d2356124991fa48e6881f61f646ca3 Mon Sep 17 00:00:00 2001 From: Naz Date: Fri, 1 Aug 2025 09:47:04 +0100 Subject: =?UTF-8?q?=F0=9F=90=9Bfix:=20exit=20with=20non-zero=20status=20wh?= =?UTF-8?q?en=20error=20occurs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/downloader.rs | 2 +- src/error.rs | 4 ++++ src/main.rs | 1 + src/tui.rs | 9 +++++---- 4 files changed, 11 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/downloader.rs b/src/downloader.rs index cf78a49..64df06e 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -26,7 +26,7 @@ impl Downloader { let resp = reqwest::get(&url.to_string()).await?; 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 diff --git a/src/error.rs b/src/error.rs index 3c51529..39c421d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,6 +18,9 @@ pub enum Error { #[from] EnvVar(std::env::VarError), + + #[from] + IndicatifTemplate(indicatif::style::TemplateError), } impl core::fmt::Display for Error { @@ -32,6 +35,7 @@ impl core::fmt::Display for Error { 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}"), } } } diff --git a/src/main.rs b/src/main.rs index 1ea31f8..7840fae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,5 +36,6 @@ async fn run() -> Result<()> { async fn main() { if let Err(e) = run().await { eprintln!("Error: {e}"); + std::process::exit(1); } } 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 { 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) } -- cgit v1.2.3 From dc0ee6ce99a0480b7a6c228492936b16ceaf60cd Mon Sep 17 00:00:00 2001 From: Naz Date: Sun, 3 Aug 2025 10:06:33 +0100 Subject: =?UTF-8?q?=E2=9C=A8feat:=20add=20a=20more=20spesific=20error=20fo?= =?UTF-8?q?r=20the=20download=20process?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/downloader.rs | 14 +++++++++++--- src/error.rs | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/downloader.rs b/src/downloader.rs index 64df06e..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::{Result, appimages_dir, make_progress_bar}; +use crate::{Error, Result, appimages_dir, make_progress_bar}; #[derive(Debug, Default)] pub struct Downloader {} @@ -23,7 +23,12 @@ impl Downloader { 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?; + 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)?; @@ -32,7 +37,10 @@ impl Downloader { // 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 index 39c421d..b473469 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,10 @@ pub type Result = core::result::Result; pub enum Error { InvalidPath, NotFound(String), + Download { + url: String, + source: reqwest::Error, + }, #[from] Io(std::io::Error), @@ -36,6 +40,22 @@ impl core::fmt::Display for Error { 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}") + } + } } } } -- cgit v1.2.3