aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-01-20 00:50:20 +0100
committermetamuffin <metamuffin@disroot.org>2024-01-20 00:50:20 +0100
commit46c251655db7bb3d9aa814b1a5dde85336b0b9b1 (patch)
treeab0696f2c92e8854ce6aa0737877cc15184bd8b6
parent1c37d32a0985ff7390313833345b9299f9f0b196 (diff)
downloadjellything-46c251655db7bb3d9aa814b1a5dde85336b0b9b1.tar
jellything-46c251655db7bb3d9aa814b1a5dde85336b0b9b1.tar.bz2
jellything-46c251655db7bb3d9aa814b1a5dde85336b0b9b1.tar.zst
replace sled with redb
-rw-r--r--Cargo.lock204
-rw-r--r--base/Cargo.toml7
-rw-r--r--base/src/database.rs202
-rw-r--r--common/src/lib.rs32
-rw-r--r--common/src/stream.rs3
-rw-r--r--common/src/user.rs13
-rw-r--r--import/src/infojson.rs6
-rw-r--r--import/src/lib.rs119
-rw-r--r--import/src/tmdb.rs6
-rw-r--r--server/src/main.rs31
-rw-r--r--server/src/routes/api/mod.rs12
-rw-r--r--server/src/routes/mod.rs4
-rw-r--r--server/src/routes/stream.rs10
-rw-r--r--server/src/routes/ui/account/mod.rs71
-rw-r--r--server/src/routes/ui/account/session/guard.rs18
-rw-r--r--server/src/routes/ui/account/settings.rs53
-rw-r--r--server/src/routes/ui/admin/mod.rs45
-rw-r--r--server/src/routes/ui/admin/user.rs70
-rw-r--r--server/src/routes/ui/assets.rs25
-rw-r--r--server/src/routes/ui/browser.rs43
-rw-r--r--server/src/routes/ui/error.rs40
-rw-r--r--server/src/routes/ui/home.rs62
-rw-r--r--server/src/routes/ui/node.rs35
-rw-r--r--server/src/routes/ui/player.rs10
-rw-r--r--server/src/routes/userdata.rs83
-rw-r--r--tool/src/migrate.rs374
26 files changed, 1025 insertions, 553 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3ba3efb..8d9ff8d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -159,7 +159,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -191,7 +191,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -213,7 +213,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -224,7 +224,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -308,15 +308,6 @@ checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
[[package]]
name = "bincode"
-version = "1.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "bincode"
version = "2.0.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95"
@@ -503,7 +494,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -694,7 +685,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -713,7 +704,7 @@ name = "ebml_derive"
version = "0.1.0"
dependencies = [
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -861,16 +852,6 @@ dependencies = [
]
[[package]]
-name = "fs2"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
-dependencies = [
- "libc",
- "winapi",
-]
-
-[[package]]
name = "futures"
version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -926,7 +907,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -960,15 +941,6 @@ dependencies = [
]
[[package]]
-name = "fxhash"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
-dependencies = [
- "byteorder",
-]
-
-[[package]]
name = "generator"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1305,7 +1277,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -1346,17 +1318,17 @@ version = "0.1.0"
dependencies = [
"anyhow",
"base64",
- "bincode 2.0.0-rc.3",
+ "bincode",
"jellyclient",
"jellycommon",
"log",
"rand",
+ "redb",
"serde",
+ "serde_json",
"serde_yaml",
"sha2",
- "sled",
"tokio",
- "typed-sled",
]
[[package]]
@@ -1376,7 +1348,7 @@ dependencies = [
name = "jellycommon"
version = "0.1.0"
dependencies = [
- "bincode 2.0.0-rc.3",
+ "bincode",
"chrono",
"rocket",
"serde",
@@ -1388,7 +1360,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-recursion",
- "bincode 2.0.0-rc.3",
+ "bincode",
"futures",
"jellybase",
"jellyclient",
@@ -1419,7 +1391,7 @@ name = "jellyremuxer"
version = "0.1.0"
dependencies = [
"anyhow",
- "bincode 2.0.0-rc.3",
+ "bincode",
"jellybase",
"jellycommon",
"jellymatroska",
@@ -1452,7 +1424,7 @@ dependencies = [
"argon2",
"async-recursion",
"base64",
- "bincode 2.0.0-rc.3",
+ "bincode",
"chrono",
"env_logger",
"futures",
@@ -1479,7 +1451,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"base64",
- "bincode 2.0.0-rc.3",
+ "bincode",
"clap",
"env_logger",
"indicatif",
@@ -1671,7 +1643,7 @@ checksum = "9ab6ee21fd1855134cacf2f41afdf45f1bc456c7d7f6165d763b4647062dd2be"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -1850,7 +1822,7 @@ checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -1944,7 +1916,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -1973,37 +1945,12 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
-dependencies = [
- "instant",
- "lock_api",
- "parking_lot_core 0.8.6",
-]
-
-[[package]]
-name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
- "parking_lot_core 0.9.9",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
-dependencies = [
- "cfg-if",
- "instant",
- "libc",
- "redox_syscall 0.2.16",
- "smallvec",
- "winapi",
+ "parking_lot_core",
]
[[package]]
@@ -2014,7 +1961,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall 0.4.1",
+ "redox_syscall",
"smallvec",
"windows-targets 0.48.5",
]
@@ -2056,7 +2003,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -2066,26 +2013,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
-name = "pin-project"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
-dependencies = [
- "pin-project-internal",
-]
-
-[[package]]
-name = "pin-project-internal"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.41",
-]
-
-[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2148,9 +2075,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
-version = "1.0.70"
+version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
+checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
@@ -2163,7 +2090,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
"version_check",
"yansi",
]
@@ -2185,9 +2112,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
-version = "1.0.33"
+version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@@ -2296,12 +2223,12 @@ dependencies = [
]
[[package]]
-name = "redox_syscall"
-version = "0.2.16"
+name = "redb"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+checksum = "72623e6275cd430215b741f41ebda34db93a13ebde253f908b70871c46afc5ba"
dependencies = [
- "bitflags 1.3.2",
+ "libc",
]
[[package]]
@@ -2330,7 +2257,7 @@ checksum = "2566c4bf6845f2c2e83b27043c3f5dfcd5ba8f2937d6c00dc009bfb51a079dc4"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -2442,7 +2369,7 @@ dependencies = [
"memchr",
"multer",
"num_cpus",
- "parking_lot 0.12.1",
+ "parking_lot",
"pin-project-lite",
"rand",
"ref-cast",
@@ -2472,7 +2399,7 @@ dependencies = [
"proc-macro2",
"quote",
"rocket_http",
- "syn 2.0.41",
+ "syn 2.0.48",
"unicode-xid",
]
@@ -2619,29 +2546,29 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.193"
+version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.193"
+version = "1.0.195"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
name = "serde_json"
-version = "1.0.108"
+version = "1.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
+checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
dependencies = [
"itoa",
"ryu",
@@ -2736,22 +2663,6 @@ dependencies = [
]
[[package]]
-name = "sled"
-version = "0.34.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
-dependencies = [
- "crc32fast",
- "crossbeam-epoch",
- "crossbeam-utils",
- "fs2",
- "fxhash",
- "libc",
- "log",
- "parking_lot 0.11.2",
-]
-
-[[package]]
name = "smallvec"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2829,9 +2740,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.41"
+version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
@@ -2886,7 +2797,7 @@ checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
dependencies = [
"cfg-if",
"fastrand",
- "redox_syscall 0.4.1",
+ "redox_syscall",
"rustix",
"windows-sys 0.48.0",
]
@@ -2917,7 +2828,7 @@ checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -2996,7 +2907,7 @@ dependencies = [
"libc",
"mio",
"num_cpus",
- "parking_lot 0.12.1",
+ "parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2 0.5.5",
@@ -3012,7 +2923,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -3118,7 +3029,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
]
[[package]]
@@ -3167,19 +3078,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
-name = "typed-sled"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1060f05a4450ec5b758da60951b04f225a93a62079316630e76cf25c4034500d"
-dependencies = [
- "bincode 1.3.3",
- "pin-project",
- "serde",
- "sled",
- "thiserror",
-]
-
-[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3370,7 +3268,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
"wasm-bindgen-shared",
]
@@ -3404,7 +3302,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.41",
+ "syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
diff --git a/base/Cargo.toml b/base/Cargo.toml
index ec5cbe7..36b93bc 100644
--- a/base/Cargo.toml
+++ b/base/Cargo.toml
@@ -14,6 +14,9 @@ base64 = "0.21.5"
tokio = { workspace = true }
anyhow = "1.0.75"
bincode = "2.0.0-rc.3"
-sled = "0.34.7"
-typed-sled = "0.2.3"
rand = "0.8.5"
+redb = "1.5.0"
+serde_json = "1.0.111"
+
+[features]
+db_json = []
diff --git a/base/src/database.rs b/base/src/database.rs
index 3f81cca..2a57937 100644
--- a/base/src/database.rs
+++ b/base/src/database.rs
@@ -3,43 +3,193 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
-use anyhow::Context;
+use bincode::{Decode, Encode};
use jellycommon::{
user::{NodeUserData, User},
Node,
};
use log::info;
-use std::path::Path;
-use typed_sled::Tree;
+use std::{borrow::Borrow, ops::Deref, path::Path};
-pub use sled;
-pub use typed_sled;
+pub use redb::*;
-pub struct Database {
- pub db: sled::Db,
+pub const T_USER: TableDefinition<&str, Ser<User>> = TableDefinition::new("user");
+pub const T_USER_NODE: TableDefinition<(&str, &str), Ser<NodeUserData>> =
+ TableDefinition::new("user_node");
+pub const T_INVITE: TableDefinition<&str, Ser<()>> = TableDefinition::new("invite");
+pub const T_NODE: TableDefinition<&str, Ser<Node>> = TableDefinition::new("node");
+pub const T_NODE_IMPORT: TableDefinition<&str, Ser<Vec<(Vec<usize>, Node)>>> =
+ TableDefinition::new("node_import");
- pub user: Tree<String, User>,
- pub user_node: Tree<(String, String), NodeUserData>,
- pub invite: Tree<String, ()>,
- pub node: Tree<String, Node>,
-
- pub node_import: Tree<String, Vec<(Vec<usize>, Node)>>,
+pub struct DataAcid {
+ pub inner: redb::Database,
}
-impl Database {
+impl DataAcid {
pub fn open(path: &Path) -> Result<Self, anyhow::Error> {
- info!("opening database… (might take up to O(n) time)");
- let db = sled::open(path).context("opening database")?;
- info!("creating trees");
- let r = Ok(Self {
- user: Tree::open(&db, "user"),
- invite: Tree::open(&db, "invite"),
- node: Tree::open(&db, "node"),
- user_node: Tree::open(&db, "user_node"),
- node_import: Tree::open(&db, "node_import"),
- db,
- });
+ info!("database");
+ let db = redb::Database::create(path)?;
+ let r = Self { inner: db };
+
+ {
+ let txn = r.begin_write()?;
+ drop(txn.open_table(T_INVITE)?);
+ drop(txn.open_table(T_USER)?);
+ drop(txn.open_table(T_USER_NODE)?);
+ drop(txn.open_table(T_NODE)?);
+ drop(txn.open_table(T_NODE_IMPORT)?);
+ txn.commit()?;
+ }
+
info!("ready");
- r
+ Ok(r)
+ }
+}
+
+impl Deref for DataAcid {
+ type Target = Database;
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+pub trait TableExt<Key, KeyRef, Value> {
+ fn get(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>>;
+ fn insert(self, db: &DataAcid, key: KeyRef, value: Value) -> anyhow::Result<()>;
+ fn remove(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>>;
+}
+impl<'a, 'b, 'c, Key, Value, KeyRef> TableExt<Key, KeyRef, Value>
+ for TableDefinition<'a, Key, Ser<Value>>
+where
+ Key: Borrow<<Key as RedbValue>::SelfType<'b>> + redb::RedbKey,
+ Value: bincode::Encode + bincode::Decode + std::fmt::Debug,
+ KeyRef: Borrow<<Key as redb::RedbValue>::SelfType<'c>>,
+{
+ fn get(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>> {
+ let txn = db.inner.begin_read()?;
+ let table = txn.open_table(self)?;
+ let user = table.get(key)?.map(|v| v.value().0);
+ drop(table);
+ Ok(user)
+ }
+ fn insert(self, db: &DataAcid, key: KeyRef, value: Value) -> anyhow::Result<()> {
+ let txn = db.inner.begin_write()?;
+ let mut table = txn.open_table(self)?;
+ table.insert(key, Ser(value))?;
+ drop(table);
+ txn.commit()?;
+ Ok(())
+ }
+ fn remove(self, db: &DataAcid, key: KeyRef) -> anyhow::Result<Option<Value>> {
+ let txn = db.inner.begin_write()?;
+ let mut table = txn.open_table(self)?;
+ let prev = table.remove(key)?.map(|v| v.value().0);
+ drop(table);
+ txn.commit()?;
+ Ok(prev)
+ }
+}
+
+// pub trait TableIterExt<
+// 'a,
+// Key: redb::RedbKey + 'static,
+// Value: redb::RedbValue + 'static,
+// F: FnOnce(&redb::Range<'a, Key, Value>) -> anyhow::Result<T>,
+// T: 'static,
+// >
+// {
+// fn iter(self, db: &'a DataAcid, f: F) -> anyhow::Result<T>;
+// }
+// impl<'a, Key, Value, F, T> TableIterExt<'a, Key, Value, F, T>
+// for TableDefinition<'static, Key, Value>
+// where
+// Key: redb::RedbKey,
+// Value: redb::RedbValue,
+// F: FnOnce(&redb::Range<'a, Key, Value>) -> anyhow::Result<T>,
+// T: 'static,
+// {
+// fn iter(self, db: &DataAcid, f: F) -> anyhow::Result<T> {
+// let txn = db.begin_read()?;
+// let table = txn.open_table(self)?;
+// let iter = table.iter()?;
+// let ret = f(&iter)?;
+// drop(iter);
+// drop(table);
+// drop(txn);
+// Ok(ret)
+// }
+// }
+
+#[derive(Debug)]
+#[cfg(not(feature = "db_json"))]
+pub struct Ser<T>(pub T);
+#[cfg(not(feature = "db_json"))]
+impl<T: Encode + Decode + std::fmt::Debug> RedbValue for Ser<T> {
+ type SelfType<'a> = Ser<T>
+ where
+ Self: 'a;
+ type AsBytes<'a> = Vec<u8>
+ where
+ Self: 'a;
+
+ fn fixed_width() -> Option<usize> {
+ None
+ }
+
+ fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
+ where
+ Self: 'a,
+ {
+ Ser(bincode::decode_from_slice(data, bincode::config::legacy())
+ .unwrap()
+ .0)
+ }
+
+ fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
+ where
+ Self: 'a,
+ Self: 'b,
+ {
+ bincode::encode_to_vec(&value.0, bincode::config::legacy()).unwrap()
+ }
+
+ fn type_name() -> redb::TypeName {
+ TypeName::new("bincode")
+ }
+}
+
+#[derive(Debug)]
+#[cfg(feature = "db_json")]
+pub struct Ser<T>(pub T);
+#[cfg(feature = "db_json")]
+impl<T: Serialize + for<'a> Deserialize<'a> + std::fmt::Debug> RedbValue for Ser<T> {
+ type SelfType<'a> = Ser<T>
+ where
+ Self: 'a;
+ type AsBytes<'a> = Vec<u8>
+ where
+ Self: 'a;
+
+ fn fixed_width() -> Option<usize> {
+ None
+ }
+
+ fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
+ where
+ Self: 'a,
+ {
+ Ser(serde_json::from_slice(data).unwrap())
+ }
+
+ fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
+ where
+ Self: 'a,
+ Self: 'b,
+ {
+ serde_json::to_vec(&value.0).unwrap()
+ }
+
+ fn type_name() -> redb::TypeName {
+ TypeName::new("json")
}
}
diff --git a/common/src/lib.rs b/common/src/lib.rs
index c126e65..a58dc48 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -14,14 +14,13 @@ pub mod user;
pub use chrono;
use bincode::{Decode, Encode};
-use chrono::{DateTime, Utc};
#[cfg(feature = "rocket")]
use rocket::{FromFormField, UriDisplayQuery};
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, path::PathBuf};
-#[derive(Debug, Clone, Deserialize, Serialize, Default)]
+#[derive(Debug, Clone, Deserialize, Serialize, Default, Encode, Decode)]
pub struct Node {
#[serde(default)]
pub public: NodePublic,
@@ -30,7 +29,7 @@ pub struct Node {
}
#[rustfmt::skip]
-#[derive(Debug, Clone, Deserialize, Serialize, Default)]
+#[derive(Debug, Clone, Deserialize, Serialize, Default, Encode,Decode)]
pub struct NodePrivate {
#[serde(default)] pub id: Option<String>,
#[serde(default)] pub poster: Option<AssetLocation>,
@@ -39,7 +38,7 @@ pub struct NodePrivate {
}
#[rustfmt::skip]
-#[derive(Debug, Clone, Deserialize, Serialize, Default)]
+#[derive(Debug, Clone, Deserialize, Serialize, Default, Encode, Decode)]
pub struct NodePublic {
#[serde(default)] pub kind: Option<NodeKind>,
#[serde(default)] pub title: Option<String>,
@@ -48,20 +47,20 @@ pub struct NodePublic {
#[serde(default)] pub children: Vec<String>,
#[serde(default)] pub tagline: Option<String>,
#[serde(default)] pub description: Option<String>,
- #[serde(default)] pub release_date: Option<DateTime<Utc>>,
+ #[serde(default)] pub release_date: Option<i64>,
#[serde(default)] pub index: Option<usize>,
#[serde(default)] pub media: Option<MediaInfo>,
#[serde(default)] pub ratings: BTreeMap<Rating, f64>,
#[serde(default)] pub federated: Option<String>,
}
-#[derive(Debug, Clone, Deserialize, Serialize, Default)]
+#[derive(Debug, Clone, Deserialize, Serialize, Default, Encode, Decode)]
pub struct ImportOptions {
pub id: String,
pub sources: Vec<ImportSource>,
}
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize, Encode, Decode)]
#[serde(rename_all = "snake_case")]
pub enum ImportSource {
Override(Node),
@@ -85,7 +84,7 @@ pub enum ImportSource {
},
}
-#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq)]
+#[derive(Debug, Clone, Deserialize, Serialize, Hash, PartialEq, Eq, Encode, Decode)]
#[serde(rename_all = "snake_case")]
pub enum AssetLocation {
Cache(PathBuf),
@@ -96,7 +95,7 @@ pub enum AssetLocation {
}
#[rustfmt::skip]
-#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
+#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default, Encode,Decode)]
#[cfg_attr(feature = "rocket", derive(FromFormField, UriDisplayQuery))]
#[serde(rename_all = "snake_case")]
pub enum NodeKind {
@@ -110,18 +109,13 @@ pub enum NodeKind {
#[cfg_attr(feature = "rocket", field(value = "episode"))] Episode,
}
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize, Encode, Decode)]
#[serde(rename_all = "snake_case")]
pub enum TrackSource {
Local(LocalTrack),
Remote(usize),
}
-pub enum PublicMediaSource {
- Local,
- Remote(String),
-}
-
pub type TrackID = usize;
#[derive(Debug, Clone, Deserialize, Serialize, Encode, Decode, Hash)]
@@ -131,7 +125,7 @@ pub struct LocalTrack {
pub codec_private: Option<Vec<u8>>,
}
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize, Encode, Decode)]
pub struct MediaInfo {
pub duration: f64, // in seconds
pub tracks: Vec<SourceTrack>,
@@ -157,7 +151,9 @@ pub struct SourceTrack {
pub federated: Vec<String>,
}
-#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(
+ Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode,
+)]
#[serde(rename_all = "snake_case")]
pub enum Rating {
Imdb,
@@ -186,7 +182,7 @@ pub enum SourceTrackKind {
}
#[rustfmt::skip]
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, Encode, Decode)]
#[cfg_attr(feature = "rocket", derive(FromFormField, UriDisplayQuery))]
pub enum AssetRole {
#[cfg_attr(feature = "rocket", field(value = "poster"))] Poster,
diff --git a/common/src/stream.rs b/common/src/stream.rs
index 4aa51d3..7b56d0e 100644
--- a/common/src/stream.rs
+++ b/common/src/stream.rs
@@ -1,3 +1,4 @@
+use bincode::{Decode, Encode};
/*
This file is part of jellything (https://codeberg.org/metamuffin/jellything)
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
@@ -18,7 +19,7 @@ pub struct StreamSpec {
}
#[rustfmt::skip]
-#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, Encode, Decode)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "rocket", derive(FromFormField, UriDisplayQuery))]
pub enum StreamFormat {
diff --git a/common/src/user.rs b/common/src/user.rs
index 5f012dd..b3360c2 100644
--- a/common/src/user.rs
+++ b/common/src/user.rs
@@ -4,6 +4,7 @@
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
use crate::{stream::StreamFormat, user};
+use bincode::{Decode, Encode};
#[cfg(feature = "rocket")]
use rocket::{FromFormField, UriDisplayQuery};
use serde::{Deserialize, Serialize};
@@ -12,7 +13,7 @@ use std::{
fmt::Display,
};
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct User {
pub name: String,
pub display_name: String,
@@ -22,12 +23,12 @@ pub struct User {
pub permissions: PermissionSet,
}
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct NodeUserData {
pub watched: WatchedState,
}
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Encode, Decode)]
#[serde(rename_all = "snake_case")]
pub enum WatchedState {
None,
@@ -44,7 +45,7 @@ pub struct CreateSessionParams {
pub drop_permissions: Option<HashSet<UserPermission>>,
}
-#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Encode, Decode)]
#[cfg_attr(feature = "rocket", derive(FromFormField, UriDisplayQuery))]
#[serde(rename_all = "snake_case")]
pub enum Theme {
@@ -61,10 +62,10 @@ impl Theme {
];
}
-#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+#[derive(Debug, Clone, Serialize, Deserialize, Default, Encode, Decode)]
pub struct PermissionSet(pub HashMap<UserPermission, bool>);
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Encode, Decode)]
#[serde(rename_all = "snake_case")]
pub enum UserPermission {
Admin,
diff --git a/import/src/infojson.rs b/import/src/infojson.rs
index c18c19d..e6b001f 100644
--- a/import/src/infojson.rs
+++ b/import/src/infojson.rs
@@ -5,7 +5,7 @@
*/
use anyhow::Context;
-use jellycommon::chrono::{format::Parsed, DateTime, Utc};
+use jellycommon::chrono::{format::Parsed, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -123,7 +123,7 @@ pub struct YHeatmapSample {
pub value: f64,
}
-pub fn parse_upload_date(d: &str) -> anyhow::Result<DateTime<Utc>> {
+pub fn parse_upload_date(d: &str) -> anyhow::Result<i64> {
let (year, month, day) = (&d[0..4], &d[4..6], &d[6..8]);
let (year, month, day) = (
year.parse().context("parsing year")?,
@@ -139,5 +139,5 @@ pub fn parse_upload_date(d: &str) -> anyhow::Result<DateTime<Utc>> {
p.hour_mod_12 = Some(0);
p.minute = Some(0);
p.second = Some(0);
- Ok(p.to_datetime_with_timezone(&Utc)?)
+ Ok(p.to_datetime_with_timezone(&Utc)?.timestamp_millis())
}
diff --git a/import/src/lib.rs b/import/src/lib.rs
index 692cf7f..a4d1611 100644
--- a/import/src/lib.rs
+++ b/import/src/lib.rs
@@ -13,7 +13,7 @@ use async_recursion::async_recursion;
use futures::{executor::block_on, stream::FuturesUnordered, StreamExt};
use jellybase::{
cache::{async_cache_file, cache_memory},
- database::Database,
+ database::{DataAcid, ReadableTable, Ser, T_NODE, T_NODE_IMPORT},
federation::Federation,
AssetLocationExt, CONF,
};
@@ -42,32 +42,54 @@ use tokio::{io::AsyncWriteExt, sync::Semaphore, task::spawn_blocking};
static IMPORT_SEM: LazyLock<Semaphore> = LazyLock::new(|| Semaphore::new(1));
-pub async fn import(db: &Database, fed: &Federation) -> anyhow::Result<()> {
+pub async fn import(db: &DataAcid, fed: &Federation) -> anyhow::Result<()> {
let permit = IMPORT_SEM.try_acquire()?;
- if !db.node_import.is_empty() {
- info!("clearing temporary node tree from an aborted last import...");
- db.node_import.clear()?;
+
+ {
+ let txn = db.inner.begin_write()?;
+ let mut table = txn.open_table(T_NODE_IMPORT)?;
+ if !table.is_empty()? {
+ info!("clearing temporary node tree from an aborted last import...");
+ table.drain::<&str>(..)?;
+ }
+ drop(table);
+ txn.commit()?;
}
info!("loading sources...");
import_path(CONF.library_path.clone(), vec![], db, fed)
.await
.context("indexing")?;
info!("removing old nodes...");
- db.node.clear()?;
+ {
+ let txn = db.inner.begin_write()?;
+ let mut table = txn.open_table(T_NODE)?;
+ table.drain::<&str>(..)?;
+ drop(table);
+ txn.commit()?;
+ }
info!("merging nodes...");
merge_nodes(db).context("merging nodes")?;
info!("generating paths...");
generate_node_paths(db).context("generating paths")?;
info!("clearing temporary node tree...");
- db.node_import.clear()?;
+ {
+ let txn = db.inner.begin_write()?;
+ let mut table = txn.open_table(T_NODE_IMPORT)?;
+ table.drain::<&str>(..)?;
+ drop(table);
+ txn.commit()?;
+ }
info!("import completed");
drop(permit);
Ok(())
}
-pub fn merge_nodes(db: &Database) -> anyhow::Result<()> {
- for r in db.node_import.iter() {
- let (id, mut nodes) = r?;
+pub fn merge_nodes(db: &DataAcid) -> anyhow::Result<()> {
+ let txn_read = db.inner.begin_read()?;
+ let t_node_import = txn_read.open_table(T_NODE_IMPORT)?;
+ for r in t_node_import.iter()? {
+ let (id, nodes) = r?;
+ let mut nodes = nodes.value().0;
nodes.sort_by(|(x, _), (y, _)| compare_index_path(x, y));
@@ -77,27 +99,36 @@ pub fn merge_nodes(db: &Database) -> anyhow::Result<()> {
.reduce(|x, y| merge_node(x, y))
.unwrap();
- node.public.id = Some(id.clone());
+ node.public.id = Some(id.value().to_owned());
node.public.path = vec![]; // will be reconstructed in the next pass
- db.node.insert(&id, &node)?;
+ {
+ let txn_write = db.inner.begin_write()?;
+ let mut t_node = txn_write.open_table(T_NODE)?;
+ t_node.insert(id.value(), Ser(node))?;
+ drop(t_node);
+ txn_write.commit()?;
+ }
}
Ok(())
}
-pub fn generate_node_paths(db: &Database) -> anyhow::Result<()> {
- fn traverse(db: &Database, c: String, mut path: Vec<String>) -> anyhow::Result<()> {
- let node = db
- .node
- .update_and_fetch(&c, |mut nc| {
- if let Some(nc) = &mut nc {
- if nc.public.path.is_empty() {
- nc.public.path = path.clone();
- }
- }
- nc
- })?
- .ok_or(anyhow!("node {c:?} missing"))?;
+pub fn generate_node_paths(db: &DataAcid) -> anyhow::Result<()> {
+ fn traverse(db: &DataAcid, c: String, mut path: Vec<String>) -> anyhow::Result<()> {
+ let node = {
+ let txn = db.inner.begin_write()?;
+ let table = txn.open_table(T_NODE)?;
+
+ let mut node = table.get(&*c)?.ok_or(anyhow!("your mum"))?.value().0;
+
+ if node.public.path.is_empty() {
+ node.public.path = path.clone();
+ }
+
+ drop(table);
+ txn.commit()?;
+ node
+ };
path.push(c);
for c in node.public.children {
@@ -126,7 +157,7 @@ fn compare_index_path(x: &[usize], y: &[usize]) -> Ordering {
pub async fn import_path(
path: PathBuf,
index_path: Vec<usize>,
- db: &Database,
+ db: &DataAcid,
fed: &Federation,
) -> anyhow::Result<()> {
if path.is_dir() {
@@ -190,15 +221,19 @@ async fn process_source(
s: ImportSource,
path: &Path,
index_path: &[usize],
- db: &Database,
+ db: &DataAcid,
fed: &Federation,
) -> anyhow::Result<()> {
- let insert_node = move |id: &String, n: Node| -> anyhow::Result<()> {
- db.node_import.fetch_and_update(id, |l| {
- let mut l = l.unwrap_or_default();
- l.push((index_path.to_vec(), n.clone()));
- Some(l)
- })?;
+ let insert_node = move |id: &str, n: Node| -> anyhow::Result<()> {
+ let txn = db.inner.begin_write()?;
+ let mut table = txn.open_table(T_NODE_IMPORT)?;
+
+ let mut node = table.get(id)?.map(|a| a.value().0).unwrap_or_default();
+ node.push((index_path.to_vec(), n.clone()));
+ table.insert(id, Ser(node))?;
+
+ drop(table);
+ txn.commit()?;
Ok(())
};
match s {
@@ -497,16 +532,20 @@ static SEM_REMOTE_IMPORT: Semaphore = Semaphore::const_new(16);
async fn import_remote(
id: String,
host: &str,
- db: &Database,
+ db: &DataAcid,
session: &Arc<Session>,
index_path: &[usize],
) -> anyhow::Result<()> {
- let insert_node = move |id: &String, n: Node| -> anyhow::Result<()> {
- db.node_import.fetch_and_update(id, |l| {
- let mut l = l.unwrap_or_default();
- l.push((index_path.to_vec(), n.clone()));
- Some(l)
- })?;
+ let insert_node = move |id: &str, n: Node| -> anyhow::Result<()> {
+ let txn = db.inner.begin_write()?;
+ let mut table = txn.open_table(T_NODE_IMPORT)?;
+
+ let mut node = table.get(id)?.map(|a| a.value().0).unwrap_or_default();
+ node.push((index_path.to_vec(), n.clone()));
+ table.insert(id, Ser(node))?;
+
+ drop(table);
+ txn.commit()?;
Ok(())
};
let _permit = SEM_REMOTE_IMPORT.acquire().await.unwrap();
diff --git a/import/src/tmdb.rs b/import/src/tmdb.rs
index 37447e6..95ebef4 100644
--- a/import/src/tmdb.rs
+++ b/import/src/tmdb.rs
@@ -5,7 +5,7 @@
*/
use anyhow::Context;
use bincode::{Decode, Encode};
-use jellycommon::chrono::{format::Parsed, DateTime, Utc};
+use jellycommon::chrono::{format::Parsed, Utc};
use log::info;
use serde::Deserialize;
@@ -115,7 +115,7 @@ pub async fn tmdb_image(path: &str) -> anyhow::Result<Vec<u8>> {
Ok(res.bytes().await?.to_vec())
}
-pub fn parse_release_date(d: &str) -> anyhow::Result<DateTime<Utc>> {
+pub fn parse_release_date(d: &str) -> anyhow::Result<i64> {
let (year, month, day) = (&d[0..4], &d[5..7], &d[8..10]);
let (year, month, day) = (
year.parse().context("parsing year")?,
@@ -131,5 +131,5 @@ pub fn parse_release_date(d: &str) -> anyhow::Result<DateTime<Utc>> {
p.hour_mod_12 = Some(0);
p.minute = Some(0);
p.second = Some(0);
- Ok(p.to_datetime_with_timezone(&Utc)?)
+ Ok(p.to_datetime_with_timezone(&Utc)?.timestamp_millis())
}
diff --git a/server/src/main.rs b/server/src/main.rs
index acb8c87..6862a98 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -8,10 +8,14 @@
#![feature(let_chains)]
use crate::routes::ui::{account::hash_password, admin::log::enable_logging};
-use database::Database;
-use jellybase::{federation::Federation, CONF};
+use database::DataAcid;
+use jellybase::{
+ database::{ReadableTable, Ser, T_USER},
+ federation::Federation,
+ CONF,
+};
use jellycommon::user::{PermissionSet, Theme, User};
-use log::{error, warn, info};
+use log::{error, info, warn};
use routes::build_rocket;
use tokio::fs::create_dir_all;
@@ -25,16 +29,20 @@ async fn main() {
log::warn!("authentification bypass enabled");
create_dir_all(&CONF.cache_path).await.unwrap();
- let database = Database::open(&CONF.database_path).unwrap();
+ let database = DataAcid::open(&CONF.database_path).unwrap();
let federation = Federation::initialize();
if let Some(username) = &CONF.admin_username
&& let Some(password) = &CONF.admin_password
{
- database
- .user
- .fetch_and_update(&username, |admin| {
- Some(User {
+ let txn = database.begin_write().unwrap();
+ let mut users = txn.open_table(T_USER).unwrap();
+
+ let admin = users.get(username.as_str()).unwrap().map(|x| x.value().0);
+ users
+ .insert(
+ username.as_str(),
+ Ser(User {
admin: true,
name: username.clone(),
password: hash_password(&username, &password),
@@ -46,9 +54,12 @@ async fn main() {
theme: Theme::Dark,
permissions: PermissionSet::default(),
})
- })
- })
+ }),
+ )
.unwrap();
+
+ drop(users);
+ txn.commit().unwrap();
} else {
info!("admin account disabled")
}
diff --git a/server/src/routes/api/mod.rs b/server/src/routes/api/mod.rs
index 828b576..d8ea167 100644
--- a/server/src/routes/api/mod.rs
+++ b/server/src/routes/api/mod.rs
@@ -7,8 +7,9 @@ use super::ui::{
account::{login_logic, session::AdminSession},
error::MyResult,
};
-use crate::database::Database;
+use crate::database::DataAcid;
use anyhow::{anyhow, Context};
+use jellybase::database::{TableExt, T_NODE};
use jellycommon::{user::CreateSessionParams, Node};
use rocket::{
get,
@@ -35,7 +36,7 @@ pub fn r_api_version() -> &'static str {
#[post("/api/create_session", data = "<data>")]
pub fn r_api_account_login(
- database: &State<Database>,
+ database: &State<DataAcid>,
data: Json<CreateSessionParams>,
) -> MyResult<Value> {
let token = login_logic(
@@ -51,13 +52,12 @@ pub fn r_api_account_login(
#[get("/api/node_raw/<id>")]
pub fn r_api_node_raw(
admin: AdminSession,
- database: &State<Database>,
+ database: &State<DataAcid>,
id: &str,
) -> MyResult<Json<Node>> {
drop(admin);
- let node = database
- .node
- .get(&id.to_string())
+ let node = T_NODE
+ .get(database, id)
.context("retrieving library node")?
.ok_or(anyhow!("node does not exist"))?;
Ok(Json(node))
diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs
index 47fd6d2..6bc5127 100644
--- a/server/src/routes/mod.rs
+++ b/server/src/routes/mod.rs
@@ -3,7 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
-use crate::{database::Database, routes::ui::error::MyResult};
+use crate::{database::DataAcid, routes::ui::error::MyResult};
use api::{r_api_account_login, r_api_node_raw, r_api_root, r_api_version};
use base64::Engine;
use jellybase::{federation::Federation, CONF};
@@ -49,7 +49,7 @@ macro_rules! uri {
};
}
-pub fn build_rocket(database: Database, federation: Federation) -> Rocket<Build> {
+pub fn build_rocket(database: DataAcid, federation: Federation) -> Rocket<Build> {
rocket::build()
.configure(Config {
address: std::env::var("BIND_ADDR")
diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs
index e8b14b5..c033bda 100644
--- a/server/src/routes/stream.rs
+++ b/server/src/routes/stream.rs
@@ -4,9 +4,10 @@
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
use super::ui::{account::session::Session, error::MyError};
-use crate::database::Database;
+use crate::database::DataAcid;
use anyhow::{anyhow, Result};
use jellybase::{
+ database::{TableExt, T_NODE},
federation::Federation,
permission::{NodePermissionExt, PermissionSetExt},
CONF,
@@ -46,14 +47,13 @@ pub async fn r_stream_head(
pub async fn r_stream(
session: Session,
federation: &State<Federation>,
- db: &State<Database>,
+ db: &State<DataAcid>,
id: &str,
range: Option<RequestRange>,
spec: StreamSpec,
) -> Result<Either<StreamResponse, RedirectResponse>, MyError> {
- let node = db
- .node
- .get(&id.to_string())?
+ let node = T_NODE
+ .get(&db, id)?
.only_if_permitted(&session.user.permissions)
.ok_or(anyhow!("node does not exist"))?;
let source = node
diff --git a/server/src/routes/ui/account/mod.rs b/server/src/routes/ui/account/mod.rs
index cd8695f..8af92a0 100644
--- a/server/src/routes/ui/account/mod.rs
+++ b/server/src/routes/ui/account/mod.rs
@@ -8,7 +8,7 @@ pub mod settings;
use super::{error::MyError, layout::LayoutPage};
use crate::{
- database::Database,
+ database::DataAcid,
routes::ui::{
account::session::Session, error::MyResult, home::rocket_uri_macro_r_home,
layout::DynLayoutPage,
@@ -18,7 +18,10 @@ use crate::{
use anyhow::anyhow;
use argon2::{password_hash::Salt, Argon2, PasswordHasher};
use chrono::Duration;
-use jellybase::CONF;
+use jellybase::{
+ database::{Ser, TableExt, T_INVITE, T_USER},
+ CONF,
+};
use jellycommon::user::{PermissionSet, Theme, User, UserPermission};
use rocket::{
form::{Contextual, Form},
@@ -121,7 +124,7 @@ pub fn r_account_logout() -> DynLayoutPage<'static> {
#[post("/account/register", data = "<form>")]
pub fn r_account_register_post<'a>(
- database: &'a State<Database>,
+ database: &'a State<DataAcid>,
_sess: Option<Session>,
form: Form<Contextual<'a, RegisterForm>>,
) -> MyResult<DynLayoutPage<'a>> {
@@ -131,15 +134,17 @@ pub fn r_account_register_post<'a>(
None => return Err(format_form_error(form)),
};
- if database.invite.remove(&form.invitation).unwrap().is_none() {
- return Err(MyError(anyhow!("invitation invalid")));
+ let txn = database.begin_write()?;
+ let mut invites = txn.open_table(T_INVITE)?;
+ let mut users = txn.open_table(T_USER)?;
+
+ if invites.remove(&*form.invitation)?.is_none() {
+ Err(anyhow!("invitation invalid"))?;
}
- match database
- .user
- .compare_and_swap(
- &form.username,
- None,
- Some(&User {
+ let prev_user = users
+ .insert(
+ &*form.username,
+ Ser(User {
display_name: form.username.clone(),
name: form.username.clone(),
password: hash_password(&form.username, &form.password),
@@ -147,27 +152,32 @@ pub fn r_account_register_post<'a>(
theme: Theme::Dark,
permissions: PermissionSet::default(),
}),
- )
- .unwrap()
- {
- Ok(_) => Ok(LayoutPage {
- title: "Registration successful".to_string(),
- content: markup::new! {
- h1 { @if logged_in {
- "Registration successful, you may switch account now."
- } else {
- "Registration successful, you may log in now."
- }}
- },
- ..Default::default()
- }),
- Err(_) => Err(MyError(anyhow!("username is taken"))),
+ )?
+ .map(|x| x.value().0);
+ if prev_user.is_some() {
+ Err(anyhow!("username taken"))?;
}
+
+ drop(users);
+ drop(invites);
+ txn.commit()?;
+
+ Ok(LayoutPage {
+ title: "Registration successful".to_string(),
+ content: markup::new! {
+ h1 { @if logged_in {
+ "Registration successful, you may switch account now."
+ } else {
+ "Registration successful, you may log in now."
+ }}
+ },
+ ..Default::default()
+ })
}
#[post("/account/login", data = "<form>")]
pub fn r_account_login_post(
- database: &State<Database>,
+ database: &State<DataAcid>,
jar: &CookieJar,
form: Form<Contextual<LoginForm>>,
) -> MyResult<Redirect> {
@@ -194,7 +204,7 @@ pub fn r_account_logout_post(jar: &CookieJar) -> MyResult<Redirect> {
}
pub fn login_logic(
- database: &Database,
+ database: &DataAcid,
username: &str,
password: &str,
expire: Option<i64>,
@@ -203,9 +213,8 @@ pub fn login_logic(
// hashing the password regardless if the accounts exists to prevent timing attacks
let password = hash_password(username, password);
- let mut user = database
- .user
- .get(&username.to_string())?
+ let mut user = T_USER
+ .get(database, username)?
.ok_or(anyhow!("invalid password"))?;
if user.password != password {
diff --git a/server/src/routes/ui/account/session/guard.rs b/server/src/routes/ui/account/session/guard.rs
index ae1ebd3..b2fd408 100644
--- a/server/src/routes/ui/account/session/guard.rs
+++ b/server/src/routes/ui/account/session/guard.rs
@@ -4,8 +4,9 @@
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
use super::{AdminSession, Session};
-use crate::{database::Database, routes::ui::error::MyError};
+use crate::{database::DataAcid, routes::ui::error::MyError};
use anyhow::anyhow;
+use jellybase::database::{ReadableTable, T_USER};
use log::warn;
use rocket::{
async_trait,
@@ -35,8 +36,19 @@ impl Session {
username = "admin".to_string();
}
- let db = req.guard::<&State<Database>>().await.unwrap();
- let user = db.user.get(&username)?.ok_or(anyhow!("user not found"))?;
+ let db = req.guard::<&State<DataAcid>>().await.unwrap();
+
+ let user = {
+ let txn = db.inner.begin_read()?;
+ let table = txn.open_table(T_USER)?;
+ let user = table
+ .get(&*username)?
+ .ok_or(anyhow!("user not found"))?
+ .value()
+ .0;
+ drop(table);
+ user
+ };
Ok(Session { user })
}
diff --git a/server/src/routes/ui/account/settings.rs b/server/src/routes/ui/account/settings.rs
index f14478b..ecc0723 100644
--- a/server/src/routes/ui/account/settings.rs
+++ b/server/src/routes/ui/account/settings.rs
@@ -5,7 +5,7 @@
*/
use super::{format_form_error, hash_password};
use crate::{
- database::Database,
+ database::DataAcid,
routes::ui::{
account::{rocket_uri_macro_r_account_login, session::Session},
error::MyResult,
@@ -13,7 +13,11 @@ use crate::{
},
uri,
};
-use jellybase::permission::PermissionSetExt;
+use anyhow::anyhow;
+use jellybase::{
+ database::{ReadableTable, Ser, T_USER},
+ permission::PermissionSetExt,
+};
use jellycommon::user::{Theme, UserPermission};
use markup::{Render, RenderAttributeValue};
use rocket::{
@@ -97,7 +101,7 @@ pub fn r_account_settings(session: Session) -> DynLayoutPage<'static> {
#[post("/account/settings", data = "<form>")]
pub fn r_account_settings_post(
session: Session,
- database: &State<Database>,
+ database: &State<DataAcid>,
form: Form<Contextual<SettingsForm>>,
) -> MyResult<DynLayoutPage<'static>> {
session
@@ -111,23 +115,32 @@ pub fn r_account_settings_post(
};
let mut out = String::new();
- database.user.fetch_and_update(&session.user.name, |k| {
- k.map(|mut k| {
- if let Some(password) = &form.password {
- k.password = hash_password(&session.user.name, password);
- out += "Password updated\n";
- }
- if let Some(display_name) = &form.display_name {
- k.display_name = display_name.clone();
- out += "Display name updated\n";
- }
- if let Some(theme) = form.theme {
- k.theme = theme;
- out += "Theme updated\n";
- }
- k
- })
- })?;
+
+ let txn = database.begin_write()?;
+ let mut users = txn.open_table(T_USER)?;
+
+ let mut user = users
+ .get(&*session.user.name)?
+ .ok_or(anyhow!("user missing"))?
+ .value()
+ .0;
+
+ if let Some(password) = &form.password {
+ user.password = hash_password(&session.user.name, password);
+ out += "Password updated\n";
+ }
+ if let Some(display_name) = &form.display_name {
+ user.display_name = display_name.clone();
+ out += "Display name updated\n";
+ }
+ if let Some(theme) = form.theme {
+ user.theme = theme;
+ out += "Theme updated\n";
+ }
+
+ users.insert(&*session.user.name, Ser(user))?;
+ drop(users);
+ txn.commit()?;
Ok(settings_page(
session, // using the old session here, results in outdated theme being displayed
diff --git a/server/src/routes/ui/admin/mod.rs b/server/src/routes/ui/admin/mod.rs
index b976192..60ed416 100644
--- a/server/src/routes/ui/admin/mod.rs
+++ b/server/src/routes/ui/admin/mod.rs
@@ -8,7 +8,7 @@ pub mod user;
use super::account::session::AdminSession;
use crate::{
- database::Database,
+ database::DataAcid,
routes::ui::{
admin::log::rocket_uri_macro_r_admin_log,
error::MyResult,
@@ -17,7 +17,11 @@ use crate::{
uri,
};
use anyhow::anyhow;
-use jellybase::{federation::Federation, CONF};
+use jellybase::{
+ database::{ReadableTable, TableExt, T_INVITE},
+ federation::Federation,
+ CONF,
+};
use jellyimport::import;
use rand::Rng;
use rocket::{form::Form, get, post, FromForm, State};
@@ -27,16 +31,28 @@ use user::rocket_uri_macro_r_admin_users;
#[get("/admin/dashboard")]
pub fn r_admin_dashboard(
_session: AdminSession,
- database: &State<Database>,
+ database: &State<DataAcid>,
) -> MyResult<DynLayoutPage<'static>> {
admin_dashboard(database, None)
}
pub fn admin_dashboard<'a>(
- database: &Database,
+ database: &DataAcid,
flash: Option<MyResult<String>>,
) -> MyResult<DynLayoutPage<'a>> {
- let invites = database.invite.iter().collect::<Result<Vec<_>, _>>()?;
+ let invites = {
+ let txn = database.begin_read()?;
+ let table = txn.open_table(T_INVITE)?;
+ let i = table
+ .iter()?
+ .map(|a| {
+ let (x, _) = a.unwrap();
+ x.value().to_owned()
+ })
+ .collect::<Vec<_>>();
+ drop(table);
+ i
+ };
let flash = flash.map(|f| f.map_err(|e| format!("{e:?}")));
Ok(LayoutPage {
@@ -64,8 +80,8 @@ pub fn admin_dashboard<'a>(
ul { @for t in &invites {
li {
form[method="POST", action=uri!(r_admin_remove_invite())] {
- span { @t.0 }
- input[type="text", name="invite", value=&t.0, hidden];
+ span { @t }
+ input[type="text", name="invite", value=&t, hidden];
input[type="submit", value="Invalidate"];
}
}
@@ -78,10 +94,10 @@ pub fn admin_dashboard<'a>(
#[post("/admin/generate_invite")]
pub fn r_admin_invite(
_session: AdminSession,
- database: &State<Database>,
+ database: &State<DataAcid>,
) -> MyResult<DynLayoutPage<'static>> {
let i = format!("{}", rand::thread_rng().gen::<u128>());
- database.invite.insert(&i, &())?;
+ T_INVITE.insert(&database, &*i, ())?;
admin_dashboard(database, Some(Ok(format!("Invite: {}", i))))
}
@@ -94,13 +110,12 @@ pub struct DeleteInvite {
#[post("/admin/remove_invite", data = "<form>")]
pub fn r_admin_remove_invite(
session: AdminSession,
- database: &State<Database>,
+ database: &State<DataAcid>,
form: Form<DeleteInvite>,
) -> MyResult<DynLayoutPage<'static>> {
drop(session);
- database
- .invite
- .remove(&form.invite)?
+ T_INVITE
+ .remove(&database, form.invite.as_str())?
.ok_or(anyhow!("invite did not exist"))?;
admin_dashboard(database, Some(Ok("Invite invalidated".into())))
@@ -109,7 +124,7 @@ pub fn r_admin_remove_invite(
#[post("/admin/import")]
pub async fn r_admin_import(
session: AdminSession,
- database: &State<Database>,
+ database: &State<DataAcid>,
federation: &State<Federation>,
) -> MyResult<DynLayoutPage<'static>> {
drop(session);
@@ -127,7 +142,7 @@ pub async fn r_admin_import(
#[post("/admin/delete_cache")]
pub async fn r_admin_delete_cache(
session: AdminSession,
- database: &State<Database>,
+ database: &State<DataAcid>,
) -> MyResult<DynLayoutPage<'static>> {
drop(session);
let t = Instant::now();
diff --git a/server/src/routes/ui/admin/user.rs b/server/src/routes/ui/admin/user.rs
index 5c2c737..7d619c0 100644
--- a/server/src/routes/ui/admin/user.rs
+++ b/server/src/routes/ui/admin/user.rs
@@ -4,7 +4,7 @@
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
use crate::{
- database::Database,
+ database::DataAcid,
routes::ui::{
account::session::AdminSession,
error::MyResult,
@@ -13,23 +13,36 @@ use crate::{
uri,
};
use anyhow::{anyhow, Context};
+use jellybase::database::{ReadableTable, Ser, TableExt, T_USER};
use jellycommon::user::{PermissionSet, UserPermission};
use rocket::{form::Form, get, post, FromForm, FromFormField, State};
#[get("/admin/users")]
pub fn r_admin_users(
_session: AdminSession,
- database: &State<Database>,
+ database: &State<DataAcid>,
) -> MyResult<DynLayoutPage<'static>> {
user_management(database, None)
}
fn user_management<'a>(
- database: &Database,
+ database: &DataAcid,
flash: Option<MyResult<String>>,
) -> MyResult<DynLayoutPage<'a>> {
// TODO this doesnt scale, pagination!
- let users = database.user.iter().collect::<Result<Vec<_>, _>>()?;
+ let users = {
+ let txn = database.begin_read()?;
+ let table = txn.open_table(T_USER)?;
+ let i = table
+ .iter()?
+ .map(|a| {
+ let (x, y) = a.unwrap();
+ (x.value().to_owned(), y.value().0)
+ })
+ .collect::<Vec<_>>();
+ drop(table);
+ i
+ };
let flash = flash.map(|f| f.map_err(|e| format!("{e:?}")));
Ok(LayoutPage {
@@ -51,20 +64,19 @@ fn user_management<'a>(
#[get("/admin/user/<name>")]
pub fn r_admin_user<'a>(
_session: AdminSession,
- database: &State<Database>,
+ database: &State<DataAcid>,
name: &'a str,
) -> MyResult<DynLayoutPage<'a>> {
manage_single_user(database, None, name.to_string())
}
fn manage_single_user<'a>(
- database: &Database,
+ database: &DataAcid,
flash: Option<MyResult<String>>,
name: String,
) -> MyResult<DynLayoutPage<'a>> {
- let user = database
- .user
- .get(&name)?
+ let user = T_USER
+ .get(&database, &*name)?
.ok_or(anyhow!("user does not exist"))?;
let flash = flash.map(|f| f.map_err(|e| format!("{e:?}")));
@@ -140,26 +152,31 @@ pub enum GrantState {
#[post("/admin/update_user_permission", data = "<form>")]
pub fn r_admin_user_permission(
session: AdminSession,
- database: &State<Database>,
+ database: &State<DataAcid>,
form: Form<UserPermissionForm>,
) -> MyResult<DynLayoutPage<'static>> {
drop(session);
let perm = serde_json::from_str::<UserPermission>(&form.permission)
.context("parsing provided permission")?;
- database
- .user
- .update_and_fetch(&form.name, |user| {
- user.map(|mut user| {
- match form.action {
- GrantState::Grant => drop(user.permissions.0.insert(perm.clone(), true)),
- GrantState::Revoke => drop(user.permissions.0.insert(perm.clone(), false)),
- GrantState::Unset => drop(user.permissions.0.remove(&perm)),
- }
- user
- })
- })?
- .ok_or(anyhow!("user did not exist"))?;
+ let txn = database.begin_write()?;
+ let mut users = txn.open_table(T_USER)?;
+
+ let mut user = users
+ .get(&*form.name)?
+ .ok_or(anyhow!("user missing"))?
+ .value()
+ .0;
+
+ match form.action {
+ GrantState::Grant => drop(user.permissions.0.insert(perm.clone(), true)),
+ GrantState::Revoke => drop(user.permissions.0.insert(perm.clone(), false)),
+ GrantState::Unset => drop(user.permissions.0.remove(&perm)),
+ }
+
+ users.insert(&*form.name, Ser(user))?;
+ drop(users);
+ txn.commit()?;
manage_single_user(
database,
@@ -171,13 +188,12 @@ pub fn r_admin_user_permission(
#[post("/admin/remove_user", data = "<form>")]
pub fn r_admin_remove_user(
session: AdminSession,
- database: &State<Database>,
+ database: &State<DataAcid>,
form: Form<DeleteUser>,
) -> MyResult<DynLayoutPage<'static>> {
drop(session);
- database
- .user
- .remove(&form.name)?
+ T_USER
+ .remove(&database, form.name.as_str())?
.ok_or(anyhow!("user did not exist"))?;
user_management(database, Some(Ok("User removed".into())))
}
diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs
index ddbc2ee..b1a13da 100644
--- a/server/src/routes/ui/assets.rs
+++ b/server/src/routes/ui/assets.rs
@@ -4,12 +4,15 @@
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
use crate::{
- database::Database,
+ database::DataAcid,
routes::ui::{account::session::Session, error::MyResult, CacheControlFile},
};
use anyhow::{anyhow, Context};
use jellybase::{
- cache::async_cache_file, federation::Federation, permission::NodePermissionExt,
+ cache::async_cache_file,
+ database::{TableExt, T_NODE},
+ federation::Federation,
+ permission::NodePermissionExt,
AssetLocationExt,
};
pub use jellycommon::AssetRole;
@@ -22,14 +25,13 @@ use tokio::fs::File;
#[get("/n/<id>/asset?<role>&<width>")]
pub async fn r_item_assets(
session: Session,
- db: &State<Database>,
+ db: &State<DataAcid>,
id: &str,
role: AssetRole,
width: Option<usize>,
) -> MyResult<(ContentType, CacheControlFile)> {
- let node = db
- .node
- .get(&id.to_string())?
+ let node = T_NODE
+ .get(&db, id)?
.only_if_permitted(&session.user.permissions)
.ok_or(anyhow!("node does not exist"))?;
let mut asset = match role {
@@ -38,7 +40,9 @@ pub async fn r_item_assets(
};
if let None = asset {
if let Some(parent) = &node.public.path.last() {
- let parent = db.node.get(parent)?.ok_or(anyhow!("node does not exist"))?;
+ let parent = T_NODE
+ .get(&db, parent.as_str())?
+ .ok_or(anyhow!("node does not exist"))?;
asset = match role {
AssetRole::Backdrop => parent.private.backdrop,
AssetRole::Poster => parent.private.poster,
@@ -55,15 +59,14 @@ pub async fn r_item_assets(
#[get("/n/<id>/thumbnail?<t>&<width>")]
pub async fn r_node_thumbnail(
session: Session,
- db: &State<Database>,
+ db: &State<DataAcid>,
fed: &State<Federation>,
id: &str,
t: f64,
width: Option<usize>,
) -> MyResult<(ContentType, CacheControlFile)> {
- let node = db
- .node
- .get(&id.to_string())?
+ let node = T_NODE
+ .get(&db, id)?
.only_if_permitted(&session.user.permissions)
.ok_or(anyhow!("node does not exist"))?;
diff --git a/server/src/routes/ui/browser.rs b/server/src/routes/ui/browser.rs
index 509f242..e811516 100644
--- a/server/src/routes/ui/browser.rs
+++ b/server/src/routes/ui/browser.rs
@@ -10,9 +10,8 @@ use super::{
node::NodeCard,
sort::{filter_and_sort_nodes, NodeFilterSort, NodeFilterSortForm},
};
-use crate::{database::Database, uri};
-use anyhow::Context;
-use jellycommon::{user::NodeUserData, NodePublic};
+use crate::{database::DataAcid, uri};
+use jellybase::database::{ReadableTable, T_NODE, T_USER_NODE};
use rocket::{get, State};
/// This function is a stub and only useful for use in the uri! macro.
@@ -22,25 +21,31 @@ pub fn r_all_items() {}
#[get("/items?<page>&<filter..>")]
pub fn r_all_items_filter(
sess: Session,
- db: &State<Database>,
+ db: &State<DataAcid>,
page: Option<usize>,
filter: NodeFilterSort,
) -> Result<DynLayoutPage<'_>, MyError> {
- let mut items = db
- .node
- .iter()
- .map(|e| {
- let (i, n) = e.context("listing")?;
- let u = db
- .user_node
- .get(&(sess.user.name.clone(), i.clone()))?
- .unwrap_or_default();
- Ok((i, n, u))
- })
- .collect::<anyhow::Result<Vec<_>>>()?
- .into_iter()
- .map(|(k, n, u)| (k, n.public, u))
- .collect::<Vec<(String, NodePublic, NodeUserData)>>();
+ let mut items = {
+ let txn = db.begin_read()?;
+ let nodes = txn.open_table(T_NODE)?;
+ let node_users = txn.open_table(T_USER_NODE)?;
+ let i = nodes
+ .iter()?
+ .map(|a| {
+ let (x, y) = a.unwrap();
+ let (x, y) = (x.value().to_owned(), y.value().0);
+ let z = node_users
+ .get(&(sess.user.name.as_str(), x.as_str()))
+ .unwrap()
+ .map(|z| z.value().0)
+ .unwrap_or_default();
+ let y = y.public;
+ (x, y, z)
+ })
+ .collect::<Vec<_>>();
+ drop(nodes);
+ i
+ };
filter_and_sort_nodes(&filter, &mut items);
diff --git a/server/src/routes/ui/error.rs b/server/src/routes/ui/error.rs
index 07a6bed..98c6b7f 100644
--- a/server/src/routes/ui/error.rs
+++ b/server/src/routes/ui/error.rs
@@ -5,7 +5,7 @@
*/
use super::layout::{DynLayoutPage, LayoutPage};
use crate::{routes::ui::account::rocket_uri_macro_r_account_login, uri};
-use jellybase::{database::sled, AssetLocationExt};
+use jellybase::AssetLocationExt;
use jellycommon::AssetLocation;
use log::info;
use rocket::{
@@ -96,13 +96,43 @@ impl From<std::io::Error> for MyError {
MyError(anyhow::anyhow!("{err}"))
}
}
-impl From<sled::Error> for MyError {
- fn from(err: sled::Error) -> Self {
+impl From<serde_json::Error> for MyError {
+ fn from(err: serde_json::Error) -> Self {
MyError(anyhow::anyhow!("{err}"))
}
}
-impl From<serde_json::Error> for MyError {
- fn from(err: serde_json::Error) -> Self {
+impl From<jellybase::database::CommitError> for MyError {
+ fn from(err: jellybase::database::CommitError) -> Self {
+ MyError(anyhow::anyhow!("{err}"))
+ }
+}
+impl From<jellybase::database::CompactionError> for MyError {
+ fn from(err: jellybase::database::CompactionError) -> Self {
+ MyError(anyhow::anyhow!("{err}"))
+ }
+}
+impl From<jellybase::database::DatabaseError> for MyError {
+ fn from(err: jellybase::database::DatabaseError) -> Self {
+ MyError(anyhow::anyhow!("{err}"))
+ }
+}
+impl From<jellybase::database::SavepointError> for MyError {
+ fn from(err: jellybase::database::SavepointError) -> Self {
+ MyError(anyhow::anyhow!("{err}"))
+ }
+}
+impl From<jellybase::database::StorageError> for MyError {
+ fn from(err: jellybase::database::StorageError) -> Self {
+ MyError(anyhow::anyhow!("{err}"))
+ }
+}
+impl From<jellybase::database::TableError> for MyError {
+ fn from(err: jellybase::database::TableError) -> Self {
+ MyError(anyhow::anyhow!("{err}"))
+ }
+}
+impl From<jellybase::database::TransactionError> for MyError {
+ fn from(err: jellybase::database::TransactionError) -> Self {
MyError(anyhow::anyhow!("{err}"))
}
}
diff --git a/server/src/routes/ui/home.rs b/server/src/routes/ui/home.rs
index d332447..9a00532 100644
--- a/server/src/routes/ui/home.rs
+++ b/server/src/routes/ui/home.rs
@@ -9,44 +9,48 @@ use super::{
node::{DatabaseNodeUserDataExt, NodeCard},
};
use crate::{
- database::Database,
+ database::DataAcid,
routes::ui::{error::MyResult, layout::DynLayoutPage},
};
use anyhow::Context;
use chrono::{Datelike, Utc};
-use jellybase::CONF;
-use jellycommon::{
- user::{NodeUserData, WatchedState},
- NodePublic,
+use jellybase::{
+ database::{ReadableTable, TableExt, T_NODE, T_USER_NODE},
+ CONF,
};
+use jellycommon::user::WatchedState;
use rocket::{get, State};
use tokio::fs::read_to_string;
#[get("/")]
-pub fn r_home(sess: Session, db: &State<Database>) -> MyResult<DynLayoutPage> {
- let mut items = db
- .node
- .iter()
- .map(|e| {
- let (i, n) = e.context("listing")?;
- let u = db
- .user_node
- .get(&(sess.user.name.clone(), i.clone()))?
- .unwrap_or_default();
- Ok((i, n, u))
- })
- .collect::<anyhow::Result<Vec<_>>>()?
- .into_iter()
- .map(|(k, n, u)| (k, n.public, u))
- .collect::<Vec<(String, NodePublic, NodeUserData)>>();
-
+pub fn r_home(sess: Session, db: &State<DataAcid>) -> MyResult<DynLayoutPage> {
+ let mut items = {
+ let txn = db.begin_read()?;
+ let nodes = txn.open_table(T_NODE)?;
+ let node_users = txn.open_table(T_USER_NODE)?;
+ let i = nodes
+ .iter()?
+ .map(|a| {
+ let (x, y) = a.unwrap();
+ let (x, y) = (x.value().to_owned(), y.value().0);
+ let z = node_users
+ .get(&(sess.user.name.as_str(), x.as_str()))
+ .unwrap()
+ .map(|z| z.value().0)
+ .unwrap_or_default();
+ let y = y.public;
+ (x, y, z)
+ })
+ .collect::<Vec<_>>();
+ drop(nodes);
+ i
+ };
let random = (0..16)
.flat_map(|i| Some(items[cheap_daily_random(i).checked_rem(items.len())?].clone()))
.collect::<Vec<_>>();
- let toplevel = db
- .node
- .get(&"library".to_string())?
+ let toplevel = T_NODE
+ .get(&db, "library")?
.context("root node missing")?
.public
.children
@@ -56,11 +60,7 @@ pub fn r_home(sess: Session, db: &State<Database>) -> MyResult<DynLayoutPage> {
.into_iter()
.collect::<Vec<_>>();
- items.sort_by_key(|(_, n, _)| {
- n.release_date
- .map(|d| -d.naive_utc().timestamp())
- .unwrap_or(i64::MAX)
- });
+ items.sort_by_key(|(_, n, _)| n.release_date.map(|d| -d).unwrap_or(i64::MAX));
let latest = items
.iter()
@@ -73,7 +73,7 @@ pub fn r_home(sess: Session, db: &State<Database>) -> MyResult<DynLayoutPage> {
.filter(|(_, _, u)| matches!(u.watched, WatchedState::Progress(_)))
.map(|k| k.to_owned())
.collect::<Vec<_>>();
-
+
let watchlist = items
.iter()
.filter(|(_, _, u)| matches!(u.watched, WatchedState::Pending))
diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs
index 0dbb027..c055953 100644
--- a/server/src/routes/ui/node.rs
+++ b/server/src/routes/ui/node.rs
@@ -9,7 +9,7 @@ use super::{
sort::{filter_and_sort_nodes, NodeFilterSort, NodeFilterSortForm},
};
use crate::{
- database::Database,
+ database::DataAcid,
routes::{
api::AcceptJson,
ui::{
@@ -22,8 +22,12 @@ use crate::{
},
uri,
};
-use anyhow::{anyhow, Context, Result};
-use jellybase::permission::NodePermissionExt;
+use anyhow::{anyhow, Result};
+use chrono::NaiveDateTime;
+use jellybase::{
+ database::{TableExt, T_NODE, T_USER_NODE},
+ permission::NodePermissionExt,
+};
use jellycommon::{
user::{NodeUserData, WatchedState},
Chapter, MediaInfo, NodeKind, NodePublic, Rating, SourceTrackKind,
@@ -40,21 +44,18 @@ pub fn r_library_node(id: String) {
pub async fn r_library_node_filter<'a>(
session: Session,
id: &'a str,
- db: &'a State<Database>,
+ db: &'a State<DataAcid>,
aj: AcceptJson,
filter: NodeFilterSort,
) -> Result<Either<DynLayoutPage<'a>, Json<NodePublic>>, MyError> {
- let node = db
- .node
- .get(&id.to_string())
- .context("retrieving library node")?
+ let node = T_NODE
+ .get(&db, id)?
.only_if_permitted(&session.user.permissions)
.ok_or(anyhow!("node does not exist"))?
.public;
- let udata = db
- .user_node
- .get(&(session.user.name.clone(), id.to_string()))?
+ let udata = T_USER_NODE
+ .get(&db, &(session.user.name.as_str(), id))?
.unwrap_or_default();
if *aj {
@@ -192,7 +193,7 @@ markup::define! {
p { @m.resolution_name() }
}
@if let Some(d) = &node.release_date {
- p { @d.format("%Y-%m-%d").to_string() }
+ p { @NaiveDateTime::from_timestamp_millis(*d).unwrap().and_utc().to_string() }
}
@if !node.children.is_empty() {
p { @format!("{} items", node.children.len()) }
@@ -244,7 +245,7 @@ pub trait DatabaseNodeUserDataExt {
session: &Session,
) -> Result<(String, NodePublic, NodeUserData)>;
}
-impl DatabaseNodeUserDataExt for Database {
+impl DatabaseNodeUserDataExt for DataAcid {
fn get_node_with_userdata(
&self,
id: &str,
@@ -252,12 +253,12 @@ impl DatabaseNodeUserDataExt for Database {
) -> Result<(String, NodePublic, NodeUserData)> {
Ok((
id.to_owned(),
- self.node
- .get(&id.to_owned())?
+ T_NODE
+ .get(self, id)?
.ok_or(anyhow!("node does not exist: {id}"))?
.public,
- self.user_node
- .get(&(session.user.name.to_owned(), id.to_owned()))?
+ T_USER_NODE
+ .get(self, &(session.user.name.as_str(), id))?
.unwrap_or_default(),
))
}
diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs
index e3c5cb2..62f014c 100644
--- a/server/src/routes/ui/player.rs
+++ b/server/src/routes/ui/player.rs
@@ -5,7 +5,7 @@
*/
use super::{account::session::Session, layout::LayoutPage};
use crate::{
- database::Database,
+ database::DataAcid,
routes::{
stream::rocket_uri_macro_r_stream,
ui::{
@@ -17,6 +17,7 @@ use crate::{
uri,
};
use anyhow::anyhow;
+use jellybase::database::{TableExt, T_NODE};
use jellycommon::{
stream::{StreamFormat, StreamSpec},
Node, SourceTrackKind, TrackID,
@@ -44,14 +45,11 @@ impl PlayerConfig {
#[get("/n/<id>/player?<conf..>", rank = 4)]
pub fn r_player<'a>(
_sess: Session,
- db: &'a State<Database>,
+ db: &'a State<DataAcid>,
id: &'a str,
conf: PlayerConfig,
) -> MyResult<DynLayoutPage<'a>> {
- let item = db
- .node
- .get(&id.to_string())?
- .ok_or(anyhow!("node does not exist"))?;
+ let item = T_NODE.get(db, id)?.ok_or(anyhow!("node does not exist"))?;
let spec = StreamSpec {
tracks: None
diff --git a/server/src/routes/userdata.rs b/server/src/routes/userdata.rs
index 2ded24a..8803bde 100644
--- a/server/src/routes/userdata.rs
+++ b/server/src/routes/userdata.rs
@@ -3,10 +3,10 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
-use super::ui::{account::session::Session, error::MyResult};
+use super::ui::{account::session::Session, error::MyResult, node::DatabaseNodeUserDataExt};
use crate::routes::ui::node::rocket_uri_macro_r_library_node;
use anyhow::anyhow;
-use jellybase::database::Database;
+use jellybase::database::{DataAcid, ReadableTable, Ser, TableExt, T_NODE, T_USER_NODE};
use jellycommon::user::{NodeUserData, WatchedState};
use rocket::{
get, post, response::Redirect, serde::json::Json, FromFormField, State, UriDisplayQuery,
@@ -22,38 +22,41 @@ pub enum UrlWatchedState {
#[get("/n/<id>/userdata")]
pub fn r_node_userdata(
session: Session,
- db: &State<Database>,
+ db: &State<DataAcid>,
id: &str,
) -> MyResult<Json<NodeUserData>> {
- db.node
- .get(&id.to_string())?
- .ok_or(anyhow!("node does not exist"))?;
- let key = (session.user.name.clone(), id.to_owned());
- Ok(Json(db.user_node.get(&key)?.unwrap_or_default()))
+ let (_, _, u) = db.get_node_with_userdata(id, &session)?;
+ Ok(Json(u))
}
#[post("/n/<id>/watched?<state>")]
pub async fn r_player_watched(
session: Session,
- db: &State<Database>,
+ db: &State<DataAcid>,
id: &str,
state: UrlWatchedState,
) -> MyResult<Redirect> {
- db.node
- .get(&id.to_string())?
- .ok_or(anyhow!("node does not exist"))?;
+ T_NODE.get(db, id)?.ok_or(anyhow!("node does not exist"))?;
- let key = (session.user.name.clone(), id.to_owned());
+ // let key = (session.user.name.clone(), id.to_owned());
- db.user_node.fetch_and_update(&key, |t| {
- let mut t = t.unwrap_or_default();
- t.watched = match state {
- UrlWatchedState::None => WatchedState::None,
- UrlWatchedState::Watched => WatchedState::Watched,
- UrlWatchedState::Pending => WatchedState::Pending,
- };
- Some(t)
- })?;
+ let txn = db.begin_write()?;
+ let mut user_nodes = txn.open_table(T_USER_NODE)?;
+
+ let mut udata = user_nodes
+ .get((session.user.name.as_str(), id))?
+ .map(|x| x.value().0)
+ .unwrap_or_default();
+
+ udata.watched = match state {
+ UrlWatchedState::None => WatchedState::None,
+ UrlWatchedState::Watched => WatchedState::Watched,
+ UrlWatchedState::Pending => WatchedState::Pending,
+ };
+
+ user_nodes.insert((session.user.name.as_str(), id), Ser(udata))?;
+ drop(user_nodes);
+ txn.commit()?;
Ok(Redirect::found(rocket::uri!(r_library_node(id))))
}
@@ -61,24 +64,30 @@ pub async fn r_player_watched(
#[post("/n/<id>/progress?<t>")]
pub async fn r_player_progress(
session: Session,
- db: &State<Database>,
+ db: &State<DataAcid>,
id: &str,
t: f64,
) -> MyResult<()> {
- db.node
- .get(&id.to_string())?
- .ok_or(anyhow!("node does not exist"))?;
+ T_NODE.get(db, id)?.ok_or(anyhow!("node does not exist"))?;
+
+ let txn = db.begin_write()?;
+ let mut user_nodes = txn.open_table(T_USER_NODE)?;
+
+ let mut udata = user_nodes
+ .get((session.user.name.as_str(), id))?
+ .map(|x| x.value().0)
+ .unwrap_or_default();
+
+ udata.watched = match udata.watched {
+ WatchedState::None | WatchedState::Pending | WatchedState::Progress(_) => {
+ WatchedState::Progress(t)
+ }
+ WatchedState::Watched => WatchedState::Watched,
+ };
+
+ user_nodes.insert((session.user.name.as_str(), id), Ser(udata))?;
+ drop(user_nodes);
+ txn.commit()?;
- let key = (session.user.name.clone(), id.to_owned());
- db.user_node.fetch_and_update(&key, |d| {
- let mut d = d.unwrap_or_default();
- d.watched = match d.watched {
- WatchedState::None | WatchedState::Pending | WatchedState::Progress(_) => {
- WatchedState::Progress(t)
- }
- WatchedState::Watched => WatchedState::Watched,
- };
- Some(d)
- })?;
Ok(())
}
diff --git a/tool/src/migrate.rs b/tool/src/migrate.rs
index 5037bf4..c774832 100644
--- a/tool/src/migrate.rs
+++ b/tool/src/migrate.rs
@@ -6,15 +6,89 @@
use crate::{Action, MigrateMode};
use anyhow::{bail, Context};
use indicatif::ProgressIterator;
-use jellybase::database::{typed_sled::Tree, Database};
+use jellybase::database::{DataAcid, ReadableTable, Ser, T_INVITE, T_USER, T_USER_NODE};
+use jellycommon::user::{NodeUserData, User};
use log::{info, warn};
-use serde::Serialize;
+use std::io::{BufRead, BufReader};
use std::{
fs::File,
- io::{BufRead, BufReader, BufWriter, Write},
+ io::{BufWriter, Write},
path::Path,
};
+// macro_rules! process_tree {
+// ($mode:ident, $save_location:ident, $da:ident, $name:literal, $table:ident, $dt:tt) => {{
+// let path = $save_location.join($name);
+// match $mode {
+// MigrateMode::Export => {
+// let mut o = BufWriter::new(File::create(path)?);
+// let txn = $da.begin_read()?;
+// let table = txn.open_table($table)?;
+
+// let len = table.len()?;
+// for r in table.iter()?.progress_count(len.try_into().unwrap()) {
+// let (k, v) = r?;
+// serde_json::to_writer(&mut o, &(k.value(), v.value().0))?;
+// writeln!(&mut o)?;
+// }
+// drop(table);
+// }
+// MigrateMode::Import => {
+// {
+// let txn = $da.begin_read()?;
+// let table = txn.open_table($table)?;
+// if !table.is_empty()? {
+// bail!("tree not empty, `rm -rf` your db please :)")
+// }
+// }
+
+// let Ok(i) = File::open(&path) else {
+// warn!("{path:?} does not exist; the import of that tree will be skipped.");
+// return Ok(());
+// };
+// let i = BufReader::new(i);
+// for l in i.lines() {
+// let l = l?;
+// let (k, v) = serde_json::from_str::<$dt>(&l).context("reading db dump item")?;
+// {
+// let txn = $da.begin_write()?;
+// let mut table = txn.open_table($table)?;
+
+// table.insert(&convert(k), Ser(v))?;
+// drop(table);
+// txn.commit()?
+// }
+// }
+// }
+// }
+// }};
+// }
+
+// pub(crate) fn migrate(action: Action) -> anyhow::Result<()> {
+// match action {
+// Action::Migrate {
+// mode,
+// save_location,
+// database,
+// } => {
+// std::fs::create_dir_all(&save_location)?;
+
+// let da = DataAcid::open(&database)?;
+
+// info!("processing 'user'");
+// process_tree(mode, &save_location.join("user"), &da, T_USER);
+// info!("processing 'user_node'");
+// process_tree(mode, &save_location.join("user_node"), &da, T_USER_NODE);
+// info!("processing 'invite'");
+// process_tree(mode, &save_location.join("invite"), &da, T_INVITE);
+// info!("done");
+
+// Ok(())
+// }
+// _ => unreachable!(),
+// }
+// }
+
pub(crate) fn migrate(action: Action) -> anyhow::Result<()> {
match action {
Action::Migrate {
@@ -23,73 +97,261 @@ pub(crate) fn migrate(action: Action) -> anyhow::Result<()> {
database,
} => {
std::fs::create_dir_all(&save_location)?;
- let db = Database::open(&database)?;
+
+ let da = DataAcid::open(&database)?;
info!("processing 'user'");
- process_tree(mode, &save_location.join("user"), &db.user)?;
+ {
+ let path: &Path = &save_location.join("user");
+ let da = &da;
+ match mode {
+ MigrateMode::Export => {
+ let mut o = BufWriter::new(File::create(path)?);
+ let txn = da.begin_read()?;
+ let table = txn.open_table(T_USER)?;
+
+ let len = table.len()?;
+ for r in table.iter()?.progress_count(len.try_into().unwrap()) {
+ let (k, v) = r?;
+ serde_json::to_writer(&mut o, &(k.value(), v.value().0))?;
+ writeln!(&mut o)?;
+ }
+ drop(table);
+ }
+ MigrateMode::Import => {
+ {
+ let txn = da.begin_read()?;
+ let table = txn.open_table(T_USER)?;
+ if !table.is_empty()? {
+ bail!("tree not empty, `rm -rf` your db please :)")
+ }
+ }
+
+ let Ok(i) = File::open(path) else {
+ warn!(
+ "{path:?} does not exist; the import of that tree will be skipped."
+ );
+ return Ok(());
+ };
+ let i = BufReader::new(i);
+ for l in i.lines() {
+ let l = l?;
+ let (k, v) = serde_json::from_str::<(String, User)>(l.as_str())
+ .context("reading db dump item")?;
+ {
+ let txn = da.begin_write()?;
+ let mut table = txn.open_table(T_USER)?;
+ table.insert(k.as_str(), Ser(v))?;
+ drop(table);
+ txn.commit()?
+ }
+ }
+ }
+ }
+ };
info!("processing 'user_node'");
- process_tree(mode, &save_location.join("user_node"), &db.user_node)?;
+ {
+ let path: &Path = &save_location.join("user_node");
+ let da = &da;
+ match mode {
+ MigrateMode::Export => {
+ let mut o = BufWriter::new(File::create(path)?);
+ let txn = da.begin_read()?;
+ let table = txn.open_table(T_USER_NODE)?;
+
+ let len = table.len()?;
+ for r in table.iter()?.progress_count(len.try_into().unwrap()) {
+ let (k, v) = r?;
+ serde_json::to_writer(&mut o, &(k.value(), v.value().0))?;
+ writeln!(&mut o)?;
+ }
+ drop(table);
+ }
+ MigrateMode::Import => {
+ {
+ let txn = da.begin_read()?;
+ let table = txn.open_table(T_USER_NODE)?;
+ if !table.is_empty()? {
+ bail!("tree not empty, `rm -rf` your db please :)")
+ }
+ }
+
+ let Ok(i) = File::open(path) else {
+ warn!(
+ "{path:?} does not exist; the import of that tree will be skipped."
+ );
+ return Ok(());
+ };
+ let i = BufReader::new(i);
+ for l in i.lines() {
+ let l = l?;
+ let (k, v) = serde_json::from_str::<((String, String), NodeUserData)>(
+ l.as_str(),
+ )
+ .context("reading db dump item")?;
+ {
+ let txn = da.begin_write()?;
+ let mut table = txn.open_table(T_USER_NODE)?;
+
+ table.insert((k.0.as_str(), k.1.as_str()), Ser(v))?;
+ drop(table);
+ txn.commit()?
+ }
+ }
+ }
+ }
+ };
info!("processing 'invite'");
- process_tree(mode, &save_location.join("invite"), &db.invite)?;
- info!("processing 'node'");
- process_tree(mode, &save_location.join("node"), &db.node)?;
+ {
+ let path: &Path = &save_location.join("invite");
+ let da = &da;
+ match mode {
+ MigrateMode::Export => {
+ let mut o = BufWriter::new(File::create(path)?);
+ let txn = da.begin_read()?;
+ let table = txn.open_table(T_INVITE)?;
+
+ let len = table.len()?;
+ for r in table.iter()?.progress_count(len.try_into().unwrap()) {
+ let (k, v) = r?;
+ serde_json::to_writer(&mut o, &(k.value(), v.value().0))?;
+ writeln!(&mut o)?;
+ }
+ drop(table);
+ }
+ MigrateMode::Import => {
+ {
+ let txn = da.begin_read()?;
+ let table = txn.open_table(T_INVITE)?;
+ if !table.is_empty()? {
+ bail!("tree not empty, `rm -rf` your db please :)")
+ }
+ }
+
+ let Ok(i) = File::open(path) else {
+ warn!(
+ "{path:?} does not exist; the import of that tree will be skipped."
+ );
+ return Ok(());
+ };
+ let i = BufReader::new(i);
+ for l in i.lines() {
+ let l = l?;
+ let (k, _v) = serde_json::from_str::<(String, ())>(l.as_str())
+ .context("reading db dump item")?;
+ {
+ let txn = da.begin_write()?;
+ let mut table = txn.open_table(T_INVITE)?;
+
+ table.insert(k.as_str(), Ser(()))?;
+ drop(table);
+ txn.commit()?
+ }
+ }
+ }
+ }
+ };
info!("done");
- Ok(())
}
_ => unreachable!(),
}
+ Ok(())
}
+/*
-fn process_tree<
- K: Serialize + for<'de> serde::Deserialize<'de>,
- V: Serialize + for<'de> serde::Deserialize<'de>,
->(
+fn process_tree<'c, 'd, K, V>(
mode: MigrateMode,
path: &Path,
- tree: &Tree<K, V>,
-) -> anyhow::Result<()> {
+ da: &DataAcid,
+ table: TableDefinition<'static, K, Ser<V>>,
+) -> anyhow::Result<()>
+where
+ K: RedbKey + Owny<'c> + Clone,
+ V: Encode + Decode + Debug + Serialize + Owny<'d> + Clone,
+ <K as Owny<'c>>::Owned: for<'a> serde::Deserialize<'a>,
+ <V as Owny<'d>>::Owned: for<'a> serde::Deserialize<'a>,
+{
match mode {
- MigrateMode::Export => export_tree(path, tree),
- MigrateMode::Import => import_tree(path, tree),
- }
-}
+ MigrateMode::Export => {
+ // let mut o = BufWriter::new(File::create(path)?);
+ // let txn = da.begin_read()?;
+ // let table = txn.open_table(table)?;
-fn export_tree<
- K: Serialize + for<'de> serde::Deserialize<'de>,
- V: Serialize + for<'de> serde::Deserialize<'de>,
->(
- path: &Path,
- tree: &Tree<K, V>,
-) -> anyhow::Result<()> {
- let mut o = BufWriter::new(File::create(path)?);
- let len = tree.len();
- for r in tree.iter().progress_count(len.try_into().unwrap()) {
- let (k, v) = r?;
- serde_json::to_writer(&mut o, &(k, v))?;
- writeln!(&mut o)?;
- }
- Ok(())
-}
+ // let len = table.len()?;
+ // for r in table.iter()?.progress_count(len.try_into().unwrap()) {
+ // let (k, v) = r?;
+ // serde_json::to_writer(&mut o, &(k, v.value().0))?;
+ // writeln!(&mut o)?;
+ // }
+ // drop(table);
+ }
+ MigrateMode::Import => {
+ {
+ let txn = da.begin_read()?;
+ let table = txn.open_table(table)?;
+ if !table.is_empty()? {
+ bail!("tree not empty, `rm -rf` your db please :)")
+ }
+ }
-fn import_tree<
- K: Serialize + for<'de> serde::Deserialize<'de>,
- V: Serialize + for<'de> serde::Deserialize<'de>,
->(
- path: &Path,
- tree: &Tree<K, V>,
-) -> anyhow::Result<()> {
- if !tree.is_empty() {
- bail!("tree not empty, `rm -rf` your db please :)")
- }
- let Ok(i) = File::open(path) else {
- warn!("{path:?} does not exist; the import of that tree will be skipped.");
- return Ok(());
- };
- let i = BufReader::new(i);
- for l in i.lines() {
- let l = l?;
- let (k, v) = serde_json::from_str::<(K, V)>(&l).context("reading db dump item")?;
- tree.insert(&k, &v)?;
+ let Ok(i) = File::open(path) else {
+ warn!("{path:?} does not exist; the import of that tree will be skipped.");
+ return Ok(());
+ };
+ let i = BufReader::new(i);
+ for l in i.lines() {
+ let l = l?;
+ let (k, v) =
+ serde_json::from_str::<(<K as Owny>::Owned, <V as Owny>::Owned)>(l.as_str())
+ .context("reading db dump item")?;
+ {
+ let (k, v) = (k.borrow(), v.borrow());
+
+ let txn = da.begin_write()?;
+ let mut table = txn.open_table(table)?;
+
+ table.insert(k, Ser(v))?;
+ drop(table);
+ txn.commit()?
+ }
+ }
+ }
}
Ok(())
-}
+} */
+
+// trait Owny<'a> {
+// type Owned;
+// fn borrow(x: &'a Self::Owned) -> Self;
+// }
+// impl<'a> Owny<'a> for &'a str {
+// type Owned = String;
+// fn borrow(x: &'a Self::Owned) -> Self {
+// x.as_str()
+// }
+// }
+// impl<'a> Owny<'a> for (&'a str, &'a str) {
+// type Owned = (String, String);
+
+// fn borrow(x: &'a Self::Owned) -> Self {
+// (x.0.as_str(), x.1.as_str())
+// }
+// }
+// impl Owny<'_> for User {
+// type Owned = User;
+// fn borrow(x: &Self::Owned) -> Self {
+// x.to_owned()
+// }
+// }
+// impl Owny<'_> for NodeUserData {
+// type Owned = NodeUserData;
+// fn borrow(x: &Self::Owned) -> Self {
+// x.to_owned()
+// }
+// }
+// impl Owny<'_> for () {
+// type Owned = ();
+// fn borrow(x: &Self::Owned) -> Self {
+// x.to_owned()
+// }
+// }