Fetch file version from Nexus
All checks were successful
lint/clippy Checking for common mistakes and opportunities for code improvement
build/msvc Build for the target platform: msvc
build/linux Build for the target platform: linux

When importing an archive file downloaded from Nexus, the file name does
include a version field. But, presumably for compatibility reasons,
Nexus replaces special characters with `-`, so that this field doesn't
match common schemes like `1.0.0`.
So instead we use the also included update timestamp to find the
corresponding file info from Nexus and use the version data from that.

Closes #131.
This commit is contained in:
Lucas Schwiderski 2023-11-24 13:54:19 +01:00
parent 12e01075d9
commit 246564d00f
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
3 changed files with 70 additions and 3 deletions

View file

@ -381,7 +381,7 @@ pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result<Mod
.wrap_err_with(|| format!("Failed to read file {}", info.path.display()))?;
let data = Cursor::new(data);
let nexus = if let Some((_, id, version, _)) = info
let nexus = if let Some((_, id, version, timestamp)) = info
.path
.file_name()
.and_then(|s| s.to_str())
@ -393,9 +393,23 @@ pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result<Mod
.mods_id(id)
.await
.wrap_err_with(|| format!("Failed to query mod {} from Nexus", id))?;
let info = NexusInfo::from(mod_info);
tracing::debug!("{:?}", info);
let version = match api.file_version(id, timestamp).await {
Ok(version) => version,
Err(err) => {
let err = Report::new(err);
tracing::warn!(
"Failed to fetch version for Nexus download. \
Falling back to file name:\n{:?}",
err
);
version
}
};
let info = NexusInfo::from(mod_info);
tracing::debug!(version, ?info);
Some((info, version))
} else {
None

View file

@ -39,6 +39,8 @@ pub enum Error {
Infallible(#[from] Infallible),
#[error("invalid NXM URL '{}': {0}", .1.as_str())]
InvalidNXM(&'static str, Url),
#[error("{0}")]
Custom(String),
}
pub type Result<T> = std::result::Result<T, Error>;
@ -102,6 +104,28 @@ impl Api {
self.send(req).await
}
#[tracing::instrument(skip(self))]
pub async fn file_version<T>(&self, id: u64, timestamp: T) -> Result<String>
where
T: std::fmt::Debug,
OffsetDateTime: PartialEq<T>,
{
let url = BASE_URL_GAME.join(&format!("mods/{id}/files.json"))?;
let req = self.client.get(url);
let files: FileList = self.send(req).await?;
let Some(file) = files
.files
.into_iter()
.find(|file| file.updated_timestamp == timestamp)
else {
let err = Error::Custom("Timestamp does not match any file".into());
return Err(err);
};
Ok(file.version)
}
pub fn parse_file_name<S: AsRef<str>>(
name: S,
) -> Option<(String, u64, String, OffsetDateTime)> {

View file

@ -64,6 +64,35 @@ pub struct Mod {
// pub contains_adult_content: bool,
}
#[derive(Debug, Deserialize)]
pub struct File {
pub id: Vec<u64>,
pub uid: u64,
pub file_id: u64,
pub name: String,
pub version: String,
pub category_id: u64,
pub category_name: String,
pub is_primary: bool,
pub size: u64,
pub file_name: String,
#[serde(with = "time::serde::timestamp")]
pub updated_timestamp: OffsetDateTime,
pub mod_version: String,
pub external_virus_scan_url: String,
pub description: String,
pub size_kb: u64,
pub size_in_bytes: u64,
pub changelog_html: String,
pub content_preview_link: String,
}
#[derive(Debug, Deserialize)]
pub struct FileList {
pub files: Vec<File>,
// pub file_updates: Vec<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub struct DownloadLink {
pub name: String,