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

Add v2 ResoluteMod model and migration #193

Merged
merged 3 commits into from
Jul 29, 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
8 changes: 4 additions & 4 deletions Cargo.lock

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

8 changes: 5 additions & 3 deletions crates/resolute/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ futures-util = "0.3"
path-clean = "1.0"
sha2 = "0.10"
steamlocate = "2.0.0-beta.2"
native_db = { version = "0.7", optional = true }
native_db = { version = "0.7.1", optional = true, default-features = false }
native_model = { version = "0.4", optional = true }
redb = { version = "2.0", optional = true }
redb = { version = "2.1", optional = true }

[features]
default = ["db"]
default = ["db", "migrate"]
db = ["dep:native_db", "dep:native_model", "dep:redb"]
migrate = ["models_v1", "native_db/upgrade_0_5_x"]
models_v1 = []
42 changes: 31 additions & 11 deletions crates/resolute/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use core::result;
use std::{io::ErrorKind, path::Path};

use log::{info, warn};
use native_db::{db_type::Error as NativeDbError, Builder, Database, Models};
use once_cell::sync::Lazy;
use redb::{DatabaseError, StorageError};

use crate::{mods::ResoluteMod, Error, Result as ResoluteResult};
use crate::{models::ResoluteMod, Error, Result};

/// Wrapper for interacting with a Resolute database
#[allow(missing_debug_implementations)]
Expand All @@ -16,7 +17,7 @@ pub struct ResoluteDatabase<'a> {
impl ResoluteDatabase<'_> {
/// Opens a database using a provided builder.
/// If the database doesn't already exist at the given path, it will be created.
pub fn open(db_path: impl AsRef<Path>) -> ResoluteResult<Self> {
pub fn open(db_path: impl AsRef<Path>) -> Result<Self> {
info!("Opening database at {}", db_path.as_ref().display());

// Try to open an already-existing database
Expand All @@ -35,40 +36,49 @@ impl ResoluteDatabase<'_> {
Err(err) => return Err(err.into()),
};

// Run migrations
#[cfg(feature = "migrate")]
{
info!("Running migrations on database");
let rw = db.rw_transaction()?;
rw.migrate::<ResoluteMod>()?;
rw.commit()?;
}

info!("Database initialized");
Ok(Self { db })
}

/// Retrieves all mods stored in the database
pub fn get_mods(&self) -> ResoluteResult<Vec<ResoluteMod>> {
pub fn get_mods(&self) -> Result<Vec<ResoluteMod>> {
let read = self.db.r_transaction()?;
let mods = read.scan().primary()?.all().collect::<Result<_, _>>()?;
let mods = read.scan().primary()?.all().collect::<result::Result<_, _>>()?;
Ok(mods)
}

/// Retrieves all mods from the database that have an installed version
pub fn get_installed_mods(&self) -> ResoluteResult<Vec<ResoluteMod>> {
pub fn get_installed_mods(&self) -> Result<Vec<ResoluteMod>> {
let read = self.db.r_transaction()?;
let mods = read
.scan()
.primary()?
.all()
.collect::<Result<Vec<_>, _>>()?
.collect::<result::Result<Vec<_>, _>>()?
.into_iter()
.filter(|rmod: &ResoluteMod| rmod.installed_version.is_some())
.collect();
Ok(mods)
}

/// Retrieves a single mod from the database by its ID
pub fn get_mod(&self, id: impl AsRef<str>) -> ResoluteResult<Option<ResoluteMod>> {
pub fn get_mod(&self, id: impl AsRef<str>) -> Result<Option<ResoluteMod>> {
let read = self.db.r_transaction()?;
let rmod = read.get().primary(id.as_ref())?;
Ok(rmod)
}

/// Stores a mod in the database (overwrites any existing entry for the same mod)
pub fn store_mod(&self, rmod: ResoluteMod) -> ResoluteResult<()> {
pub fn store_mod(&self, rmod: ResoluteMod) -> Result<()> {
let mod_name = rmod.to_string();

let rw = self.db.rw_transaction()?;
Expand All @@ -80,7 +90,7 @@ impl ResoluteDatabase<'_> {
}

/// Removes a mod from the database
pub fn remove_mod(&self, rmod: ResoluteMod) -> ResoluteResult<()> {
pub fn remove_mod(&self, rmod: ResoluteMod) -> Result<()> {
let mod_name = rmod.to_string();

// Remove the mod
Expand All @@ -93,7 +103,7 @@ impl ResoluteDatabase<'_> {
}

/// Removes a mod from the database by its ID
pub fn remove_mod_by_id(&self, id: impl AsRef<str>) -> ResoluteResult<()> {
pub fn remove_mod_by_id(&self, id: impl AsRef<str>) -> Result<()> {
// Find the item in the database
let id = id.as_ref();
let read = self.db.r_transaction()?;
Expand All @@ -108,10 +118,20 @@ impl ResoluteDatabase<'_> {
}

/// Models that a [`ResoluteDatabase`] interacts with
#[allow(clippy::absolute_paths)]
pub static MODELS: Lazy<Models> = Lazy::new(|| {
let mut models = Models::new();

// Current models
models
.define::<ResoluteMod>()
.expect("Unable to define ResoluteMod model");
.expect("Unable to define current ResoluteMod model");

// Legacy models
#[cfg(feature = "models_v1")]
models
.define::<crate::models::v1::ResoluteMod>()
.expect("Unable to define v1 ResoluteMod model");

models
});
2 changes: 1 addition & 1 deletion crates/resolute/src/discover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use sha2::{Digest, Sha256};
use steamlocate::SteamDir;

use crate::{
mods::{ModVersion, ResoluteMod, ResoluteModMap},
models::{ModVersion, ResoluteMod, ResoluteModMap},
Result,
};

Expand Down
2 changes: 1 addition & 1 deletion crates/resolute/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tokio::task;

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

/// Error returned from a Downloader
Expand Down
2 changes: 1 addition & 1 deletion crates/resolute/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub mod discover;
mod error;
pub mod manager;
pub mod manifest;
pub mod mods;
pub mod models;

pub use error::Error;
pub use error::Result;
2 changes: 1 addition & 1 deletion crates/resolute/src/manager/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};

use log::info;

use crate::mods::{ModArtifact, ModVersion};
use crate::models::{ModArtifact, ModVersion};
use crate::Error;

use super::artifacts::{self, ArtifactAction, ArtifactError, ArtifactErrorVec, MappableToArtifactError};
Expand Down
2 changes: 1 addition & 1 deletion crates/resolute/src/manager/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use tokio::{
io::{AsyncWriteExt, BufWriter},
};

use crate::mods::{ModArtifact, ModVersion};
use crate::models::{ModArtifact, ModVersion};
use crate::{Error, Result};

use super::artifacts::{self, ArtifactAction, ArtifactError, MappableToArtifactError};
Expand Down
6 changes: 3 additions & 3 deletions crates/resolute/src/manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tokio::task;

#[cfg(feature = "db")]
use crate::db::ResoluteDatabase;
use crate::mods::{self, ResoluteMod, ResoluteModMap};
use crate::models::{self, ResoluteMod, ResoluteModMap};
use crate::{discover, manifest, Error, Result};

pub use self::delete::Deleter;
Expand Down Expand Up @@ -93,7 +93,7 @@ impl_ModManager_with_without_db! {
// Parse the JSON into raw manifest data, load that into a mod map
let mut mods = task::spawn_blocking(move || -> Result<ResoluteModMap> {
let data = manifest.parse(&json)?;
let mods = mods::load_manifest(data);
let mods = models::load_manifest(data);
Ok(mods)
})
.await??;
Expand All @@ -113,7 +113,7 @@ impl_ModManager_with_without_db! {
pub async fn mark_installed_mods(&self, mods: &mut ResoluteModMap) -> Result<Option<ResoluteModMap>> {
use std::{collections::HashSet, ffi::OsString};

use crate::mods::{ModArtifact, ModVersion};
use crate::models::{ModArtifact, ModVersion};

// Load the installed mods
let LoadedMods {
Expand Down
1 change: 1 addition & 0 deletions crates/resolute/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ pub struct ManifestEntryVersion {
pub conflicts: Option<ManifestEntryDependencies>,
#[serde(rename = "releaseUrl")]
pub release_url: Option<Url>,
pub changelog: Option<String>,
}

/// Represents a single "artifacts" entry in the manifest JSON
Expand Down
5 changes: 5 additions & 0 deletions crates/resolute/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[cfg(all(feature = "db", feature = "models_v1"))]
pub mod v1;
pub mod v2;

pub use v2::*;
154 changes: 154 additions & 0 deletions crates/resolute/src/models/v1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#![allow(clippy::exhaustive_structs, clippy::absolute_paths)]

use std::collections::HashMap;

#[cfg(feature = "db")]
use native_db::{native_db, ToKey};
#[cfg(feature = "db")]
use native_model::{native_model, Model};
use semver::Version;
use serde::{Deserialize, Serialize};
use url::Url;

use super::{ModAuthor, ModDependencyMap};

/// First version of the [`super::ResoluteMod`] struct, kept around for database migration purposes
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "db", native_model(id = 1, version = 1))]
#[cfg_attr(feature = "db", native_db)]
pub struct ResoluteMod {
// The primary_key and secondary_key macros don't work with cfg_attr for whatever reason
#[cfg(feature = "db")]
#[primary_key]
pub id: String,
#[cfg(not(feature = "db"))]
pub id: String,

// The primary_key and secondary_key macros don't work with cfg_attr for whatever reason
#[cfg(feature = "db")]
#[secondary_key]
pub name: String,
#[cfg(not(feature = "db"))]
pub name: String,

pub description: String,
pub category: String,
pub authors: Vec<ModAuthor>,
#[serde(rename = "sourceLocation")]
pub source_location: Option<Url>,
pub website: Option<Url>,
pub tags: Option<Vec<String>>,
pub flags: Option<Vec<String>>,
pub platforms: Option<Vec<String>>,
pub versions: HashMap<Version, ModVersion>,
#[serde(rename = "installedVersion")]
pub installed_version: Option<Version>,
}

impl From<ResoluteMod> for super::v2::ResoluteMod {
fn from(value: ResoluteMod) -> Self {
Self {
id: value.id,
name: value.name,
description: value.description,
category: value.category,
authors: value.authors,
source_location: value.source_location,
website: value.website,
tags: value.tags,
flags: value.flags,
platforms: value.platforms,
versions: value.versions.into_iter().map(|(svr, ver)| (svr, ver.into())).collect(),
active: value.installed_version.is_some(),
installed_version: value.installed_version,
}
}
}

impl From<super::v2::ResoluteMod> for ResoluteMod {
fn from(value: super::v2::ResoluteMod) -> Self {
Self {
id: value.id,
name: value.name,
description: value.description,
category: value.category,
authors: value.authors,
source_location: value.source_location,
website: value.website,
tags: value.tags,
flags: value.flags,
platforms: value.platforms,
versions: value.versions.into_iter().map(|(svr, ver)| (svr, ver.into())).collect(),
installed_version: value.installed_version,
}
}
}

/// First version of the [`super::ModVersion`] struct, kept around for database migration purposes
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModVersion {
pub semver: Version,
pub artifacts: Vec<ModArtifact>,
pub dependencies: ModDependencyMap,
pub conflicts: ModDependencyMap,
#[serde(rename = "releaseUrl")]
pub release_url: Option<Url>,
}

impl From<ModVersion> for super::v2::ModVersion {
fn from(value: ModVersion) -> Self {
Self {
semver: value.semver,
artifacts: value.artifacts.into_iter().map(Into::into).collect(),
dependencies: value.dependencies,
conflicts: value.conflicts,
release_url: value.release_url,
changelog: None,
}
}
}

impl From<super::v2::ModVersion> for ModVersion {
fn from(value: super::v2::ModVersion) -> Self {
Self {
semver: value.semver,
artifacts: value.artifacts.into_iter().map(Into::into).collect(),
dependencies: value.dependencies,
conflicts: value.conflicts,
release_url: value.release_url,
}
}
}

/// First version of the [`super::ModArtifact`] struct, kept around for database migration purposes
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModArtifact {
pub url: Url,
pub sha256: String,
pub filename: Option<String>,
#[serde(rename = "installLocation")]
pub install_location: Option<String>,
}

impl From<ModArtifact> for super::v2::ModArtifact {
fn from(value: ModArtifact) -> Self {
Self {
url: value.url,
sha256: value.sha256,
filename: value.filename,
install_location: value.install_location,
override_filename: None,
}
}
}

impl From<super::v2::ModArtifact> for ModArtifact {
fn from(value: super::v2::ModArtifact) -> Self {
Self {
url: value.url,
sha256: value.sha256,
filename: value.filename,
install_location: value.install_location,
}
}
}
Loading