From bc34326b935d8d460c3a14951237a744d12d7de3 Mon Sep 17 00:00:00 2001 From: pommicket Date: Tue, 13 Aug 2024 00:08:45 -0400 Subject: start migration to postgres --- server/Cargo.lock | 414 ++++++++++++++++++++++++++++++++++++++++++----------- server/Cargo.toml | 3 +- server/src/main.rs | 281 ++++++++++++------------------------ 3 files changed, 421 insertions(+), 277 deletions(-) diff --git a/server/Cargo.lock b/server/Cargo.lock index 610d853..ee52661 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -38,12 +49,24 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "block-buffer" version = "0.10.4" @@ -53,6 +76,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -86,30 +115,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - [[package]] name = "crypto-common" version = "0.1.6" @@ -134,8 +139,15 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fnv" version = "1.0.7" @@ -143,13 +155,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "fs2" -version = "0.4.3" +name = "futures-channel" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ - "libc", - "winapi", + "futures-core", + "futures-sink", ] [[package]] @@ -196,15 +208,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -238,6 +241,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "1.1.0" @@ -255,15 +267,6 @@ version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "itoa" version = "1.0.11" @@ -276,10 +279,20 @@ version = "0.1.0" dependencies = [ "futures-util", "rand", - "sled", "tokio", + "tokio-postgres", "tokio-tungstenite", "tungstenite", + "zerocopy", +] + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", ] [[package]] @@ -304,6 +317,16 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -340,29 +363,57 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.6" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", - "instant", "libc", - "redox_syscall", + "redox_syscall 0.5.3", "smallvec", - "winapi", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", ] [[package]] @@ -377,6 +428,35 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "postgres-protocol" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acda0ebdebc28befa84bee35e651e4c5f09073d668c7aed4cf7e23c3cda84b23" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02048d9e032fb3cc3413bbf7b83a15d84a5d419778e2628751896d856498eee9" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -436,11 +516,20 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -466,6 +555,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -475,6 +575,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -484,22 +590,6 @@ dependencies = [ "autocfg", ] -[[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", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -516,6 +606,23 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.72" @@ -547,6 +654,21 @@ dependencies = [ "syn", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.39.2" @@ -575,6 +697,32 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-postgres" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03adcf0147e203b6032c0b2d30be1415ba03bc348901f3ff1cc0df6a733e60c3" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand", + "socket2", + "tokio", + "tokio-util", + "whoami", +] + [[package]] name = "tokio-tungstenite" version = "0.23.1" @@ -587,6 +735,19 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tungstenite" version = "0.23.0" @@ -611,12 +772,33 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + [[package]] name = "utf-8" version = "0.7.6" @@ -636,26 +818,86 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "winapi" -version = "0.3.9" +name = "wasite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "cfg-if", + "once_cell", + "wasm-bindgen-macro", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "wasm-bindgen-backend" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "wasm-bindgen-macro-support" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "wasite", + "web-sys", +] [[package]] name = "windows-sys" diff --git a/server/Cargo.toml b/server/Cargo.toml index 130968f..642bbe1 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" [dependencies] futures-util = "0.3" rand = { version = "0.8.5", features = ["std", "std_rng"] } -sled = "0.34.7" tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread", "net", "io-util", "sync", "time", "process"] } +tokio-postgres = "0.7.11" tokio-tungstenite = "0.23.1" tungstenite = "0.23.0" +zerocopy = "0.7.35" diff --git a/server/src/main.rs b/server/src/main.rs index f695910..eb9674b 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,3 +1,6 @@ +#![allow(dead_code)] // TODO : delete me +#![allow(unused_variables)] // TODO : delete me + use futures_util::{SinkExt, StreamExt}; use rand::seq::SliceRandom; use rand::Rng; @@ -9,6 +12,7 @@ use std::time::{Duration, SystemTime}; use tokio::io::AsyncWriteExt; use tokio::sync::{Mutex, RwLock}; use tungstenite::protocol::Message; +use zerocopy::AsBytes; const PUZZLE_ID_CHARSET: &[u8] = b"23456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; const PUZZLE_ID_LEN: usize = 7; @@ -21,19 +25,48 @@ fn generate_puzzle_id() -> [u8; PUZZLE_ID_LEN] { #[derive(Debug)] struct Server { - puzzles: sled::Tree, - pieces: sled::Tree, - connectivity: sled::Tree, // keep this in memory, since we want to reset it to 0 when the server restarts player_counts: Mutex>, wikimedia_featured: Vec, wikimedia_potd: RwLock, + database: tokio_postgres::Client, +} + + +impl Server { + async fn create_table_if_not_exists(&self) -> Result<()> { + todo!() + } + async fn try_register_id(&self, id: [u8; PUZZLE_ID_LEN]) -> Result { + todo!() + } + async fn set_puzzle_data(&self, id: [u8; PUZZLE_ID_LEN], width: u8, height: u8, url: &str, nib_types: Vec, piece_positions: Vec, connectivity_data: Vec) -> Result<()> { + todo!() + } + async fn move_piece(&self, piece: usize, x: f32, y: f32) -> Result<()> { + todo!() + } + async fn connect_pieces(&self, piece1: usize, piece2: usize) -> Result<()> { + todo!() + } + async fn get_connectivity(&self, id: [u8; PUZZLE_ID_LEN]) -> Result> { + todo!() + } + async fn get_positions(&self, id: [u8; PUZZLE_ID_LEN]) -> Result> { + todo!() + } + async fn get_details(&self, id: [u8; PUZZLE_ID_LEN]) -> Result<(u8, u8, String)> { + todo!() + } + async fn sweep(&self) -> Result<()> { + todo!() + } } #[derive(Debug)] enum Error { Tungstenite(tungstenite::Error), - Sled(sled::Error), + Postgres(tokio_postgres::Error), IO(std::io::Error), UTF8(std::str::Utf8Error), BadPuzzleID, @@ -55,7 +88,7 @@ impl std::fmt::Display for Error { Error::TooManyPieces => write!(f, "too many pieces"), Error::NotJoined => write!(f, "haven't joined a puzzle"), Error::TooManyPlayers => write!(f, "too many players"), - Error::Sled(e) => write!(f, "{e}"), + Error::Postgres(e) => write!(f, "{e}"), Error::IO(e) => write!(f, "{e}"), Error::UTF8(e) => write!(f, "{e}"), Error::Tungstenite(e) => write!(f, "{e}"), @@ -63,9 +96,9 @@ impl std::fmt::Display for Error { } } -impl From for Error { - fn from(value: sled::Error) -> Self { - Self::Sled(value) +impl From for Error { + fn from(value: tokio_postgres::Error) -> Self { + Self::Postgres(value) } } @@ -87,21 +120,21 @@ impl From for Error { type Result = std::result::Result; -fn get_puzzle_info(server: &Server, id: &[u8]) -> Result> { - if id.len() != PUZZLE_ID_LEN { - return Err(Error::BadPuzzleID); - } - let mut data = vec![1, 0, 0, 0, 0, 0, 0, 0]; // opcode + padding - let puzzle = server.puzzles.get(id)?.ok_or(Error::BadPuzzleID)?; - data.extend_from_slice(&puzzle); +async fn get_puzzle_info(server: &Server, id: &[u8]) -> Result> { + let id: [u8; PUZZLE_ID_LEN] = id.try_into().map_err(|_| Error::BadPuzzleID)?; + let mut data = vec![1]; + let (width, height, url) = server.get_details(id).await?; + data.push(width); + data.push(height); + data.extend(url.as_bytes()); while data.len() % 8 != 0 { // padding data.push(0); } - let pieces = server.pieces.get(id)?.ok_or(Error::BadPuzzleID)?; - data.extend_from_slice(&pieces); - let connectivity = server.connectivity.get(id)?.ok_or(Error::BadPuzzleID)?; - data.extend_from_slice(&connectivity); + let pieces = server.get_positions(id).await?; + data.extend_from_slice(pieces.as_bytes()); + let connectivity = server.get_connectivity(id).await?; + data.extend_from_slice(connectivity.as_bytes()); Ok(data) } @@ -136,82 +169,46 @@ async fn handle_websocket( if (width as u16) * (height as u16) > 1000 { return Err(Error::TooManyPieces); } - let mut puzzle_data = vec![width, height]; - let timestamp: u64 = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("time went backwards :/") - .as_secs(); - for byte in timestamp.to_le_bytes() { - puzzle_data.push(byte); - } - // pick nib types + let nib_count = 2 * (width as usize) * (height as usize) - (width as usize) - (height as usize); + let mut nib_types: Vec = Vec::with_capacity(nib_count); + let mut piece_positions: Vec = Vec::with_capacity((width as usize) * (height as usize) * 2); { let mut rng = rand::thread_rng(); - for _ in 0..2u16 * (width as u16) * (height as u16) - - (width as u16) - (height as u16) - { - puzzle_data.push(rng.gen()); - puzzle_data.push(rng.gen()); + // pick nib types + for _ in 0..nib_count { + nib_types.push(rng.gen()); } - } - // URL - puzzle_data.extend(url.as_bytes()); - puzzle_data.push(0); - // puzzle ID - let mut id; - loop { - id = generate_puzzle_id(); - let data = std::mem::take(&mut puzzle_data); - if server - .puzzles - .compare_and_swap(id, None::<&'static [u8; 0]>, Some(&data[..]))? - .is_ok() - { - break; - } - } - drop(puzzle_data); // should be empty now - *puzzle_id = Some(id); - let pieces_data: Box<[u8]>; - { - let mut rng = rand::thread_rng(); - let mut positions = vec![]; - positions.reserve_exact((width as usize) * (height as usize)); - // positions + // pick piece positions for y in 0..(height as u16) { for x in 0..(width as u16) { let dx: f32 = rng.gen_range(0.0..0.5); let dy: f32 = rng.gen_range(0.0..0.5); - positions.push([ - (x as f32 + dx) / ((width + 1) as f32), - (y as f32 + dy) / ((height + 1) as f32), - ]); + piece_positions.push((x as f32 + dx) / ((width + 1) as f32)); + piece_positions.push((y as f32 + dy) / ((height + 1) as f32)); } } - positions.shuffle(&mut rng); - // rust isn't smart enough to do the zero-copy with f32::to_le_bytes and Vec::into_flattened - let ptr: *mut [[f32; 2]] = Box::into_raw(positions.into_boxed_slice()); - let ptr: *mut [u8] = std::ptr::slice_from_raw_parts_mut( - ptr.cast(), - (width as usize) * (height as usize) * 8, - ); - // evil unsafe code >:3 - pieces_data = unsafe { Box::from_raw(ptr) }; + piece_positions.shuffle(&mut rng); } - server.pieces.insert(id, pieces_data)?; - let mut connectivity_data = - Vec::with_capacity((width as usize) * (height as usize) * 2); + let mut connectivity_data: Vec = + Vec::with_capacity((width as usize) * (height as usize)); for i in 0..(width as u16) * (height as u16) { - connectivity_data.extend(i.to_le_bytes()); + connectivity_data.push(i); } - server.connectivity.insert(id, connectivity_data)?; + let mut id; + loop { + id = generate_puzzle_id(); + if server.try_register_id(id).await? { + break; + } + } + server.set_puzzle_data(id, width, height, url, nib_types, piece_positions, connectivity_data).await?; server.player_counts.lock().await.insert(id, 1); ws.send(Message::Text(format!( "id: {}", std::str::from_utf8(&id).expect("puzzle ID has bad utf-8???") ))) .await?; - let info = get_puzzle_info(server, &id)?; + let info = get_puzzle_info(server, &id).await?; ws.send(Message::Binary(info)).await?; } else if let Some(id) = text.strip_prefix("join ") { let id = id.as_bytes().try_into().map_err(|_| Error::BadSyntax)?; @@ -223,17 +220,10 @@ async fn handle_websocket( *entry += 1; drop(player_counts); // release lock *puzzle_id = Some(id); - let info = get_puzzle_info(server, &id)?; + let info = get_puzzle_info(server, &id).await?; ws.send(Message::Binary(info)).await?; } else if text.starts_with("move ") { let puzzle_id = puzzle_id.ok_or(Error::NotJoined)?; - #[derive(Clone, Copy)] - struct Motion { - piece: usize, - x: f32, - y: f32, - } - let mut motions = vec![]; for line in text.split('\n') { let mut parts = line.split(' '); parts.next(); // skip "move" @@ -252,29 +242,7 @@ async fn handle_websocket( .ok_or(Error::BadSyntax)? .parse() .map_err(|_| Error::BadSyntax)?; - motions.push(Motion { piece, x, y }); - } - let mut error = None; - server - .pieces - .fetch_and_update(puzzle_id, |curr_pieces: Option<&[u8]>| { - let Some(curr_pieces) = curr_pieces else { - error = Some(Error::BadPuzzleID); - return None; - }; - let mut new_pieces = curr_pieces.to_vec(); - for Motion { piece, x, y } in motions.iter().copied() { - let Some(slice) = new_pieces.get_mut(8 * piece..8 * piece + 8) else { - error = Some(Error::BadPieceID); - break; - }; - slice[0..4].copy_from_slice(&x.to_le_bytes()); - slice[4..8].copy_from_slice(&y.to_le_bytes()); - } - Some(new_pieces) - })?; - if let Some(error) = error { - return Err(error); + server.move_piece(piece, x, y).await?; } ws.send(Message::Text("ack".to_string())).await?; } else if let Some(data) = text.strip_prefix("connect ") { @@ -290,52 +258,12 @@ async fn handle_websocket( .ok_or(Error::BadSyntax)? .parse() .map_err(|_| Error::BadSyntax)?; - let mut error = None; - server - .connectivity - .fetch_and_update(puzzle_id, |curr_connectivity| { - let Some(curr_connectivity) = curr_connectivity else { - error = Some(Error::BadPuzzleID); - return None; - }; - let mut new_connectivity = curr_connectivity.to_vec(); - if piece1 >= curr_connectivity.len() / 2 - || piece2 >= curr_connectivity.len() / 2 - { - error = Some(Error::BadPieceID); - return Some(new_connectivity); - } - let piece2_group = u16::from_le_bytes([ - curr_connectivity[piece2 * 2], - curr_connectivity[piece2 * 2 + 1], - ]); - let a = curr_connectivity[piece1 * 2]; - let b = curr_connectivity[piece1 * 2 + 1]; - for piece in 0..curr_connectivity.len() / 2 { - let piece_group = u16::from_le_bytes([ - curr_connectivity[piece * 2], - curr_connectivity[piece * 2 + 1], - ]); - if piece_group == piece2_group { - new_connectivity[piece * 2] = a; - new_connectivity[piece * 2 + 1] = b; - } - } - Some(new_connectivity) - })?; - if let Some(error) = error { - return Err(error); - } + server.connect_pieces(piece1, piece2).await?; } else if text == "poll" { let puzzle_id = puzzle_id.ok_or(Error::NotJoined)?; - let pieces = server.pieces.get(puzzle_id)?.ok_or(Error::BadPuzzleID)?; - let connectivity = server - .connectivity - .get(puzzle_id)? - .ok_or(Error::BadPuzzleID)?; let mut data = vec![2, 0, 0, 0, 0, 0, 0, 0]; // opcode / version number + padding - data.extend_from_slice(&pieces); - data.extend_from_slice(&connectivity); + data.extend_from_slice(server.get_positions(puzzle_id).await?.as_bytes()); + data.extend_from_slice(server.get_connectivity(puzzle_id).await?.as_bytes()); ws.send(Message::Binary(data)).await?; } else if text == "randomFeaturedWikimedia" { let choice = rand::thread_rng().gen_range(0..server.wikimedia_featured.len()); @@ -427,22 +355,23 @@ async fn main() { let server_arc: Arc = Arc::new({ let wikimedia_featured = read_to_lines("featuredpictures.txt").expect("Couldn't read featuredpictures.txt"); - let db = sled::open("database.sled").expect("error opening database"); - let puzzles = db.open_tree("PUZZLES").expect("error opening puzzles tree"); - let pieces = db.open_tree("PIECES").expect("error opening pieces tree"); - let connectivity = db - .open_tree("CONNECTIVITY") - .expect("error opening connectivity tree"); let potd = get_potd().await; + let (client, connection) = tokio_postgres::connect("host=/var/run/postgresql dbname=jigsaw", tokio_postgres::NoTls).await.expect("Couldn't connect to database"); + + // docs say: "The connection object performs the actual communication with the database, so spawn it off to run on its own." + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); Server { - puzzles, - pieces, player_counts: Mutex::new(HashMap::new()), - connectivity, + database: client, wikimedia_potd: RwLock::new(potd), wikimedia_featured, } }); + server_arc.create_table_if_not_exists().await.expect("error creating table"); let server_arc_clone = server_arc.clone(); tokio::task::spawn(async move { let server: &Server = server_arc_clone.as_ref(); @@ -466,36 +395,8 @@ async fn main() { loop { // TODO : sweep let now = SystemTime::now(); - let mut to_delete = vec![]; - for item in server.puzzles.iter() { - let (key, value) = item.expect("sweep failed to read database"); - let timestamp: [u8; 8] = value[2..2 + 8].try_into().unwrap(); - let timestamp = - SystemTime::UNIX_EPOCH + Duration::from_secs(u64::from_le_bytes(timestamp)); - if now.duration_since(timestamp).unwrap_or_default() - >= Duration::from_secs(60 * 60 * 24 * 7) - { - // delete puzzles created at least 1 week ago - to_delete.push(key); - } - } - for key in to_delete { - // technically there is a race condition here but stop being silly - server - .puzzles - .remove(&key) - .expect("sweep failed to delete puzzle"); - server - .pieces - .remove(&key) - .expect("sweep failed to delete pieces"); - server - .connectivity - .remove(&key) - .expect("sweep failed to delete connectivity"); - if let Some(key) = <[u8; PUZZLE_ID_LEN]>::try_from(&key[..]).ok() { - server.player_counts.lock().await.remove(&key); - } + if let Err(e) = server.sweep().await { + eprintln!("error sweeping DB: {e}"); } tokio::time::sleep(std::time::Duration::from_secs(3600)).await; } -- cgit v1.2.3