Skip to content

Commit

Permalink
refactor: use anyhow::Result and don't mess with Errors
Browse files Browse the repository at this point in the history
  • Loading branch information
brusherru committed Feb 7, 2024
1 parent 511a9c9 commit d26cb72
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 158 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.79"
chrono = "0.4.31"
clap = { version="4.4.14", features=["derive"] }
duration-string = "0.3.0"
Expand Down
69 changes: 30 additions & 39 deletions src/checksum.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,38 @@
use reqwest::blocking::Client;
use anyhow::Result;
use reqwest::blocking::{Client, Response};
use std::{
fs::File,
io::{self, BufReader, BufRead, Error},
path::Path,
fs::File,
io::{BufRead, BufReader},
path::Path,
};
use url::Url;

use crate::utils::strip_trailing_newline;

pub fn download_checksum(url: &Url) -> Result<String, Error> {
let mut u = url.clone();
u.path_segments_mut()
.expect("Wrong URL")
.pop()
.push("state.sql.md5");
let md5_url = u.to_string();

let client = Client::new();
let response = client
.get(md5_url)
.send()
.map_err(|e| Error::new(std::io::ErrorKind::Other, e.to_string()))?;

if response.status().is_success() {
let md5 = response
.text()
.map_err(|e| Error::new(std::io::ErrorKind::Other, e.to_string()))?;
let stripped = strip_trailing_newline(&md5);
Ok(stripped.to_string())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Cannot download MD5 checksum",
))
}
pub fn download_checksum(url: &Url) -> Result<String> {
let mut u = url.clone();
u.path_segments_mut()
.expect("Wrong URL")
.pop()
.push("state.sql.md5");
let md5_url = u.to_string();

let client = Client::new();
let response: Response = client.get(md5_url).send()?;

if response.status().is_success() {
let md5 = response.text()?;
let stripped = strip_trailing_newline(&md5);
Ok(stripped.to_string())
} else {
anyhow::bail!(
"Cannot download MD5 checksum: status code is {:?}",
response.status()
);
}
}

pub fn calculate_checksum(file_path: &Path) -> io::Result<String> {
pub fn calculate_checksum(file_path: &Path) -> Result<String> {
let file = File::open(file_path)?;
let mut reader = BufReader::with_capacity(16 * 1024 * 1024, file);
let mut hasher = md5::Context::new();
Expand All @@ -55,15 +51,10 @@ pub fn calculate_checksum(file_path: &Path) -> io::Result<String> {
Ok(format!("{:x}", hash))
}

pub fn verify(
redirect_file_path: &Path,
unpacked_file_path: &Path,
) -> Result<bool, std::io::Error> {
let archive_url_str = String::from_utf8(std::fs::read(redirect_file_path)?)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
pub fn verify(redirect_file_path: &Path, unpacked_file_path: &Path) -> Result<bool> {
let archive_url_str = String::from_utf8(std::fs::read(redirect_file_path)?)?;

let archive_url = Url::parse(&archive_url_str)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
let archive_url = Url::parse(&archive_url_str)?;

let md5_expected = download_checksum(&archive_url)?;
let md5_actual = calculate_checksum(unpacked_file_path)?;
Expand Down
189 changes: 96 additions & 93 deletions src/download.rs
Original file line number Diff line number Diff line change
@@ -1,122 +1,125 @@
use anyhow::Result;
use reqwest::blocking::Client;
use std::collections::VecDeque;
use std::error::Error;
use std::fs::{self, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
use std::time::Instant;

pub fn download_file(url: &str, file_path: &Path, redirect_path: &Path) -> Result<(), Box<dyn Error>> {
if let Some(dir) = file_path.parent() {
fs::create_dir_all(dir)?;
}
pub fn download_file(url: &str, file_path: &Path, redirect_path: &Path) -> Result<()> {
if let Some(dir) = file_path.parent() {
fs::create_dir_all(dir)?;
}

let mut file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(file_path)?;
let mut file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(file_path)?;

let file_size = file.metadata()?.len();
let file_size = file.metadata()?.len();

let client = Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()?;
let mut response = client
.get(url)
.header("Range", format!("bytes={}-", file_size))
.send()?;
let client = Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()?;
let mut response = client
.get(url)
.header("Range", format!("bytes={}-", file_size))
.send()?;

let final_url = response.url().clone();
let final_url = response.url().clone();

fs::write(redirect_path, final_url.as_str())?;
fs::write(redirect_path, final_url.as_str())?;

if !response.status().is_success() {
let err_message = format!("Cannot download: {:?}", response.status());
fs::remove_file(redirect_path)?;
fs::remove_file(file_path)?;
return Err(Box::new(io::Error::new(io::ErrorKind::NotFound, err_message)));
}
if !response.status().is_success() {
fs::remove_file(redirect_path)?;
fs::remove_file(file_path)?;

anyhow::bail!(
"Failed to download: Response status code is {:?}",
response.status()
);
}

let total_size = response
.headers()
.get(reqwest::header::CONTENT_LENGTH)
.and_then(|ct_len| ct_len.to_str().ok())
.and_then(|ct_len| ct_len.parse::<u64>().ok())
.unwrap_or(0)
+ file_size;

file.seek(SeekFrom::End(0))?;
let mut downloaded: u64 = file_size;
let mut last_reported_progress: i64 = -1;
let start = Instant::now();
let mut measurements = VecDeque::with_capacity(10);

let mut buffer = [0; 16 * 1024];
while let Ok(bytes_read) = response.read(&mut buffer) {
if bytes_read == 0 {
break;
}
file.write_all(&buffer[..bytes_read])?;
downloaded += bytes_read as u64;

let elapsed = start.elapsed().as_secs_f64();
let speed = if elapsed > 0.0 {
downloaded as f64 / elapsed
} else {
0.0
};
measurements.push_back(speed);
if measurements.len() > 10 {
measurements.pop_front();
}
let avg_speed = measurements.iter().sum::<f64>() / measurements.len() as f64;
let eta = if avg_speed > 0.0 {
(total_size as f64 - downloaded as f64) / avg_speed
} else {
0.0
};

let progress = (downloaded as f64 / total_size as f64 * 100.0).round() as i64;
if progress > last_reported_progress {
println!(
"Downloading... {:.2}% ({:.2} MB/{:.2} MB) ETA: {:.0} sec",
progress,
downloaded as f64 / 1_024_000.00,
total_size as f64 / 1_024_000.00,
eta
);
last_reported_progress = progress;
}
let total_size = response
.headers()
.get(reqwest::header::CONTENT_LENGTH)
.and_then(|ct_len| ct_len.to_str().ok())
.and_then(|ct_len| ct_len.parse::<u64>().ok())
.unwrap_or(0)
+ file_size;

file.seek(SeekFrom::End(0))?;
let mut downloaded: u64 = file_size;
let mut last_reported_progress: i64 = -1;
let start = Instant::now();
let mut measurements = VecDeque::with_capacity(10);

let mut buffer = [0; 16 * 1024];
while let Ok(bytes_read) = response.read(&mut buffer) {
if bytes_read == 0 {
break;
}
file.write_all(&buffer[..bytes_read])?;
downloaded += bytes_read as u64;

let elapsed = start.elapsed().as_secs_f64();
let speed = if elapsed > 0.0 {
downloaded as f64 / elapsed
} else {
0.0
};
measurements.push_back(speed);
if measurements.len() > 10 {
measurements.pop_front();
}
let avg_speed = measurements.iter().sum::<f64>() / measurements.len() as f64;
let eta = if avg_speed > 0.0 {
(total_size as f64 - downloaded as f64) / avg_speed
} else {
0.0
};

let progress = (downloaded as f64 / total_size as f64 * 100.0).round() as i64;
if progress > last_reported_progress {
println!(
"Downloading... {:.2}% ({:.2} MB/{:.2} MB) ETA: {:.0} sec",
progress,
downloaded as f64 / 1_024_000.00,
total_size as f64 / 1_024_000.00,
eta
);
last_reported_progress = progress;
}
}

println!("Download finished");
println!("Download finished");

Ok(())
Ok(())
}

pub fn download_with_retries(
url: &str,
file_path: &Path,
redirect_path: &Path,
max_retries: u32,
) -> Result<(), Box<dyn Error>> {
) -> Result<()> {
let mut attempts = 0;

loop {
match download_file(url, file_path, redirect_path) {
Ok(()) => return Ok(()),
Err(e) if attempts < max_retries => {
eprintln!(
"Download error: {}. Attempt {} / {}",
e,
attempts + 1,
max_retries
);
attempts += 1;
std::thread::sleep(std::time::Duration::from_secs(5));
}
Err(e) => return Err(e),
match download_file(url, file_path, redirect_path) {
Ok(()) => return Ok(()),
Err(e) if attempts < max_retries => {
eprintln!(
"Download error: {}. Attempt {} / {}",
e,
attempts + 1,
max_retries
);
attempts += 1;
std::thread::sleep(std::time::Duration::from_secs(5));
}
Err(e) => return Err(e),
}
}
}
4 changes: 2 additions & 2 deletions src/go_spacemesh.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::error::Error;
use anyhow::Result;
use std::process::Command;

use crate::utils::trim_version;

pub fn get_version(path: &str) -> Result<String, Box<dyn Error>> {
pub fn get_version(path: &str) -> Result<String> {
let output = Command::new(path).arg("version").output()?;

let version = String::from_utf8(output.stdout)?;
Expand Down
15 changes: 8 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use chrono::Duration;
use clap::{Parser, Subcommand};
use std::error::Error;
use std::path::PathBuf;
use std::process;
use url::Url;
Expand Down Expand Up @@ -73,7 +72,7 @@ fn go_spacemesh_default_path() -> &'static str {
"./go-spacemesh"
}
}
fn main() -> Result<(), Box<dyn Error>> {
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();

match cli.command {
Expand Down Expand Up @@ -157,12 +156,14 @@ fn main() -> Result<(), Box<dyn Error>> {
Ok(_) => {
println!("Archive unpacked successfully");
}
Err(e) if e.raw_os_error() == Some(28) => {
println!("Cannot unpack archive: not enough disk space");
std::fs::remove_file(&unpacked_file_path)?;
process::exit(2);
}
Err(e) => {
if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
if io_err.raw_os_error() == Some(28) {
println!("Cannot unpack archive: not enough disk space");
std::fs::remove_file(&unpacked_file_path)?;
process::exit(2);
}
}
println!("Cannot unpack archive: {}", e);
std::fs::remove_file(&unpacked_file_path)?;
std::fs::remove_file(&archive_file_path)?;
Expand Down
7 changes: 4 additions & 3 deletions src/sql.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use anyhow::{Context, Result};
use rusqlite::{params, Connection};
use std::{error::Error, path::PathBuf};
use std::path::PathBuf;

pub fn get_last_layer_from_db(db_path: &PathBuf) -> Result<i32, Box<dyn Error>> {
let conn = Connection::open(db_path)?;
pub fn get_last_layer_from_db(db_path: &PathBuf) -> Result<i32> {
let conn = Connection::open(db_path).context("Failed to connect to db")?;

let mut stmt = conn.prepare("SELECT * FROM layers ORDER BY id DESC LIMIT 1")?;
let mut layer_iter = stmt.query_map(params![], |row| row.get::<_, i32>(0))?;
Expand Down
Loading

0 comments on commit d26cb72

Please sign in to comment.