use rusqlite::Connection;
use crate::{
config::Cfg,
data::{list::List, modloader::Modloader},
};
type DbE<T> = Result<T, DBError>;
#[derive(Debug, thiserror::Error)]
pub enum DBError {
#[error("sqlite: {source}")]
Sqlite {
#[from]
source: rusqlite::Error,
},
#[error("conversion: {source}")]
Conversion {
#[from]
source: crate::errors::ConversionError,
},
#[error("not enough arguments")]
NotEnoughArguments,
#[error("couldn't find requested mod/version")]
NotFound,
#[error("no mods/versions are available in the requested scope")]
NotAvailable,
}
//MODS
/// # Errors
pub fn mods_insert(config: &Cfg, id: &str, slug: &str, name: &str) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute(
"INSERT INTO mods (id, slug, title) VALUES (?1, ?2, ?3)",
[id, slug, name.replace('\'', "").as_str()],
)?;
Ok(())
}
/// # Errors
pub fn mods_get_all_ids(config: &Cfg) -> DbE<Vec<String>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut mods: Vec<String> = Vec::new();
let mut stmt = connection.prepare("SELECT id FROM mods")?;
let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
for id in id_iter {
mods.push(id?);
}
if mods.is_empty() {
Err(DBError::NotFound)
} else {
Ok(mods)
}
}
/// Get mod id based on the slug or name
/// # Arguments
///
///* `data` - file directory of the database
///* `slug` - Slug or Name of a mod
///
/// # Errors
///
/// Will return `DBError` when no mod id is found
pub fn mods_get_id(data: &str, slug: &str) -> DbE<String> {
let data = format!("{data}/data.db");
let connection = Connection::open(data)?;
let mut mod_id = String::new();
//get from slug
let mut stmt = connection.prepare("SELECT id FROM mods WHERE slug = ?")?;
let id_iter = stmt.query_map([slug], |row| row.get::<usize, String>(0))?;
for id in id_iter {
mod_id = id?;
}
//get from id if no slug found
if mod_id.is_empty() {
let mut stmt =
connection.prepare("SELECT id FROM mods WHERE id = ?")?;
let id_iter =
stmt.query_map([slug], |row| row.get::<usize, String>(0))?;
for id in id_iter {
mod_id = id?;
}
}
//get from title if no id found from slug
if mod_id.is_empty() {
let mut stmt =
connection.prepare("SELECT id FROM mods WHERE title = ?")?;
let id_iter =
stmt.query_map([slug], |row| row.get::<usize, String>(0))?;
for id in id_iter {
mod_id = id?;
}
}
if mod_id.is_empty() {
Err(DBError::NotFound)?;
};
Ok(mod_id)
}
pub struct ModInfo {
pub slug: String,
pub title: String,
}
/// # Errors
pub fn mods_get_info(config: &Cfg, id: &str) -> DbE<ModInfo> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut mod_info: Option<ModInfo> = None;
let mut stmt =
connection.prepare("SELECT title, slug FROM mods WHERE id = ?")?;
let name_iter = stmt.query_map([id], |row| {
Ok(vec![
row.get::<usize, String>(0)?,
row.get::<usize, String>(1)?,
])
})?;
for info in name_iter {
let i = info?;
mod_info = Some(ModInfo {
title: String::from(&i[0]),
slug: String::from(&i[1]),
});
}
if let Some(mi) = mod_info {
Ok(mi)
} else {
Err(DBError::NotFound)?
}
}
/// # Errors
pub fn mods_remove(config: &Cfg, id: &str) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute("DELETE FROM mods WHERE id = ?", [id])?;
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DBModlistVersions {
pub mod_id: String,
pub versions: String,
}
/// # Errors
pub fn mods_get_versions(
config: &Cfg,
mods: &[String],
) -> DbE<Vec<DBModlistVersions>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
if mods.is_empty() {
Err(DBError::NotEnoughArguments)?;
}
let mut wherestr = String::from("WHERE");
for (i, id) in mods.iter().enumerate() {
let mut or = " OR";
if i == mods.len() - 1 {
or = "";
};
wherestr = format!("{wherestr} id = '{id}'{or}");
}
let mut versionmaps: Vec<DBModlistVersions> = Vec::new();
let mut stmt = connection.prepare(
format!("SELECT id, versions, title FROM mods {wherestr}").as_str(),
)?;
let id_iter = stmt.query_map([], |row| {
Ok(vec![
row.get::<usize, String>(0)?,
row.get::<usize, String>(1)?,
row.get::<usize, String>(2)?,
])
})?;
for ver in id_iter {
let version = ver?;
versionmaps.push(DBModlistVersions {
mod_id: String::from(&version[0]),
versions: String::from(&version[1]),
});
}
if versionmaps.is_empty() {
Err(DBError::NotFound)
} else {
Ok(versionmaps)
}
}
//userlist
/// # Errors
pub fn userlist_insert(
config: &Cfg,
list_id: &str,
mod_id: &str,
current_version: &str,
applicable_versions: &[String],
current_link: &str,
set_version: bool,
) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let sv = if set_version { "1" } else { "0" };
connection.execute(
format!("INSERT INTO {list_id} VALUES (?1, ?2, ?3, ?4, 'NONE', ?5)")
.as_str(),
[
mod_id,
current_version,
applicable_versions.join("|").as_str(),
current_link,
sv,
],
)?;
Ok(())
}
/// # Errors
pub fn userlist_get_all_ids(config: &Cfg, list_id: &str) -> DbE<Vec<String>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut mod_ids: Vec<String> = Vec::new();
let mut stmt =
connection.prepare(format!("SELECT mod_id FROM {list_id}").as_str())?;
let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
for id in id_iter {
mod_ids.push(id?);
}
if mod_ids.is_empty() {
Err(DBError::NotAvailable)
} else {
Ok(mod_ids)
}
}
/// # Errors
pub fn userlist_remove(config: &Cfg, list_id: &str, mod_id: &str) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute(
format!("DELETE FROM {list_id} WHERE mod_id = ?").as_str(),
[mod_id],
)?;
Ok(())
}
/// # Errors
pub fn userlist_get_applicable_versions(
config: &Cfg,
list_id: &str,
mod_id: String,
) -> DbE<String> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut version: String = String::new();
let mut stmt = connection.prepare(
format!("SELECT applicable_versions FROM {list_id} WHERE mod_id = ?")
.as_str(),
)?;
let ver_iter =
stmt.query_map([mod_id], |row| row.get::<usize, String>(0))?;
for ver in ver_iter {
version = ver?;
}
if version.is_empty() {
Err(DBError::NotFound)
} else {
Ok(version)
}
}
/// # Errors
pub fn userlist_get_all_applicable_versions_with_mods(
config: &Cfg,
list_id: &str,
) -> DbE<Vec<(String, String)>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut versions: Vec<(String, String)> = Vec::new();
let mut stmt = connection.prepare(
format!("SELECT mod_id, applicable_versions FROM {list_id}").as_str(),
)?;
let id_iter = stmt.query_map([], |row| {
Ok(vec![
row.get::<usize, String>(0)?,
row.get::<usize, String>(1)?,
])
})?;
for ver in id_iter {
let out = ver?;
versions.push((out[0].clone(), out[1].clone()));
}
if versions.is_empty() {
return Err(DBError::NotAvailable);
};
Ok(versions)
}
/// # Errors
pub fn userlist_get_current_version(
config: &Cfg,
list_id: &str,
mod_id: &str,
) -> DbE<String> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut version: String = String::new();
let mut stmt = connection.prepare(
format!("SELECT current_version FROM {list_id} WHERE mod_id = ?")
.as_str(),
)?;
let ver_iter =
stmt.query_map([&mod_id], |row| row.get::<usize, String>(0))?;
for ver in ver_iter {
version = ver?;
}
if version.is_empty() {
Err(DBError::NotFound)
} else {
Ok(version)
}
}
/// # Errors
pub fn userlist_get_all_current_version_ids(
config: &Cfg,
list_id: &str,
) -> DbE<Vec<String>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut versions: Vec<String> = Vec::new();
let mut stmt = connection
.prepare(format!("SELECT current_version FROM {list_id}").as_str())?;
let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
for id in id_iter {
versions.push(id?);
}
if versions.is_empty() {
return Err(DBError::NotAvailable);
};
Ok(versions)
}
/// # Errors
pub fn userlist_get_all_current_versions_with_mods(
config: &Cfg,
list_id: &str,
) -> DbE<Vec<(String, String)>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut versions: Vec<(String, String)> = Vec::new();
let mut stmt = connection.prepare(
format!("SELECT mod_id, current_version FROM {list_id}").as_str(),
)?;
let id_iter = stmt.query_map([], |row| {
Ok(vec![
row.get::<usize, String>(0)?,
row.get::<usize, String>(1)?,
])
})?;
for ver in id_iter {
let out = ver?;
versions.push((out[0].clone(), out[1].clone()));
}
if versions.is_empty() {
return Err(DBError::NotAvailable);
};
Ok(versions)
}
/// # Errors
pub fn userlist_get_set_version(
config: &Cfg,
list_id: &str,
mod_id: &str,
) -> DbE<bool> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut set_version: bool = false;
let mut stmt = connection.prepare(
format!("SELECT set_version FROM {list_id} WHERE mod_id = ?").as_str(),
)?;
let ver_iter =
stmt.query_map([&mod_id], |row| row.get::<usize, bool>(0))?;
for ver in ver_iter {
set_version = ver?;
}
Ok(set_version)
}
/// # Errors
pub fn userlist_change_versions(
config: &Cfg,
list_id: &str,
current_version: String,
versions: String,
link: String,
mod_id: String,
) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute(format!("UPDATE {list_id} SET current_version = ?1, applicable_versions = ?2, current_download = ?3 WHERE mod_id = ?4").as_str(), [current_version, versions, link, mod_id])?;
Ok(())
}
/// # Errors
pub fn userlist_add_disabled_versions(
config: &Cfg,
list_id: &str,
disabled_version: String,
mod_id: String,
) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let currently_disabled_versions =
userlist_get_disabled_versions(config, list_id, String::from(&mod_id))?;
let disabled_versions = if currently_disabled_versions == "NONE" {
disabled_version
} else {
format!("{currently_disabled_versions}|{disabled_version}")
};
connection.execute(
format!(
"UPDATE {list_id} SET disabled_versions = ?1 WHERE mod_id = ?2"
)
.as_str(),
[disabled_versions, mod_id],
)?;
Ok(())
}
/// # Errors
pub fn userlist_get_disabled_versions(
config: &Cfg,
list_id: &str,
mod_id: String,
) -> DbE<String> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut version: String = String::new();
let mut stmt = connection.prepare(
format!("SELECT disabled_versions FROM {list_id} WHERE mod_id = ?")
.as_str(),
)?;
let ver_iter =
stmt.query_map([mod_id], |row| row.get::<usize, String>(0))?;
for ver in ver_iter {
version = ver?;
}
if version.is_empty() {
Err(DBError::NotFound)
} else {
Ok(version)
}
}
/// # Errors
pub fn userlist_get_all_downloads(
config: &Cfg,
list_id: &str,
) -> DbE<Vec<String>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut links: Vec<String> = Vec::new();
let mut stmt = connection
.prepare(format!("SELECT current_download FROM {list_id}").as_str())?;
let link_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
for link in link_iter {
let l = link?;
links.push(l);
}
if links.is_empty() {
return Err(DBError::NotAvailable);
};
Ok(links)
}
//lists
///Inserts into lists table and creates new table
/// # Errors
pub fn lists_insert(
config: &Cfg,
id: &str,
mc_version: &str,
mod_loader: &Modloader,
download_folder: &str,
) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute(
"INSERT INTO lists VALUES (?1, ?2, ?3, ?4)",
[id, mc_version, &mod_loader.to_string(), download_folder],
)?;
connection.execute(format!("CREATE TABLE {id}( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB, 'current_download' TEXT, 'disabled_versions' TEXT DEFAULT 'NONE', 'set_version' INTEGER, CONSTRAINT {id}_PK PRIMARY KEY (mod_id) )").as_str(), [])?;
Ok(())
}
/// # Errors
pub fn lists_remove(config: &Cfg, id: &str) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute("DELETE FROM lists WHERE id = ?", [&id])?;
connection.execute(format!("DROP TABLE {id}").as_str(), [])?;
Ok(())
}
/// # Errors
pub fn lists_get(config: &Cfg, list_id: &str) -> DbE<List> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut list = List {
id: String::new(),
mc_version: String::new(),
modloader: Modloader::Fabric,
download_folder: String::new(),
};
let mut stmt = connection.prepare(
"SELECT mc_version, modloader, download_folder FROM lists WHERE id = ?",
)?;
let list_iter = stmt.query_map([&list_id], |row| {
Ok(vec![
row.get::<usize, String>(0)?,
row.get::<usize, String>(1)?,
row.get::<usize, String>(2)?,
])
})?;
for l in list_iter {
let li = l?;
list = List {
id: list_id.to_string(),
mc_version: String::from(&li[0]),
modloader: Modloader::try_from(li[1].as_str())?,
download_folder: String::from(&li[2]),
};
}
if list.id.is_empty() {
Err(DBError::NotFound)?;
}
Ok(list)
}
/// # Errors
pub fn lists_version(config: &Cfg, list_id: &str, version: &str) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute(
"UPDATE lists SET mc_version = ? WHERE id = ?",
[version, list_id],
)?;
Ok(())
}
/// # Errors
pub fn lists_get_all_ids(config: &Cfg) -> DbE<Vec<String>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut list_ids: Vec<String> = Vec::new();
let mut stmt = connection.prepare("SELECT id FROM lists")?;
let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
for id in id_iter {
list_ids.push(id?);
}
if list_ids.is_empty() {
Err(DBError::NotAvailable)
} else {
Ok(list_ids)
}
}
//config
/// # Errors
pub fn config_change_current_list(config: &Cfg, id: &str) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute(
"UPDATE user_config SET value = ? WHERE id = 'current_list'",
[id],
)?;
Ok(())
}
/// # Errors
pub fn config_get_current_list(config: &Cfg) -> DbE<String> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut list_id = String::new();
let mut stmt = connection
.prepare("SELECT value FROM user_config WHERE id = 'current_list'")?;
let list_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
for list in list_iter {
list_id = list?;
}
if list_id.is_empty() {
return Err(DBError::NotAvailable);
}
Ok(list_id)
}
//SETUP(UPDATES)
/// # Errors
pub fn s_userlist_update_download(
config: &Cfg,
list_id: &str,
mod_id: String,
link: String,
) -> Result<(), Box<dyn std::error::Error>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute(
format!("UPDATE {list_id} SET current_download = ?1 WHERE mod_id = ?2")
.as_str(),
[link, mod_id],
)?;
Ok(())
}
/// # Errors
pub fn s_config_create_version(
config: &Cfg,
) -> Result<(), Box<dyn std::error::Error>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute(
"INSERT INTO 'user_config' VALUES ( 'db_version', '0.2' )",
(),
)?;
Ok(())
}
/// # Errors
pub fn s_config_update_version(
config: &Cfg,
ver: String,
) -> Result<(), Box<dyn std::error::Error>> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
connection.execute(
"UPDATE user_config SET value = ? WHERE id = 'db_version'",
[ver],
)?;
Ok(())
}
/// # Errors
pub fn s_config_get_version(
config: &Cfg,
) -> DbE<String> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut version: String = String::new();
let mut stmt = connection
.prepare("SELECT value FROM user_config WHERE id = 'db_version'")?;
let ver_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
for ver in ver_iter {
version = ver?;
}
if version.is_empty() {
return Err(DBError::NotAvailable);
};
Ok(version)
}
/// # Errors
pub fn s_insert_column(
config: &Cfg,
table: &str,
column: &str,
c_type: &str,
default: Option<String>,
) -> DbE<()> {
let data = format!("{}/data.db", config.data);
let connection = Connection::open(data)?;
let mut sql = format!("ALTER TABLE {table} ADD '{column}' {c_type}");
if let Some(def) = default {
sql = format!("{sql} DEFAULT {def}");
}
connection.execute(sql.as_str(), ())?;
Ok(())
}
/// # Errors
pub fn setup(path: &str) -> DbE<()> {
let connection = Connection::open(path)?;
connection.execute_batch(
"CREATE TABLE 'user_config' ( 'id' TEXT, 'value' TEXT );
CREATE TABLE 'mods' ( 'id' TEXT, 'slug' TEXT, 'title' TEXT, CONSTRAINT mods_PK PRIMARY KEY (id) );
CREATE TABLE 'lists' ( 'id' TEXT, 'mc_version' TEXT, 'modloader' TEXT, 'download_folder' TEXT );
INSERT INTO 'user_config' VALUES ( 'db_version', '0.5' );
INSERT INTO 'user_config' VALUES ( 'current_list', '...' )",
)?;
Ok(())
}