Skip to content
This repository has been archived by the owner on May 2, 2024. It is now read-only.

feat: persistance system #61

Open
wants to merge 19 commits into
base: oxidation
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ categories = ["caching", "config", "parsing", "os::linux-apis"]
figment = { version = "0.10.6", features = ["env", "test", "toml" ] }
num_cpus = "1.13.1"
serde = { version = "1.0.144", features = ["derive"] }
chrono = { version = "0.4.22", features = ["serde", "clock", "std"], default-features = false }
serde_derive = "1.0.144"
serde_json = "1.0.85"
error-stack = "0.2.2"

[dev-dependencies]
rstest = "0.15.0"
239 changes: 239 additions & 0 deletions examples/store_crud.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
use std::collections::HashMap;

use chrono::NaiveDateTime;
use error_stack::Result;
use libpacstall::model::{InstallState, Kind, PacBuild, Repository, Version};
use libpacstall::store::base::Store;
use libpacstall::store::errors::StoreError;
use libpacstall::store::filters;
use libpacstall::store::query_builder::{PacBuildQuery, RepositoryQuery, StringClause};

fn main() {
let mut store = Store::in_memory();

example_entity_insertion(&mut store).unwrap();
example_entity_query(&store);
example_entity_update(&mut store).unwrap();
example_entity_deletion(&mut store).unwrap();
}

fn example_entity_query(store: &Store) {
println!("## Running [example_entity_query]");

println!("\n\tSearching for all pacbuilds that contain the word 'discord' in their name.");

let pacbuilds = store.query_pacbuilds(|store| {
store.find(
PacBuildQuery::select().where_name(StringClause::Contains(String::from("discord"))),
)
});

println!(
"\n\tWe are expecting to find 1 result. Found: {}.",
pacbuilds.len()
);
assert_eq!(pacbuilds.len(), 1);
println!("\tDone!");

let pacbuild = pacbuilds.first().unwrap();

println!(
"\n\tWe are expecting to find 'discord-deb' from 'https://awesome-repository.local' \
repository."
);
println!(
"\t\tFound '{}' from repository '{}'",
&pacbuild.name, &pacbuild.repository
);
assert_eq!(pacbuild.name, String::from("discord-deb"));
assert_eq!(
pacbuild.repository,
String::from("https://awesome-repository.local")
);
println!("\tDone!\n");
}

#[allow(clippy::redundant_pattern_matching)]
fn example_entity_insertion(store: &mut Store) -> Result<(), StoreError> {
println!("\n## Running [example_entity_insertion]\n");

// Create dummy data
let repository = create_repository(
String::from("My Awesome Repository"),
String::from("https://awesome-repository.local"),
);

let pacbuild = create_pacbuild(
String::from("discord-deb"),
InstallState::None,
Kind::DebFile(String::from("some hash")),
repository.url.clone(),
);

// Insert repository first, because the pacbuild depends on it.
println!("\n\tAttempting to insert the new repository into the store.");
store.mutate_repositories(|store| store.insert(repository.clone()))?;
println!("\tDone!\n");

// Repository exists so it is safe to add the pacbuild.
println!("\tAttempting to insert the new pacbuild into the store.");
store.mutate_pacbuilds(|store| store.insert(pacbuild.clone()))?;
println!("\tDone!\n");

// PacBuild is already inserted, so trying to insert it again would result in a
// conflict error.
println!("\tAttempting to insert the same pacbuild into the store.");
let result = store.mutate_pacbuilds(|store| store.insert(pacbuild.clone()));
if let Err(_) = &result {
println!("\t\tInserting the same pacbuild failed as expected.");

// Uncomment the next line to see how the stacktrace looks :)
// result.unwrap();
} else {
panic!("\t\tThis will never be printed.")
}
println!("\tDone!\n");

Ok(())
}

fn example_entity_update(store: &mut Store) -> Result<(), StoreError> {
println!("## Running [example_entity_update]\n");

// Search for the discord package.
println!("\tSearching for a single package called 'discord-deb'.");
let mut pacbuild = store
.query_pacbuilds(|store| {
store.single(
PacBuildQuery::select().where_name("discord-deb".into()) // Same as StringClause::Equals(String::from("discord-deb"))
)
})
.unwrap();

println!("\tFound: {:#?}\n", pacbuild);
assert_eq!(pacbuild.install_state, InstallState::None);

// Assume we installed it
println!("\tWe update it so it looks like it is installed.");
pacbuild.install_state = InstallState::Direct(current_time(), Version::single(1));
store.mutate_pacbuilds(|store| store.update(pacbuild.clone()))?;
println!("\tUpdated pacbuild: {:#?}\n", pacbuild);

// Search again
println!("\tWe search for the same package again.");
let same_pacbuild = store
.query_pacbuilds(|store| {
store.single(PacBuildQuery::select().where_install_state(filters::InstallState::Direct))
})
.unwrap();
println!(
"\tValue after re-querying the store: {:#?}\n",
same_pacbuild
);

println!("\tAsserting that the change propagated.");
assert_eq!(pacbuild, same_pacbuild);
println!("\tDone!");

Ok(())
}

fn example_entity_deletion(store: &mut Store) -> Result<(), StoreError> {
println!("## Running [example_entity_deletion]\n");

// Select the first repository
println!("\tFetching a repository.");
let repository = store
.query_repositories(|store| store.single(RepositoryQuery::select()))
.unwrap();
println!("\tFound: {:?}\n", repository);

let pacbuilds = store.query_pacbuilds(|store| {
store.find(PacBuildQuery::select().where_repository_url(repository.url.as_str().into()))
});
println!(
"\tThis repository has a total of **{}** pacbuilds.\n",
pacbuilds.len()
);

// We attempt to delete it
println!("\tAttempting to delete it.");
store.mutate_repositories(|store| {
store.remove(RepositoryQuery::select().where_url(repository.url.as_str().into()))
})?;
println!("\tDone!\n");

// Selecting the same repository again
println!("\tSelecting the same repository again.");
let found = store.query_repositories(|store| {
store.single(RepositoryQuery::select().where_url(repository.url.as_str().into()))
});
assert!(found.is_none());
println!("\tFound no match.\n");

// Find any pacbuild from that repository.
println!("\tAttempting to find any pacbuild from that repository.");
let pacbuilds = store.query_pacbuilds(|store| {
store.find(PacBuildQuery::select().where_repository_url(repository.url.as_str().into()))
});

println!("\tWe expect to find none. Found: **{}**\n", pacbuilds.len());
assert_eq!(pacbuilds.len(), 0);

Ok(())
}

fn create_pacbuild(
name: String,
install_state: InstallState,
kind: Kind,
repository_url: String,
) -> PacBuild {
println!(
"\tCreating dummy PacBuild[name = '{}', install_state = '{:?}', kind = '{:?}', repository \
= '{}']",
name, install_state, kind, repository_url
);

PacBuild {
name,
last_updated: current_time(),
repository: repository_url,
maintainers: Vec::new(),
package_names: Vec::new(),
description: String::from(""),
homepage: String::from(""),
repology_version: Version::single(1),
repology: String::from(""),
install_state,
dependencies: Vec::new(),
optional_dependencies: HashMap::new(),
licenses: vec![String::from("MIT")],
conflicts: Vec::new(),
epoch: 0,
groups: Vec::new(),
make_dependencies: Vec::new(),
package_base: None,
ppas: Vec::new(),
provides: Vec::new(),
replaces: Vec::new(),
url: String::from("https://pacbuild.pac"),
kind,
}
}

fn create_repository(name: String, url: String) -> Repository {
println!(
"\tCreating dummy Repository[name = '{}', url = '{}']",
name, url
);
Repository {
name,
url,
preference: 0,
}
}

fn current_time() -> NaiveDateTime {
NaiveDateTime::from_timestamp(chrono::Utc::now().timestamp(), 0)
}
35 changes: 2 additions & 33 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ use figment::value::{Dict, Map};
use figment::{Error, Figment, Metadata, Profile, Provider};
use serde::{Deserialize, Serialize};

use crate::model::{default_repository, Repository};

/// Pacstall's configuration.
///
/// Gives access to the [configuration](Config) extracted, and the [Figment]
Expand Down Expand Up @@ -191,39 +193,6 @@ impl Default for Settings {
}
}

/// The extracted `repositories` array of tables.
///
/// Defaults to the official repository.
#[derive(Deserialize, Debug, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Repository {
/// The name of the repository.
pub name: String,
/// URL of the repository.
///
/// Note that the URL **isn't verified** during extraction!
pub url: String,
/// Preference of the repository.
///
/// Specifies which repository to look into first during certain operations
/// like installing a package. If the package isn't present in the first
/// preferred repository, then the second preferred repository is looked
/// into.
pub preference: u32,
}

fn default_repository() -> Vec<Repository> { vec![Repository::default()] }

impl Default for Repository {
fn default() -> Self {
Self {
name: "official".into(),
url: "https://github.com/pacstall/pacstall-programs".into(),
preference: 1,
}
}
}

#[cfg(test)]
mod tests {
use std::fs::{self, File};
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
)]
#![allow(clippy::must_use_candidate)]
pub mod config;
pub mod model;
pub mod store;
7 changes: 7 additions & 0 deletions src/model/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//! Provides structs to handle Pacstall's data models.

mod pacbuild;
mod repository;

pub use crate::model::pacbuild::*;
pub use crate::model::repository::{default_repository, Repository};
saenai255 marked this conversation as resolved.
Show resolved Hide resolved
Loading