summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
9 files changed, 128 insertions, 56 deletions
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)
}