Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Downloader to use linear-ish types, replace ArtifactPaths struct with ModArtifact helpers, and add reusable artifact FS interaction logic #141

Merged
merged 7 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions crates/resolute/src/discover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use sha2::{Digest, Sha256};
use steamlocate::SteamDir;

use crate::{
manager::ArtifactPaths,
mods::{ModVersion, ResoluteMod, ResoluteModMap},
Result,
};
Expand Down Expand Up @@ -52,7 +51,7 @@ pub fn discover_mods(base_path: impl AsRef<Path>, mods: ResoluteModMap) -> Resul
pub fn discover_mods_by_checksum(base_path: impl AsRef<Path>, mods: &ResoluteModMap) -> Result<ResoluteModMap> {
let mut discovered = ResoluteModMap::new();
let mut checksums: HashMap<PathBuf, Option<String>> = HashMap::new();
let base_path = base_path.as_ref().to_path_buf();
let base_path = base_path.as_ref();

'mods: for (id, rmod) in mods {
'versions: for (semver, version) in &rmod.versions {
Expand All @@ -62,8 +61,8 @@ pub fn discover_mods_by_checksum(base_path: impl AsRef<Path>, mods: &ResoluteMod
trace!("Checking for artifact {} from mod {} v{}", artifact, rmod, semver);

// Build the path that the artifact would use
let path = match ArtifactPaths::try_new(artifact, &base_path) {
Ok(paths) => base_path.join(paths.final_dest),
let path = match artifact.dest_within(base_path) {
Ok(path) => path,
Err(_err) => continue 'versions,
};

Expand Down
96 changes: 9 additions & 87 deletions crates/resolute/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::{fmt::Display, path::PathBuf};

use reqwest::StatusCode;
use semver::Version;

use crate::mods::ResoluteMod;
use crate::{
manager::artifacts::{ArtifactError, ArtifactErrorVec},
mods::ResoluteMod,
};

/// Error returned from a Downloader
#[derive(thiserror::Error, Debug)]
Expand Down Expand Up @@ -46,10 +47,13 @@ pub enum Error {
ModNotInstalled(Box<ResoluteMod>),

#[error("artifact error: {0}")]
Artifact(ArtifactError),
Artifact(#[from] ArtifactError),

#[error("multiple artifact errors: {0}")]
Artifacts(ArtifactErrorVec),
Artifacts(#[from] ArtifactErrorVec),

#[error("no old artifact exists to delete")]
NoOldArtifact,

#[error("resonite discovery error: {0}")]
Discovery(#[from] steamlocate::Error),
Expand All @@ -63,87 +67,5 @@ pub enum Error {
ItemNotFound(String),
}

/// An error performing an action on an artifact
#[derive(Debug)]
pub struct ArtifactError {
pub action: ArtifactAction,
pub path: Option<PathBuf>,
pub source: Box<Error>,
}

impl std::error::Error for ArtifactError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}

impl Display for ArtifactError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.path {
Some(path) => write!(
f,
"artifact {} error for file ({}): {}",
self.action,
path.display(),
self.source
),
None => write!(f, "artifact error: {}", self.source),
}
}
}

/// An action being attempted on an artifact
#[derive(Debug)]
pub enum ArtifactAction {
Download,
Delete,
Rename,
}

impl Display for ArtifactAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let label = match self {
Self::Download => "download",
Self::Delete => "delete",
Self::Rename => "rename",
};

write!(f, "{}", label)
}
}

/// A Vec of artifact errors
#[derive(Debug, Default)]
pub struct ArtifactErrorVec(pub Vec<ArtifactError>);

impl ArtifactErrorVec {
/// Creates a new empty error vec
pub fn new() -> Self {
Self(Vec::new())
}

#[inline]
pub fn push(&mut self, err: ArtifactError) {
self.0.push(err)
}

#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
}

impl Display for ArtifactErrorVec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let text = self.0.iter().map(|err| err.to_string()).collect::<Vec<_>>().join(", ");
write!(f, "[{}]", text)
}
}

/// Alias for a `Result` with the error type `resolute::Error`.
pub type Result<T> = core::result::Result<T, Error>;
137 changes: 137 additions & 0 deletions crates/resolute/src/manager/artifacts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use std::{
fmt::Display,
path::{Path, PathBuf},
};

use tokio::fs;

use crate::Error;

/// Deletes an artifact file
pub(crate) async fn delete(path: &Path) -> Result<(), ArtifactError> {
fs::remove_file(path).await.map_err(|err| ArtifactError {
action: ArtifactAction::Delete,
path: Some(path.to_owned()),
source: Box::new(err.into()),
})?;
Ok(())
}

/// Renames an artifact file
pub(crate) async fn rename(from: &Path, to: &Path, ignore_nonexistent: bool) -> Result<bool, ArtifactError> {
match fs::rename(from, to).await {
Ok(..) => Ok(true),
Err(err) => {
if ignore_nonexistent && err.kind() == std::io::ErrorKind::NotFound {
Ok(false)
} else {
Err(ArtifactError {
action: ArtifactAction::Rename,
path: Some(from.to_owned()),
source: Box::new(err.into()),
})
}
}
}
}

/// An error performing an action on an artifact
#[derive(Debug)]
pub struct ArtifactError {
pub action: ArtifactAction,
pub path: Option<PathBuf>,
pub source: Box<Error>,
}

impl ArtifactError {
/// Creates a new ArtifactError without a path value
pub fn new_pathless(action: ArtifactAction, source: Error) -> Self {
Self {
action,
path: None,
source: Box::new(source),
}
}

/// Returns a closure that maps an [Error] to an [ArtifactError] with a None path
#[inline]
pub(crate) fn map_pathless(action: ArtifactAction) -> impl FnOnce(Error) -> Self {
|err| Self::new_pathless(action, err)
}
}

impl std::error::Error for ArtifactError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.source)
}
}

impl Display for ArtifactError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.path {
Some(path) => write!(
f,
"artifact {} error for file ({}): {}",
self.action,
path.display(),
self.source
),
None => write!(f, "artifact error: {}", self.source),
}
}
}

/// An action being attempted on an artifact
#[derive(Debug)]
pub enum ArtifactAction {
Download,
Delete,
Rename,
}

impl Display for ArtifactAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let label = match self {
Self::Download => "download",
Self::Delete => "delete",
Self::Rename => "rename",
};

write!(f, "{}", label)
}
}

/// A Vec of artifact errors
#[derive(Debug, Default)]
pub struct ArtifactErrorVec(pub Vec<ArtifactError>);

impl ArtifactErrorVec {
/// Creates a new empty error vec
pub fn new() -> Self {
Self(Vec::new())
}

#[inline]
pub fn push(&mut self, err: ArtifactError) {
self.0.push(err)
}

#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
}

impl Display for ArtifactErrorVec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let text = self.0.iter().map(|err| err.to_string()).collect::<Vec<_>>().join(", ");
write!(f, "[{}]", text)
}
}

impl std::error::Error for ArtifactErrorVec {}
Loading