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: move MinInfo into sfsu crate #799

Merged
merged 3 commits into from
May 24, 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
6 changes: 3 additions & 3 deletions src/commands/list.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use clap::{Parser, ValueEnum};
use rayon::prelude::*;

use sprinkles::{config, contexts::ScoopContext, packages::MinInfo};
use sprinkles::{config, contexts::ScoopContext};

use crate::output::structured::Structured;
use crate::{models::min::Info, output::structured::Structured};

#[derive(Debug, Clone, Parser)]
pub struct Args {
Expand Down Expand Up @@ -37,7 +37,7 @@ pub enum SortBy {

impl super::Command for Args {
async fn runner(self, ctx: &impl ScoopContext<config::Scoop>) -> Result<(), anyhow::Error> {
let mut outputs = MinInfo::list_installed(ctx, self.bucket.as_ref())?;
let mut outputs = Info::list_installed(ctx, self.bucket.as_ref())?;

outputs.par_sort_by(|a, b| match self.sort_by {
SortBy::Name => a.name.cmp(&b.name),
Expand Down
1 change: 1 addition & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod export;
pub mod info;
pub mod min;
pub mod outdated;
pub mod status;
10 changes: 6 additions & 4 deletions src/models/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ use sprinkles::{
config,
contexts::ScoopContext,
git,
packages::{Error as PackageError, MinInfo},
packages::Error as PackageError,
};

use super::min::Info;

#[derive(Debug, Clone, Serialize, Deserialize)]
/// The export data
pub struct Export {
Expand Down Expand Up @@ -70,7 +72,7 @@ impl Export {
.into_iter()
.map(Bucket::try_from)
.collect::<Result<Vec<_>, _>>()?;
let mut apps = MinInfo::list_installed(ctx, None)?;
let mut apps = Info::list_installed(ctx, None)?;
apps.par_sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));

Ok(Self {
Expand All @@ -81,8 +83,8 @@ impl Export {
}
}

impl From<MinInfo> for App {
fn from(info: MinInfo) -> Self {
impl From<Info> for App {
fn from(info: Info) -> Self {
Self {
name: info.name,
source: info.source,
Expand Down
123 changes: 123 additions & 0 deletions src/models/min.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use anyhow::Context;
use chrono::{DateTime, Local};
use quork::traits::truthy::ContainsTruth;
use rayon::prelude::*;
use serde::Serialize;
use sprinkles::{
config,
contexts::ScoopContext,
packages::{CreateManifest, InstallManifest, Manifest},
wrappers::time::NicerTime,
};
use std::path::Path;
use std::time::UNIX_EPOCH;

#[derive(Debug, Serialize)]
/// Minimal package info
pub struct Info {
/// The name of the package
pub name: String,
/// The version of the package
pub version: String,
/// The package's source (eg. bucket name)
pub source: String,
/// The last time the package was updated
pub updated: NicerTime<Local>,
/// The package's notes
pub notes: String,
}

impl Info {
/// Parse minmal package info for every installed app
///
/// # Errors
/// - Invalid file names
/// - File metadata errors
/// - Invalid time
pub fn list_installed(
ctx: &impl ScoopContext<config::Scoop>,
bucket: Option<&String>,
) -> anyhow::Result<Vec<Self>> {
let apps = ctx.installed_apps()?;

apps.par_iter()
.map(Self::from_path)
.filter(|package| {
if let Ok(pkg) = package {
if let Some(bucket) = bucket {
return &pkg.source == bucket;
}
}
// Keep errors so that the following line will return the error
true
})
.collect()
}

/// Parse minimal package into from a given path
///
/// # Errors
/// - Invalid file names
/// - File metadata errors
/// - Invalid time
///
/// # Panics
/// - Date time invalid or out of range
pub fn from_path(path: impl AsRef<Path>) -> anyhow::Result<Self> {
let path = path.as_ref();

let package_name = path
.file_name()
.map(|f| f.to_string_lossy())
.context("Missing file name")?;

let updated_time = {
let updated = {
let updated_sys = path.metadata()?.modified()?;

updated_sys.duration_since(UNIX_EPOCH)?.as_secs()
};

#[allow(clippy::cast_possible_wrap)]
DateTime::from_timestamp(updated as i64, 0)
.expect("invalid or out-of-range datetime")
.with_timezone(&Local)
};

let app_current = path.join("current");

let (manifest_broken, manifest) =
if let Ok(manifest) = Manifest::from_path(app_current.join("manifest.json")) {
(false, manifest)
} else {
(true, Manifest::default())
};

let (install_manifest_broken, install_manifest) = if let Ok(install_manifest) =
InstallManifest::from_path(app_current.join("install.json"))
{
(false, install_manifest)
} else {
(true, InstallManifest::default())
};

let broken = manifest_broken || install_manifest_broken;

let mut notes = vec![];

if broken {
notes.push("Install failed".to_string());
}
if install_manifest.hold.contains_truth() {
notes.push("Held package".to_string());
}

Ok(Self {
name: package_name.to_string(),
version: manifest.version.to_string(),
source: install_manifest.get_source(),
updated: updated_time.into(),
notes: notes.join(", "),
})
}
}