From 3aa84eff957046abc7e00cddf6727f4cb75bf3e2 Mon Sep 17 00:00:00 2001 From: Arnaud Ferraris Date: Thu, 2 Nov 2023 13:30:43 +0100 Subject: [PATCH] Implement "outdated" check and introduce "compatible" version Package search only returns a boolean depending on whether a package with a correct version exists in Debian (or NEW). This isn't sufficient to handle the cases where the package exists, but is so outdated that the version in Debian is likely incompatible with the crate we're inspecting. Moreover, this doesn't differentiate between exact version matches and semver'd matches (e.g. when the Debian version is older but has the same major -- and, if needed, minor -- as the version found on crates.io). In order to deal with the latter case, this change introduces the notion of "compatible" version: it represents crates already packaged in Debian but not on the latest version, although it *should* still be compatible with the requirements. The "compatible" and "outdated" information are then propagated, as well as the Debian package version, through a new `db::PkgInfo` structure used as the return type for the `db::search*` functions. That way, this additional information can easily be processed by other modules and ultimately be reported to the user. --- src/db.rs | 81 +++++++++++++++++++++++++++++++++-------------- src/debian.rs | 24 +++++++++++--- src/format/mod.rs | 14 ++++++-- 3 files changed, 88 insertions(+), 31 deletions(-) diff --git a/src/db.rs b/src/db.rs index 4ac8bdc..3d9bdf2 100644 --- a/src/db.rs +++ b/src/db.rs @@ -9,13 +9,26 @@ use std::time::{Duration, SystemTime}; const POSTGRES: &str = "postgresql://udd-mirror:udd-mirror@udd-mirror.debian.net/udd"; const CACHE_EXPIRE: Duration = Duration::from_secs(90 * 60); +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub enum PkgStatus { + NotFound, + Outdated, + Compatible, + Found, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PkgInfo { + pub status: PkgStatus, + pub version: String, +} + #[derive(Debug, Serialize, Deserialize)] pub struct CacheEntry { pub from: SystemTime, - pub found: bool, + pub info: PkgInfo, } -// TODO: also use this for outdated check(?) fn is_compatible(debversion: &str, crateversion: &VersionReq) -> Result { let debversion = debversion.replace('~', "-"); let debversion = Version::parse(&debversion)?; @@ -56,20 +69,28 @@ impl Connection { target: &str, package: &str, version: &Version, - ) -> Result, Error> { + ) -> Result, Error> { let path = self.cache_path(target, package, version); if !path.exists() { return Ok(None); } - let buf = fs::read(path)?; - let cache: CacheEntry = serde_json::from_slice(&buf)?; + let buf = fs::read(&path)?; + // If the cache entry can't be deserialized, it's probably using an old + // entry format, so let's discard it + let cache: CacheEntry = match serde_json::from_slice(&buf) { + Ok(e) => e, + _ => { + fs::remove_file(path)?; + return Ok(None) + }, + }; if SystemTime::now().duration_since(cache.from)? > CACHE_EXPIRE { Ok(None) } else { - Ok(Some(cache.found)) + Ok(Some(cache.info)) } } @@ -78,49 +99,49 @@ impl Connection { target: &str, package: &str, version: &Version, - found: bool, + info: &PkgInfo, ) -> Result<(), Error> { let cache = CacheEntry { from: SystemTime::now(), - found, + info: info.clone(), }; let buf = serde_json::to_vec(&cache)?; fs::write(self.cache_path(target, package, version), buf)?; Ok(()) } - pub fn search(&mut self, package: &str, version: &Version) -> Result { - if let Some(found) = self.check_cache("sid", package, version)? { - return Ok(found); + pub fn search(&mut self, package: &str, version: &Version) -> Result { + if let Some(info) = self.check_cache("sid", package, version)? { + return Ok(info); } // config.shell().status("Querying", format!("sid: {}", package))?; info!("Querying -> sid: {}", package); - let found = self.search_generic( + let info = self.search_generic( "SELECT version::text FROM sources WHERE source in ($1, $2) AND release='sid';", package, version, )?; - self.write_cache("sid", package, version, found)?; - Ok(found) + self.write_cache("sid", package, version, &info)?; + Ok(info) } - pub fn search_new(&mut self, package: &str, version: &Version) -> Result { - if let Some(found) = self.check_cache("new", package, version)? { - return Ok(found); + pub fn search_new(&mut self, package: &str, version: &Version) -> Result { + if let Some(info) = self.check_cache("new", package, version)? { + return Ok(info); } // config.shell().status("Querying", format!("new: {}", package))?; info!("Querying -> new: {}", package); - let found = self.search_generic( + let info = self.search_generic( "SELECT version::text FROM new_sources WHERE source in ($1, $2);", package, version, )?; - self.write_cache("new", package, version, found)?; - Ok(found) + self.write_cache("new", package, version, &info)?; + Ok(info) } pub fn search_generic( @@ -128,7 +149,11 @@ impl Connection { query: &str, package: &str, version: &Version, - ) -> Result { + ) -> Result { + let mut info = PkgInfo { + status: PkgStatus::NotFound, + version: String::new(), + }; let package = package.replace('_', "-"); let semver_version = if version.major == 0 { if version.minor == 0 { @@ -156,12 +181,20 @@ impl Connection { // println!("{:?} ({:?}) => {:?}", debversion, version, is_compatible(debversion, version)?); - if is_compatible(debversion, &version)? || is_compatible(debversion, &semver_version)? { - return Ok(true); + if is_compatible(debversion, &version)? { + info.version = debversion.to_string(); + info.status = PkgStatus::Found; + return Ok(info); + } else if is_compatible(debversion, &semver_version)? { + info.version = debversion.to_string(); + info.status = PkgStatus::Compatible; + } else if info.status == PkgStatus::NotFound { + info.version = debversion.to_string(); + info.status = PkgStatus::Outdated; } } - Ok(false) + Ok(info) } } diff --git a/src/debian.rs b/src/debian.rs index 53f4805..46b8703 100644 --- a/src/debian.rs +++ b/src/debian.rs @@ -1,4 +1,4 @@ -use crate::db::Connection; +use crate::db::{Connection, PkgStatus}; use crate::errors::*; use crate::graph::Graph; use cargo_metadata::{Package, PackageId, Source}; @@ -52,6 +52,8 @@ pub struct DebianInfo { pub in_unstable: bool, pub in_new: bool, pub outdated: bool, + pub compatible: bool, + pub version: String, } fn run_task(db: &mut Connection, pkg: Pkg) -> Result { @@ -59,12 +61,26 @@ fn run_task(db: &mut Connection, pkg: Pkg) -> Result { in_unstable: false, in_new: false, outdated: false, + compatible: false, + version: String::new(), }; - if db.search(&pkg.name, &pkg.version).unwrap() { + let mut info = db.search(&pkg.name, &pkg.version).unwrap(); + if info.status == PkgStatus::NotFound { + info = db.search_new(&pkg.name, &pkg.version).unwrap(); + if info.status != PkgStatus::NotFound { + deb.in_new = true; + deb.version = info.version; + } + } else { deb.in_unstable = true; - } else if db.search_new(&pkg.name, &pkg.version).unwrap() { - deb.in_new = true; + deb.version = info.version; + } + + match info.status { + PkgStatus::Outdated => deb.outdated = true, + PkgStatus::Compatible => deb.compatible = true, + _ => (), } Ok(deb) diff --git a/src/format/mod.rs b/src/format/mod.rs index b4d212a..512a30d 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -58,11 +58,19 @@ impl<'a> fmt::Display for Display<'a> { let pkg = format!("{} v{}", self.package.name, self.package.version); if let Some(deb) = &self.package.debinfo { if deb.in_unstable { - write!(fmt, "{} (in debian)", pkg.green())?; + if deb.compatible { + write!(fmt, "{} ({} in debian)", pkg.green(), deb.version.yellow())?; + } else { + write!(fmt, "{} (in debian)", pkg.green())?; + } } else if deb.in_new { - write!(fmt, "{} (in debian NEW queue)", pkg.blue())?; + if deb.compatible { + write!(fmt, "{} ({} in debian NEW queue)", pkg.blue(), deb.version.yellow())?; + } else { + write!(fmt, "{} (in debian NEW queue)", pkg.blue())?; + } } else if deb.outdated { - write!(fmt, "{} (outdated)", pkg.yellow())?; + write!(fmt, "{} (outdated, {})", pkg.red(), deb.version)?; } else { write!(fmt, "{pkg}")?; }