From 4b5dea576fbfd8445d18486ebece46241255d385 Mon Sep 17 00:00:00 2001 From: servius Date: Wed, 28 Jan 2026 01:38:14 +0530 Subject: [PATCH] feat: Added AGENTS.md --- AGENTS.md | 199 ++++++++++++++++++++++++++++++++++++++++ Cargo.lock | 135 ---------------------------- store/Cargo.toml | 2 - store/src/redb.rs | 225 ---------------------------------------------- 4 files changed, 199 insertions(+), 362 deletions(-) create mode 100644 AGENTS.md delete mode 100644 store/src/redb.rs diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2c8a569 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,199 @@ +# Agent Guidelines for Jello + +This document provides guidelines for AI coding agents working on the Jello codebase. + +## Project Overview + +Jello is a WIP video client for Jellyfin written in Rust, focusing on HDR video playback using: +- **iced** - Primary GUI toolkit +- **gstreamer** - Video + audio decoding library +- **wgpu** - Rendering video from GStreamer in iced + +## Build, Test, and Lint Commands + +### Building +```bash +# Build in release mode +cargo build --release +cargo build -r + +# Build specific workspace member +cargo build -p api +cargo build -p gst +cargo build -p ui-iced + +# Run the application +cargo run --release -- -vv +just jello # Uses justfile +``` + +### Testing +```bash +# Run all tests in workspace +cargo test --workspace + +# Run tests for a specific package +cargo test -p gst +cargo test -p api +cargo test -p iced-video + +# Run a single test by name +cargo test test_appsink +cargo test -p gst test_appsink + +# Run a specific test in a specific file +cargo test -p gst --test + +# Run tests with output +cargo test -- --nocapture +cargo test -- --show-output +``` + +### Linting and Formatting +```bash +# Check code without building +cargo check +cargo check --workspace + +# Run clippy (linter) +cargo clippy +cargo clippy --workspace +cargo clippy --workspace -- -D warnings + +# Format code +cargo fmt +cargo fmt --all + +# Check formatting without modifying files +cargo fmt --all -- --check +``` + +### Other Tools +```bash +# Check for security vulnerabilities and license compliance +cargo deny check + +# Generate Jellyfin type definitions +just typegen +``` + +## Code Style Guidelines + +### Rust Edition +- Use **Rust 2024 edition** (as specified in Cargo.toml files) + +### Imports +- Use `use` statements at the top of files +- Group imports: std library, external crates, then local modules +- Use `crate::` for absolute paths within the crate +- Common pattern: create a `priv_prelude` module for internal imports +- Use `pub use` to re-export commonly used items +- Use wildcard imports (`use crate::priv_prelude::*;`) within internal modules when a prelude exists + +Example: +```rust +use std::sync::Arc; + +use reqwest::{Method, header::InvalidHeaderValue}; +use serde::{Deserialize, Serialize}; + +use crate::errors::*; +``` + +### Naming Conventions +- **Types/Structs/Enums**: PascalCase (e.g., `JellyfinClient`, `Error`, `AppSink`) +- **Functions/Methods**: snake_case (e.g., `request_builder`, `stream_url`) +- **Variables**: snake_case (e.g., `access_token`, `device_id`) +- **Constants**: SCREAMING_SNAKE_CASE (e.g., `NEXT_ID`, `GST`) +- **Modules**: snake_case (e.g., `priv_prelude`, `error_stack`) + +### Error Handling +- Use **`error-stack`** for error handling with context propagation +- Use **`thiserror`** for defining error types +- Standard error type pattern: +```rust +pub use error_stack::{Report, ResultExt}; + +#[derive(Debug, thiserror::Error)] +#[error("An error occurred")] +pub struct Error; + +pub type Result> = core::result::Result; +``` +- Attach context to errors using `.change_context(Error)` and `.attach("description")` +- Use `#[track_caller]` on functions that may panic or error for better error messages +- Error handling example: +```rust +self.inner + .set_state(gstreamer::State::Playing) + .change_context(Error) + .attach("Failed to set pipeline to Playing state")?; +``` + +### Types +- Prefer explicit types over type inference when it improves clarity +- Use `impl Trait` for function parameters when appropriate (e.g., `impl AsRef`) +- Use `Option` and `Result` idiomatically +- Use `Arc` for shared ownership +- Use newtype patterns for semantic clarity (e.g., `ApiKey` wrapping `secrecy::SecretBox`) + +### Formatting +- Use 4 spaces for indentation +- Line length: aim for 100 characters, but not strictly enforced +- Use trailing commas in multi-line collections +- Follow standard Rust formatting conventions (enforced by `cargo fmt`) + +### Documentation +- Add doc comments (`///`) for public APIs +- Use inline comments (`//`) sparingly, prefer self-documenting code +- Include examples in doc comments when helpful + +### Async/Await +- Use `tokio` as the async runtime +- Mark async functions with `async` keyword +- Use `.await` for async operations +- Common pattern: `tokio::fs` for file operations + +### Module Structure +- Use `mod.rs` or inline modules as appropriate +- Keep related functionality together +- Use `pub(crate)` for internal APIs +- Re-export commonly used items at crate root + +### Macros +- Custom macros used: `wrap_gst!`, `parent_child!` +- Use macros for reducing boilerplate, only in the `gst` crate + +### Testing +- Place tests in the same file with `#[test]` or `#[cfg(test)]` +- Use descriptive test function names (e.g., `test_appsink`, `unique_generates_different_ids`) +- Initialize tracing in tests when needed for debugging + +### Dependencies +- Prefer well-maintained crates from crates.io +- Use `workspace.dependencies` for shared dependencies across workspace members +- Pin versions when stability is important + +### Workspace Structure +The project uses a Cargo workspace with multiple members: +- `.` - Main jello binary +- `api` - Jellyfin API client +- `gst` - GStreamer wrapper +- `ui-iced` - Iced UI implementation +- `ui-gpui` - GPUI UI implementation (optional) +- `store` - Secret/data/storage management +- `jello-types` - Shared type definitions +- `typegen` - Jellyfin type generator +- `crates/iced-video` - Custom iced video widget +- `examples/hdr-gstreamer-wgpu` - HDR example + +### Project-Specific Patterns +- Use `LazyLock` for global initialization (e.g., GStreamer init) +- Use the builder pattern with method chaining (e.g., `request_builder()`) +- Use `tap` crate's `.pipe()` for functional transformations +- Prefer `BTreeMap`/`BTreeSet` over `HashMap`/`HashSet` when order matters +- Prefer a functional programming style instead of an imperative one. +- When building UIs keep the handler and view code in the same module (eg. settings view and settings handle in the same file) + +## License +All code in this project is MIT licensed. diff --git a/Cargo.lock b/Cargo.lock index 6d831d9..27e3133 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -747,18 +747,6 @@ dependencies = [ "core2", ] -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "blade-graphics" version = "0.7.0" @@ -878,28 +866,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e79769241dcd44edf79a732545e8b5cec84c247ac060f5252cd51885d093a8fc" -[[package]] -name = "bson" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3f109694c4f45353972af96bf97d8a057f82e2d6e496457f4d135b9867a518c" -dependencies = [ - "ahash", - "base64", - "bitvec", - "getrandom 0.3.4", - "hex", - "indexmap", - "js-sys", - "rand 0.9.2", - "serde", - "serde_bytes", - "simdutf8", - "thiserror 2.0.18", - "time", - "uuid", -] - [[package]] name = "bstr" version = "1.12.1" @@ -1747,15 +1713,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" -[[package]] -name = "deranged" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", -] - [[package]] name = "derive_more" version = "0.99.20" @@ -2341,12 +2298,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futf" version = "0.1.5" @@ -4833,12 +4784,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-derive" version = "0.4.2" @@ -5733,12 +5678,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -5960,12 +5899,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.8.5" @@ -6142,16 +6075,6 @@ dependencies = [ "font-types", ] -[[package]] -name = "redb" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae323eb086579a3769daa2c753bb96deb95993c534711e0dbe881b5192906a06" -dependencies = [ - "libc", - "uuid", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -6674,16 +6597,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" -dependencies = [ - "serde", - "serde_core", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -6849,12 +6762,6 @@ dependencies = [ "quote", ] -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - [[package]] name = "simplecss" version = "0.2.2" @@ -7129,10 +7036,8 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" name = "store" version = "0.1.0" dependencies = [ - "bson", "futures", "parking_lot", - "redb", "secrecy", "serde", "tokio", @@ -7547,37 +7452,6 @@ dependencies = [ "zune-jpeg 0.4.21", ] -[[package]] -name = "time" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" - -[[package]] -name = "time-macros" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tiny-skia" version = "0.11.4" @@ -9511,15 +9385,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "x11-dl" version = "2.21.0" diff --git a/store/Cargo.toml b/store/Cargo.toml index 7dc01d4..3b6c592 100644 --- a/store/Cargo.toml +++ b/store/Cargo.toml @@ -4,10 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] -bson = { version = "3.1.0", features = ["serde"] } futures = "0.3.31" parking_lot = "0.12.5" -redb = { version = "3.1.0", features = ["uuid"] } secrecy = "0.10.3" serde = "1.0.228" tokio = { version = "1.48.0", features = ["rt"] } diff --git a/store/src/redb.rs b/store/src/redb.rs deleted file mode 100644 index 7f9ce0f..0000000 --- a/store/src/redb.rs +++ /dev/null @@ -1,225 +0,0 @@ -// use std::{ -// borrow::Borrow, -// collections::VecDeque, -// marker::PhantomData, -// path::Path, -// sync::{Arc, RwLock, atomic::AtomicBool}, -// }; -// -// use futures::task::AtomicWaker; -// use redb::{Error, Key, ReadableDatabase, TableDefinition, Value}; -// use serde::{Serialize, de::DeserializeOwned}; -// -// const USERS: TableDefinition> = TableDefinition::new("users"); -// const SERVERS: TableDefinition> = TableDefinition::new("servers"); -// const SETTINGS: TableDefinition> = TableDefinition::new("settings"); -// -// #[derive(Debug)] -// pub struct TableInner { -// db: Arc, -// } -// -// impl Clone for TableInner { -// fn clone(&self) -> Self { -// Self { -// db: Arc::clone(&self.db), -// } -// } -// } -// -// impl TableInner { -// fn new(db: Arc) -> Self { -// Self { db } -// } -// } -// -// impl TableInner { -// async fn get<'a, K: Key, V: Serialize + DeserializeOwned>( -// &self, -// table: TableDefinition<'static, K, Vec>, -// key: impl Borrow>, -// ) -> Result> { -// let db: &redb::Database = &self.db.as_ref().database; -// let db_reader = db.begin_read()?; -// let table = db_reader.open_table(table)?; -// table -// .get(key)? -// .map(|value| bson::deserialize_from_slice(&value.value())) -// .transpose() -// .map_err(|e| redb::Error::Io(std::io::Error::other(e))) -// } -// -// async fn insert< -// 'a, -// 'b, -// K: Key + Send + Sync, -// V: Serialize + DeserializeOwned + Send + Sync + 'a, -// >( -// &'b self, -// table: TableDefinition<'static, K, Vec>, -// key: impl Borrow> + Send + 'b, -// value: V, -// ) -> Result> { -// let db: &redb::Database = &self.db.as_ref().database; -// // self.db -// // .writing -// // .store(true, std::sync::atomic::Ordering::SeqCst); -// -// // let out = tokio::task::spawn_blocking(move || -> Result> -// -// let out = tokio::task::spawn_blocking(|| -> Result> { -// let db_writer = db.begin_write()?; -// let out = { -// let mut table = db_writer.open_table(table)?; -// let serialized_value = bson::serialize_to_vec(&value) -// .map_err(|e| redb::Error::Io(std::io::Error::other(e)))?; -// let previous = table.insert(key, &serialized_value)?; -// let out = previous -// .map(|value| bson::deserialize_from_slice(&value.value())) -// .transpose() -// .map_err(|e| redb::Error::Io(std::io::Error::other(e))); -// out -// }; -// db_writer.commit()?; -// out -// }) -// .await -// .expect("Task panicked"); -// -// out -// } -// } -// -// // impl Table for TableInner { -// // async fn get(&self, key: K) -> Result> {} -// // async fn insert(&self, key: K, value: V) -> Result> {} -// // async fn modify(&self, key: K, v: FnOnce(V) -> V) -> Result {} -// // async fn remove(&self, key: K) -> Result> {} -// // } -// -// #[derive(Debug)] -// pub struct Users(TableInner); -// -// impl Clone for Users { -// fn clone(&self) -> Self { -// Self(self.0.clone()) -// } -// } -// impl Users { -// const TABLE: TableDefinition<'static, uuid::Uuid, Vec> = USERS; -// } -// -// #[derive(Debug)] -// pub struct Servers(TableInner); -// impl Clone for Servers { -// fn clone(&self) -> Self { -// Self(self.0.clone()) -// } -// } -// impl Servers { -// const TABLE: TableDefinition<'static, uuid::Uuid, Vec> = SERVERS; -// } -// -// #[derive(Debug)] -// pub struct Settings(TableInner); -// impl Clone for Settings { -// fn clone(&self) -> Self { -// Self(self.0.clone()) -// } -// } -// impl Settings { -// const TABLE: TableDefinition<'static, uuid::Uuid, Vec> = SETTINGS; -// } -// -// #[derive(Debug, Clone)] -// pub struct Database { -// users: Users, -// servers: Servers, -// settings: Settings, -// handle: Arc, -// } -// -// #[derive(Debug)] -// pub struct DatabaseHandle { -// database: redb::Database, -// writing: AtomicBool, -// wakers: RwLock>, -// } -// -// #[derive(Debug)] -// pub struct DatabaseWriterGuard<'a> { -// handle: &'a DatabaseHandle, -// dropper: Arc, -// } -// -// // impl Drop for DatabaseWriterGuard<'_> { -// // fn drop(&mut self) { -// // self.handle -// // .writing -// // .store(false, std::sync::atomic::Ordering::SeqCst); -// // let is_panicking = std::thread::panicking(); -// // let Ok(writer) = self.handle.wakers.write() else { -// // if is_panicking { -// // return; -// // } else { -// // panic!("Wakers lock poisoned"); -// // } -// // } -// // if let Some(waker) = (self.handle.wakers.write()).pop() { -// // waker.wake(); -// // }; -// // // let mut wakers = self.handle.wakers.write().expect(); -// // // if let Some(waker) = self.handle.wakers.write().expect("Wakers lock poisoned").pop_front() { -// // // waker.wake(); -// // // } -// // // while let Some(waker) = wakers.pop_front() { -// // // waker.wake(); -// // // } -// // } -// // } -// -// type Result = core::result::Result; -// -// pub trait Table { -// fn insert( -// &self, -// key: K, -// value: V, -// ) -> impl Future>> + Send; -// fn modify( -// &self, -// key: K, -// v: impl FnOnce(V) -> O, -// ) -> impl Future> + Send; -// fn remove( -// &self, -// key: K, -// ) -> impl Future>> + Send; -// fn get( -// &self, -// key: K, -// ) -> impl Future>> + Send; -// } -// -// impl Database { -// pub fn create(path: impl AsRef) -> Result { -// let writing = AtomicBool::new(false); -// let wakers = RwLock::new(VecDeque::new()); -// let db = redb::Database::create(path)?; -// let db = Arc::new(DatabaseHandle { -// database: db, -// writing, -// wakers, -// }); -// let table_inner = TableInner::new(Arc::clone(&db)); -// let users = Users(table_inner.clone()); -// let servers = Servers(table_inner.clone()); -// let settings = Settings(table_inner.clone()); -// Ok(Self { -// servers, -// users, -// settings, -// handle: db, -// }) -// } -// }