wip
This commit is contained in:
@@ -37,7 +37,7 @@ type-toppings = { version = "0.2.1", features = ["result"] }
|
|||||||
wasm-bindgen = "=0.2.99"
|
wasm-bindgen = "=0.2.99"
|
||||||
web-sys = { version = "0.3.76", features = ["File", "FileList"] }
|
web-sys = { version = "0.3.76", features = ["File", "FileList"] }
|
||||||
xdg = { version = "2.5", optional = true }
|
xdg = { version = "2.5", optional = true }
|
||||||
uuid = { version = "1.12", optional = true, features = ["serde", "v4"] }
|
uuid = { version = "1.12", features = ["serde", "v4"] }
|
||||||
redb = { version = "2.4", optional = true }
|
redb = { version = "2.4", optional = true }
|
||||||
bincode = { version = "1.3", optional = true }
|
bincode = { version = "1.3", optional = true }
|
||||||
|
|
||||||
@@ -48,7 +48,6 @@ version = "1"
|
|||||||
hydrate = ["leptos/hydrate"]
|
hydrate = ["leptos/hydrate"]
|
||||||
ssr = [
|
ssr = [
|
||||||
"dep:axum",
|
"dep:axum",
|
||||||
"dep:uuid",
|
|
||||||
"dep:redb",
|
"dep:redb",
|
||||||
"dep:bincode",
|
"dep:bincode",
|
||||||
"dep:tokio",
|
"dep:tokio",
|
||||||
|
|||||||
@@ -1,8 +1,94 @@
|
|||||||
//! Shared models between server and client code.
|
//! Shared models between server and client code.
|
||||||
|
|
||||||
|
pub use v1::Hold;
|
||||||
|
pub use v1::HoldPosition;
|
||||||
|
pub use v1::HoldRole;
|
||||||
|
pub use v1::Image;
|
||||||
|
pub use v2::Method;
|
||||||
|
pub use v2::Problem;
|
||||||
|
pub use v2::ProblemId;
|
||||||
|
pub use v2::Root;
|
||||||
|
pub use v2::Wall;
|
||||||
|
pub use v2::WallId;
|
||||||
|
|
||||||
|
pub mod v2 {
|
||||||
|
use super::v1;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Root {
|
||||||
|
pub walls: BTreeSet<WallId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Wall {
|
||||||
|
pub uid: WallId,
|
||||||
|
pub rows: u64,
|
||||||
|
pub cols: u64,
|
||||||
|
pub holds: BTreeMap<v1::HoldPosition, v1::Hold>,
|
||||||
|
pub problems: BTreeSet<ProblemId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||||
|
pub struct WallId(pub uuid::Uuid);
|
||||||
|
impl WallId {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(uuid::Uuid::new_v4())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Problem {
|
||||||
|
pub uid: ProblemId,
|
||||||
|
pub name: String,
|
||||||
|
pub set_by: String,
|
||||||
|
pub holds: BTreeMap<v1::HoldPosition, v1::HoldRole>,
|
||||||
|
pub method: Method,
|
||||||
|
pub date_added: chrono::DateTime<chrono::Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||||
|
pub struct ProblemId(pub uuid::Uuid);
|
||||||
|
impl ProblemId {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(uuid::Uuid::new_v4())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Method {
|
||||||
|
FeetFollowHands,
|
||||||
|
Footless,
|
||||||
|
FootlessPlusKickboard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod v1 {
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use smart_default::SmartDefault;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
const STATE_VERSION: u64 = 1;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, SmartDefault)]
|
||||||
|
pub struct PersistentState {
|
||||||
|
/// State schema version
|
||||||
|
#[default(STATE_VERSION)]
|
||||||
|
pub version: u64,
|
||||||
|
|
||||||
|
pub wall: Wall,
|
||||||
|
pub problems: Problems,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
|
pub struct Problems {
|
||||||
|
pub problems: BTreeSet<Problem>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Wall {
|
pub struct Wall {
|
||||||
@@ -59,13 +145,14 @@ pub enum HoldRole {
|
|||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Hold {
|
pub struct Hold {
|
||||||
pub position: HoldPosition,
|
pub position: HoldPosition,
|
||||||
pub image: Option<Image>,
|
pub image: Option<Image>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use leptos::reactive::graph::ReactiveNode;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Wall() -> impl leptos::IntoView {
|
pub fn Wall() -> impl leptos::IntoView {
|
||||||
@@ -125,15 +126,31 @@ pub struct InitialData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
async fn load_initial_data() -> Result<RonCodec<InitialData>, ServerFnError> {
|
#[tracing::instrument(skip_all, err)]
|
||||||
todo!()
|
async fn load_initial_data(wall_id: models::WallId) -> Result<RonCodec<InitialData>, ServerFnError> {
|
||||||
// let state = expect_context::<State>();
|
let db = expect_context::<Arc<redb::Database>>();
|
||||||
|
|
||||||
// let wall = state.persistent.with(|s| s.wall.clone()).await;
|
#[derive(Debug, derive_more::Error, derive_more::Display)]
|
||||||
// Ok(RonCodec::new(InitialData { wall }))
|
enum Error {
|
||||||
|
#[display("Wall not found: {_0:?}")]
|
||||||
|
NotFound(#[error(not(source))] models::WallId),
|
||||||
|
}
|
||||||
|
|
||||||
|
let wall = tokio::task::spawn_blocking(move || -> Result<models::Wall, ServerFnError> {
|
||||||
|
let read_txn = db.begin_read()?;
|
||||||
|
|
||||||
|
let walls_table = read_txn.open_table(crate::server::db::current::TABLE_WALLS)?;
|
||||||
|
let wall = walls_table.get(wall_id)?.ok_or(Error::NotFound(wall_id))?.value();
|
||||||
|
|
||||||
|
Ok(wall)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
Ok(RonCodec::new(InitialData { wall }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
|
#[tracing::instrument(skip_all, err)]
|
||||||
async fn get_random_problem() -> Result<RonCodec<Option<models::Problem>>, ServerFnError> {
|
async fn get_random_problem() -> Result<RonCodec<Option<models::Problem>>, ServerFnError> {
|
||||||
todo!()
|
todo!()
|
||||||
// use rand::seq::IteratorRandom;
|
// use rand::seq::IteratorRandom;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use cli::Cli;
|
|||||||
use config::Config;
|
use config::Config;
|
||||||
use confik::Configuration;
|
use confik::Configuration;
|
||||||
use confik::EnvSource;
|
use confik::EnvSource;
|
||||||
use state::PersistentState;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
@@ -13,10 +12,9 @@ use type_toppings::ResultExt;
|
|||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
mod db;
|
pub mod db;
|
||||||
mod migrations;
|
mod migrations;
|
||||||
pub mod operations;
|
pub mod operations;
|
||||||
pub mod state;
|
|
||||||
|
|
||||||
pub const STATE_FILE: &str = "datastore/private/state.ron";
|
pub const STATE_FILE: &str = "datastore/private/state.ron";
|
||||||
|
|
||||||
@@ -128,5 +126,5 @@ pub enum Error {
|
|||||||
|
|
||||||
Confik(confik::Error),
|
Confik(confik::Error),
|
||||||
|
|
||||||
Database(redb::DatabaseError),
|
Database(redb::Error),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
|
use bincode::Bincode;
|
||||||
use redb::Database;
|
use redb::Database;
|
||||||
use redb::DatabaseError;
|
use redb::TableDefinition;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
mod bincode;
|
mod bincode;
|
||||||
pub mod db_models;
|
|
||||||
|
|
||||||
pub const DB_FILE: &str = "datastore/private/ascend.redb";
|
pub const DB_FILE: &str = "datastore/private/ascend.redb";
|
||||||
|
|
||||||
#[tracing::instrument(err)]
|
#[tracing::instrument(skip_all, err)]
|
||||||
pub fn create() -> Result<Database, DatabaseError> {
|
pub fn create() -> Result<Database, redb::Error> {
|
||||||
let file = PathBuf::from(DB_FILE);
|
let file = PathBuf::from(DB_FILE);
|
||||||
|
|
||||||
// Create parent dirs
|
// Create parent dirs
|
||||||
@@ -19,3 +21,45 @@ pub fn create() -> Result<Database, DatabaseError> {
|
|||||||
let db = Database::create(file)?;
|
let db = Database::create(file)?;
|
||||||
Ok(db)
|
Ok(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn get_version(db: &Database) -> Result<Option<Version>, redb::Error> {
|
||||||
|
let txn = db.begin_read()?;
|
||||||
|
let version = txn.open_table(TABLE_VERSION)?.get(())?.map(|v| v.value());
|
||||||
|
Ok(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const TABLE_VERSION: TableDefinition<(), Bincode<Version>> = TableDefinition::new("version");
|
||||||
|
#[derive(Serialize, Deserialize, Debug, derive_more::Display)]
|
||||||
|
#[display("{version}")]
|
||||||
|
pub struct Version {
|
||||||
|
pub version: u64,
|
||||||
|
}
|
||||||
|
impl Version {
|
||||||
|
pub fn current() -> Version {
|
||||||
|
Version { version: current::VERSION }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use v2 as current;
|
||||||
|
|
||||||
|
pub mod v2 {
|
||||||
|
use crate::models;
|
||||||
|
use crate::server::db::bincode::Bincode;
|
||||||
|
use redb::TableDefinition;
|
||||||
|
|
||||||
|
pub const VERSION: u64 = 2;
|
||||||
|
|
||||||
|
pub const TABLE_ROOT: TableDefinition<(), Bincode<models::v2::Root>> = TableDefinition::new("root");
|
||||||
|
pub const TABLE_WALLS: TableDefinition<Bincode<models::v2::WallId>, Bincode<models::v2::Wall>> = TableDefinition::new("walls");
|
||||||
|
pub const TABLE_PROBLEMS: TableDefinition<Bincode<(models::v2::WallId, models::v2::ProblemId)>, Bincode<models::v2::Problem>> =
|
||||||
|
TableDefinition::new("problems");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod v1 {
|
||||||
|
use crate::models;
|
||||||
|
use crate::server::db::bincode::Bincode;
|
||||||
|
use redb::TableDefinition;
|
||||||
|
|
||||||
|
pub const TABLE_ROOT: TableDefinition<(), Bincode<models::v1::PersistentState>> = TableDefinition::new("root");
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
use super::bincode::Bincode;
|
|
||||||
use redb::TableDefinition;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
pub use v2::Hold;
|
|
||||||
pub use v2::HoldPosition;
|
|
||||||
pub use v2::HoldRole;
|
|
||||||
pub use v2::Image;
|
|
||||||
pub use v2::Method;
|
|
||||||
pub use v2::Problem;
|
|
||||||
pub use v2::ProblemId;
|
|
||||||
pub use v2::Root;
|
|
||||||
pub use v2::TABLE_ROOT;
|
|
||||||
pub use v2::TABLE_WALLS;
|
|
||||||
pub use v2::Wall;
|
|
||||||
pub use v2::WallId;
|
|
||||||
|
|
||||||
pub const TABLE_VERSION: TableDefinition<(), Bincode<Version>> = TableDefinition::new("version");
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Version {
|
|
||||||
pub version: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod v2 {
|
|
||||||
use crate::server::db::bincode::Bincode;
|
|
||||||
use redb::TableDefinition;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
pub const TABLE_ROOT: TableDefinition<(), Bincode<Root>> = TableDefinition::new("root");
|
|
||||||
pub const TABLE_WALLS: TableDefinition<Bincode<WallId>, Bincode<Wall>> = TableDefinition::new("walls");
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Root {
|
|
||||||
pub walls: BTreeSet<WallId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Wall {
|
|
||||||
pub uid: WallId,
|
|
||||||
pub rows: u64,
|
|
||||||
pub cols: u64,
|
|
||||||
pub holds: BTreeMap<HoldPosition, Hold>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
|
||||||
pub struct WallId(pub uuid::Uuid);
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Problem {
|
|
||||||
pub uid: ProblemId,
|
|
||||||
pub name: String,
|
|
||||||
pub set_by: String,
|
|
||||||
pub holds: BTreeMap<HoldPosition, HoldRole>,
|
|
||||||
pub method: Method,
|
|
||||||
pub date_added: chrono::NaiveDate,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
|
||||||
pub struct ProblemId(pub uuid::Uuid);
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub enum Method {
|
|
||||||
FeetFollowHands,
|
|
||||||
Footless,
|
|
||||||
FootlessPlusKickboard,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub enum HoldRole {
|
|
||||||
Start,
|
|
||||||
Normal,
|
|
||||||
Zone,
|
|
||||||
End,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct HoldPosition {
|
|
||||||
pub row: u64,
|
|
||||||
pub col: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Hold {
|
|
||||||
pub position: HoldPosition,
|
|
||||||
pub image: Option<Image>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Image {
|
|
||||||
pub filename: String,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod v1 {
|
|
||||||
use crate::server::db::bincode::Bincode;
|
|
||||||
use crate::server::state::PersistentState;
|
|
||||||
use redb::TableDefinition;
|
|
||||||
|
|
||||||
pub type Root = PersistentState;
|
|
||||||
|
|
||||||
pub const TABLE_ROOT: TableDefinition<(), Bincode<Root>> = TableDefinition::new("root");
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::state::PersistentState;
|
use super::db;
|
||||||
use leptos::prelude::StorageAccess;
|
use crate::models;
|
||||||
use redb::Database;
|
use redb::Database;
|
||||||
use redb::ReadableTable;
|
use redb::ReadableTable;
|
||||||
use redb::ReadableTableMetadata;
|
use redb::ReadableTableMetadata;
|
||||||
@@ -7,34 +7,17 @@ use std::collections::BTreeSet;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use type_toppings::ResultExt;
|
use type_toppings::ResultExt;
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument(skip_all, err)]
|
||||||
pub async fn run_migrations(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn run_migrations(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
migrate_state_file().await?;
|
|
||||||
migrate_from_ron_to_redb(db).await?;
|
migrate_from_ron_to_redb(db).await?;
|
||||||
|
init_at_current_version(db).await?;
|
||||||
migrate_to_v2(db).await?;
|
migrate_to_v2(db).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State file moved to datastore/private
|
|
||||||
#[tracing::instrument(err)]
|
|
||||||
async fn migrate_state_file() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let m = PathBuf::from("state.ron");
|
|
||||||
if m.try_exists().expect_or_report_with(|| format!("Failed to read {}", m.display())) {
|
|
||||||
tracing::warn!("MIGRATING");
|
|
||||||
let p = PathBuf::from(super::STATE_FILE);
|
|
||||||
tokio::fs::create_dir_all(p.parent().unwrap()).await?;
|
|
||||||
tokio::fs::rename(m, &p).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Use redb DB instead of Ron state file
|
/// Use redb DB instead of Ron state file
|
||||||
#[tracing::instrument(err)]
|
#[tracing::instrument(skip_all, err)]
|
||||||
async fn migrate_from_ron_to_redb(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
async fn migrate_from_ron_to_redb(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
use super::db::db_models::TABLE_VERSION;
|
|
||||||
use super::db::db_models::Version;
|
|
||||||
use super::db::db_models::v1;
|
|
||||||
|
|
||||||
let ron_state_file_path = PathBuf::from(super::STATE_FILE);
|
let ron_state_file_path = PathBuf::from(super::STATE_FILE);
|
||||||
|
|
||||||
if ron_state_file_path
|
if ron_state_file_path
|
||||||
@@ -43,27 +26,27 @@ async fn migrate_from_ron_to_redb(db: &Database) -> Result<(), Box<dyn std::erro
|
|||||||
{
|
{
|
||||||
tracing::warn!("MIGRATING");
|
tracing::warn!("MIGRATING");
|
||||||
|
|
||||||
let ron_state: PersistentState = {
|
let ron_state: models::v1::PersistentState = {
|
||||||
let content = tokio::fs::read_to_string(&ron_state_file_path).await?;
|
let content = tokio::fs::read_to_string(&ron_state_file_path).await?;
|
||||||
ron::from_str(&content)?
|
ron::from_str(&content)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let write_txn = db.begin_write()?;
|
let write_txn = db.begin_write()?;
|
||||||
{
|
{
|
||||||
let mut version_table = write_txn.open_table(TABLE_VERSION)?;
|
let mut version_table = write_txn.open_table(db::TABLE_VERSION)?;
|
||||||
assert!(version_table.is_empty()?);
|
assert!(version_table.is_empty()?);
|
||||||
version_table.insert((), Version { version: 1 });
|
version_table.insert((), db::Version { version: 1 })?;
|
||||||
|
|
||||||
let mut root_table = write_txn.open_table(v1::TABLE_ROOT)?;
|
let mut root_table = write_txn.open_table(db::v1::TABLE_ROOT)?;
|
||||||
assert!(root_table.is_empty()?);
|
assert!(root_table.is_empty()?);
|
||||||
|
|
||||||
let root = v1::Root {
|
let persistent_state = models::v1::PersistentState {
|
||||||
version: ron_state.version,
|
version: ron_state.version,
|
||||||
wall: ron_state.wall,
|
wall: ron_state.wall,
|
||||||
problems: ron_state.problems,
|
problems: ron_state.problems,
|
||||||
};
|
};
|
||||||
|
|
||||||
root_table.insert((), root)?;
|
root_table.insert((), persistent_state)?;
|
||||||
}
|
}
|
||||||
write_txn.commit()?;
|
write_txn.commit()?;
|
||||||
|
|
||||||
@@ -74,63 +57,103 @@ async fn migrate_from_ron_to_redb(db: &Database) -> Result<(), Box<dyn std::erro
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(err)]
|
// TODO: Move out, is not really a migration
|
||||||
|
#[tracing::instrument(skip_all, err)]
|
||||||
|
async fn init_at_current_version(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let txn = db.begin_write()?;
|
||||||
|
{
|
||||||
|
let mut version_table = txn.open_table(db::TABLE_VERSION)?;
|
||||||
|
let is_missing_version = version_table.get(())?.is_none();
|
||||||
|
if is_missing_version {
|
||||||
|
let v = db::Version::current();
|
||||||
|
tracing::warn!("INITIALIZING DATABASE AT VERSION {v}");
|
||||||
|
version_table.insert((), v)?;
|
||||||
|
|
||||||
|
// Root table
|
||||||
|
{
|
||||||
|
let mut table = txn.open_table(db::current::TABLE_ROOT)?;
|
||||||
|
assert!(table.is_empty()?);
|
||||||
|
table.insert((), models::Root { walls: BTreeSet::new() })?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walls table
|
||||||
|
{
|
||||||
|
// Opening the table creates the table
|
||||||
|
let table = txn.open_table(db::current::TABLE_WALLS)?;
|
||||||
|
assert!(table.is_empty()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Problems table
|
||||||
|
{
|
||||||
|
// Opening the table creates the table
|
||||||
|
let table = txn.open_table(db::current::TABLE_PROBLEMS)?;
|
||||||
|
assert!(table.is_empty()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txn.commit()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, err)]
|
||||||
async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
use super::db::db_models::TABLE_VERSION;
|
use super::db;
|
||||||
use super::db::db_models::Version;
|
|
||||||
use super::db::db_models::v1;
|
|
||||||
use super::db::db_models::v2;
|
|
||||||
|
|
||||||
let txn = db.begin_write()?;
|
let txn = db.begin_write()?;
|
||||||
{
|
{
|
||||||
let mut version_table = txn.open_table(TABLE_VERSION)?;
|
let mut version_table = txn.open_table(db::TABLE_VERSION)?;
|
||||||
let version = version_table.get(())?.unwrap().value().version;
|
let version = version_table.get(())?.unwrap().value().version;
|
||||||
if version == 1 {
|
if version == 1 {
|
||||||
tracing::warn!("MIGRATING");
|
tracing::warn!("MIGRATING");
|
||||||
version_table.insert((), Version { version: 2 })?;
|
version_table.insert((), db::Version { version: 2 })?;
|
||||||
|
|
||||||
let root_table_v1 = txn.open_table(v1::TABLE_ROOT)?;
|
let root_table_v1 = txn.open_table(db::v1::TABLE_ROOT)?;
|
||||||
let root_v1 = root_table_v1.get(())?.unwrap().value();
|
let root_v1 = root_table_v1.get(())?.unwrap().value();
|
||||||
txn.delete_table(v1::TABLE_ROOT)?;
|
txn.delete_table(db::v1::TABLE_ROOT)?;
|
||||||
|
|
||||||
let v1::Root { version: _, wall, problems } = root_v1;
|
let models::v1::PersistentState { version: _, wall, problems } = root_v1;
|
||||||
|
|
||||||
|
// we'll reimport them instead of a lossy conversion.
|
||||||
|
drop(problems);
|
||||||
|
|
||||||
let mut walls = BTreeSet::new();
|
let mut walls = BTreeSet::new();
|
||||||
let wall_uid = v2::WallId(uuid::Uuid::new_v4());
|
let wall_uid = models::v2::WallId(uuid::Uuid::new_v4());
|
||||||
let holds = wall
|
let holds = wall
|
||||||
.holds
|
.holds
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(hold_position, hold)| {
|
.map(|(hold_position, hold)| {
|
||||||
(
|
(
|
||||||
v2::HoldPosition {
|
models::v1::HoldPosition {
|
||||||
row: hold_position.row,
|
row: hold_position.row,
|
||||||
col: hold_position.col,
|
col: hold_position.col,
|
||||||
},
|
},
|
||||||
v2::Hold {
|
models::v1::Hold {
|
||||||
position: v2::HoldPosition {
|
position: models::v1::HoldPosition {
|
||||||
row: hold.position.row,
|
row: hold.position.row,
|
||||||
col: hold.position.col,
|
col: hold.position.col,
|
||||||
},
|
},
|
||||||
image: hold.image.map(|i| v2::Image { filename: i.filename }),
|
image: hold.image.map(|i| models::v1::Image { filename: i.filename }),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let wall_v2 = v2::Wall {
|
let wall_v2 = models::v2::Wall {
|
||||||
uid: wall_uid,
|
uid: wall_uid,
|
||||||
rows: wall.rows,
|
rows: wall.rows,
|
||||||
cols: wall.cols,
|
cols: wall.cols,
|
||||||
holds,
|
holds,
|
||||||
|
problems: BTreeSet::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
walls.insert(wall_v2.uid);
|
walls.insert(wall_v2.uid);
|
||||||
let root_v2 = v2::Root { walls };
|
let root_v2 = models::v2::Root { walls };
|
||||||
|
|
||||||
let mut root_table_v2 = txn.open_table(v2::TABLE_ROOT)?;
|
let mut root_table_v2 = txn.open_table(db::v2::TABLE_ROOT)?;
|
||||||
root_table_v2.insert((), root_v2)?;
|
root_table_v2.insert((), root_v2)?;
|
||||||
|
|
||||||
let mut walls_table = txn.open_table(v2::TABLE_WALLS)?;
|
let mut walls_table = txn.open_table(db::v2::TABLE_WALLS)?;
|
||||||
walls_table.insert(wall_v2.uid, wall_v2)?;
|
walls_table.insert(wall_v2.uid, wall_v2)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ use crate::models;
|
|||||||
use crate::models::HoldPosition;
|
use crate::models::HoldPosition;
|
||||||
use crate::models::HoldRole;
|
use crate::models::HoldRole;
|
||||||
use crate::server::config::Config;
|
use crate::server::config::Config;
|
||||||
|
use crate::server::db;
|
||||||
use redb::Database;
|
use redb::Database;
|
||||||
|
use redb::ReadableTable;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument(skip_all)]
|
||||||
pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: &Database) -> Result<(), Error> {
|
pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Arc<Database>, wall_id: models::WallId) -> Result<(), Error> {
|
||||||
use moonboard_parser::mini_moonboard;
|
use moonboard_parser::mini_moonboard;
|
||||||
|
|
||||||
let mut problems = Vec::new();
|
let mut problems = Vec::new();
|
||||||
@@ -21,9 +24,9 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: &Databas
|
|||||||
let set_by = "mini-mb-2020-parser";
|
let set_by = "mini-mb-2020-parser";
|
||||||
|
|
||||||
let mini_moonboard = mini_moonboard::parse(file_path.as_std_path()).await?;
|
let mini_moonboard = mini_moonboard::parse(file_path.as_std_path()).await?;
|
||||||
for problem in mini_moonboard.problems {
|
for mini_mb_problem in mini_moonboard.problems {
|
||||||
let mut holds = BTreeMap::<HoldPosition, HoldRole>::new();
|
let mut holds = BTreeMap::<HoldPosition, HoldRole>::new();
|
||||||
for mv in problem.moves {
|
for mv in mini_mb_problem.moves {
|
||||||
let row = mv.description.row();
|
let row = mv.description.row();
|
||||||
let col = mv.description.column();
|
let col = mv.description.column();
|
||||||
let hold_position = HoldPosition { row, col };
|
let hold_position = HoldPosition { row, col };
|
||||||
@@ -37,29 +40,47 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: &Databas
|
|||||||
holds.insert(hold_position, role);
|
holds.insert(hold_position, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:
|
let name = mini_mb_problem.name;
|
||||||
|
|
||||||
// let name = problem.name;
|
let method = match mini_mb_problem.method {
|
||||||
|
mini_moonboard::Method::FeetFollowHands => models::Method::FeetFollowHands,
|
||||||
|
mini_moonboard::Method::Footless => models::Method::Footless,
|
||||||
|
mini_moonboard::Method::FootlessPlusKickboard => models::Method::FootlessPlusKickboard,
|
||||||
|
};
|
||||||
|
|
||||||
// let method = match problem.method {
|
let problem_id = models::ProblemId::new();
|
||||||
// mini_moonboard::Method::FeetFollowHands => models::Method::FeetFollowHands,
|
|
||||||
// mini_moonboard::Method::Footless => models::Method::Footless,
|
|
||||||
// mini_moonboard::Method::FootlessPlusKickboard => models::Method::FootlessPlusKickboard,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let problem = models::Problem::new(name, set_by.to_owned(), holds, method);
|
let problem = models::Problem {
|
||||||
// problems.push(problem);
|
uid: problem_id,
|
||||||
|
name,
|
||||||
let problem = models::Problem { holds };
|
set_by: set_by.to_owned(),
|
||||||
|
holds,
|
||||||
|
method,
|
||||||
|
date_added: chrono::Utc::now(),
|
||||||
|
};
|
||||||
problems.push(problem);
|
problems.push(problem);
|
||||||
}
|
}
|
||||||
|
|
||||||
state
|
tokio::task::spawn_blocking(move || -> Result<(), redb::Error> {
|
||||||
.persistent
|
let write_txn = db.begin_write()?;
|
||||||
.update(|s| {
|
{
|
||||||
s.problems.problems.extend(problems);
|
let mut walls_table = write_txn.open_table(db::current::TABLE_WALLS)?;
|
||||||
|
let mut problems_table = write_txn.open_table(db::current::TABLE_PROBLEMS)?;
|
||||||
|
|
||||||
|
let mut wall = walls_table.get(wall_id)?.unwrap().value();
|
||||||
|
wall.problems.extend(problems.iter().map(|p| p.uid));
|
||||||
|
walls_table.insert(wall_id, wall)?;
|
||||||
|
|
||||||
|
for problem in problems {
|
||||||
|
let key = (wall_id, problem.uid);
|
||||||
|
problems_table.insert(key, problem)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_txn.commit()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
.await?;
|
.await??;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -67,4 +88,6 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: &Databas
|
|||||||
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Parser(moonboard_parser::Error),
|
Parser(moonboard_parser::Error),
|
||||||
|
Redb(redb::Error),
|
||||||
|
Tokio(tokio::task::JoinError),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
//! Server state
|
|
||||||
|
|
||||||
const STATE_VERSION: u64 = 1;
|
|
||||||
|
|
||||||
use crate::models;
|
|
||||||
use crate::models::Wall;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
use smart_default::SmartDefault;
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, SmartDefault)]
|
|
||||||
pub struct PersistentState {
|
|
||||||
/// State schema version
|
|
||||||
#[default(STATE_VERSION)]
|
|
||||||
pub version: u64,
|
|
||||||
|
|
||||||
pub wall: Wall,
|
|
||||||
pub problems: Problems,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
|
||||||
pub struct Problems {
|
|
||||||
pub problems: BTreeSet<models::Problem>,
|
|
||||||
}
|
|
||||||
11
todo.md
Normal file
11
todo.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
- save images with a uuid
|
||||||
|
- downscale images
|
||||||
|
- associate routes with wall
|
||||||
|
- group routes by pattern (pattern family has shift/mirror variations)
|
||||||
|
- generate pattern families of variations when importing problems
|
||||||
|
- implement pattern challenge (start an "adventure mode" based on a pattern family)
|
||||||
|
- Record problem success (enum: flash, send, no-send)
|
||||||
|
- implement routes page to show all routes for a given wall
|
||||||
|
- implement favorite routes feature
|
||||||
|
- use wall id in URL.
|
||||||
|
- decide on routes vs problems terminology
|
||||||
Reference in New Issue
Block a user