Compare commits
3 Commits
4f2d311249
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e48eb8c91 | |||
| 72bf38a7ff | |||
| 97db96b105 |
199
AGENTS.md
199
AGENTS.md
@@ -1,199 +0,0 @@
|
||||
# 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 <test_file_name> <test_function_name>
|
||||
|
||||
# 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<T, E = error_stack::Report<Error>> = core::result::Result<T, E>;
|
||||
```
|
||||
- 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<str>`)
|
||||
- Use `Option<T>` and `Result<T, E>` idiomatically
|
||||
- Use `Arc<T>` for shared ownership
|
||||
- Use newtype patterns for semantic clarity (e.g., `ApiKey` wrapping `secrecy::SecretBox<String>`)
|
||||
|
||||
### 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.
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2348,12 +2348,6 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[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"
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -1,20 +1,20 @@
|
||||
[workspace]
|
||||
members = [
|
||||
".",
|
||||
"api",
|
||||
"typegen",
|
||||
"ui-gpui",
|
||||
"ui-iced",
|
||||
"store",
|
||||
"gst",
|
||||
"crates/api",
|
||||
"crates/gst",
|
||||
"crates/iced-video",
|
||||
"crates/store",
|
||||
"examples/hdr-gstreamer-wgpu",
|
||||
]
|
||||
[workspace.dependencies]
|
||||
iced = { version = "0.14.0" }
|
||||
gst = { version = "0.1.0", path = "gst" }
|
||||
iced_wgpu = { version = "0.14.0" }
|
||||
gst = { version = "0.1.0", path = "crates/gst" }
|
||||
iced-video = { version = "0.1.0", path = "crates/iced-video" }
|
||||
api = { version = "0.1.0", path = "crates/api" }
|
||||
|
||||
[patch.crates-io]
|
||||
iced_wgpu = { git = "https://github.com/uttarayan21/iced", branch = "0.14" }
|
||||
@@ -30,7 +30,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
api = { version = "0.1.0", path = "api" }
|
||||
api = { version = "0.1.0", path = "crates/api" }
|
||||
bytemuck = { version = "1.24.0", features = ["derive"] }
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
clap-verbosity-flag = { version = "3.0.4", features = ["tracing"] }
|
||||
|
||||
0
api/.gitignore → crates/api/.gitignore
vendored
0
api/.gitignore → crates/api/.gitignore
vendored
0
gst/.gitignore → crates/gst/.gitignore
vendored
0
gst/.gitignore → crates/gst/.gitignore
vendored
0
gst/Cargo.lock → crates/gst/Cargo.lock
generated
0
gst/Cargo.lock → crates/gst/Cargo.lock
generated
@@ -19,11 +19,3 @@ wgpu = { version = "27.0.1", features = ["vulkan"] }
|
||||
[dev-dependencies]
|
||||
iced.workspace = true
|
||||
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
||||
|
||||
[profile.dev]
|
||||
debug = true
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
# [patch.crates-io]
|
||||
# iced_wgpu = { git = "https://github.com/uttarayan21/iced", branch = "0.14" }
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use crate::{Error, Result, ResultExt};
|
||||
use gst::{
|
||||
Bus, Gst, MessageType, MessageView, Sink, Source,
|
||||
Bus, Gst, Sink,
|
||||
app::AppSink,
|
||||
caps::{Caps, CapsType},
|
||||
element::ElementExt,
|
||||
pipeline::PipelineExt,
|
||||
playback::{PlayFlags, Playbin3},
|
||||
videoconvertscale::VideoConvert,
|
||||
};
|
||||
use std::sync::{Arc, Mutex, atomic::AtomicBool};
|
||||
|
||||
|
||||
@@ -15,6 +15,3 @@ anyhow = "*"
|
||||
pollster = "0.4.0"
|
||||
tracing = { version = "0.1.43", features = ["log"] }
|
||||
tracing-subscriber = "0.3.22"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
62
gst/.github/workflows/build.yaml
vendored
62
gst/.github/workflows/build.yaml
vendored
@@ -1,62 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
checks-matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- id: set-matrix
|
||||
name: Generate Nix Matrix
|
||||
run: |
|
||||
set -Eeu
|
||||
matrix="$(nix eval --json '.#githubActions.matrix')"
|
||||
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
|
||||
|
||||
checks-build:
|
||||
needs: checks-matrix
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix: ${{fromJSON(needs.checks-matrix.outputs.matrix)}}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- run: nix build -L '.#${{ matrix.attr }}'
|
||||
|
||||
codecov:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: "write"
|
||||
contents: "read"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Run codecov
|
||||
run: nix build .#checks.x86_64-linux.hello-llvm-cov
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v4.0.1
|
||||
with:
|
||||
flags: unittests
|
||||
name: codecov-hello
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./result
|
||||
verbose: true
|
||||
|
||||
38
gst/.github/workflows/docs.yaml
vendored
38
gst/.github/workflows/docs.yaml
vendored
@@ -1,38 +0,0 @@
|
||||
name: docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: "write"
|
||||
contents: "read"
|
||||
pages: "write"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- uses: DeterminateSystems/flake-checker-action@main
|
||||
|
||||
- name: Generate docs
|
||||
run: nix build .#checks.x86_64-linux.hello-docs
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: result/share/doc
|
||||
|
||||
- name: Deploy to gh-pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub use error_stack::{Report, ResultExt};
|
||||
pub use error_stack::ResultExt;
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("An error occurred")]
|
||||
pub struct Error;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
mod cli;
|
||||
mod errors;
|
||||
use api::JellyfinConfig;
|
||||
use errors::*;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
|
||||
@@ -5,7 +5,7 @@ edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
api = { version = "0.1.0", path = "../api" }
|
||||
api = { workspace = true }
|
||||
blurhash = "0.2.3"
|
||||
bytes = "1.11.0"
|
||||
gpui_util = "0.2.2"
|
||||
|
||||
@@ -270,7 +270,6 @@ fn update(state: &mut State, message: Message) -> Task<Message> {
|
||||
}
|
||||
}
|
||||
Message::Video(msg) => video::update(state, msg),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,51 +441,7 @@ fn card(item: &Item) -> Element<'_, Message> {
|
||||
}
|
||||
|
||||
fn init() -> (State, Task<Message>) {
|
||||
// Create a default config for initial state
|
||||
|
||||
// let default_config = api::JellyfinConfig {
|
||||
// server_url: "http://localhost:8096".parse().expect("Valid URL"),
|
||||
// device_id: "jello-iced".to_string(),
|
||||
// device_name: "Jello Iced".to_string(),
|
||||
// client_name: "Jello".to_string(),
|
||||
// version: "0.1.0".to_string(),
|
||||
// };
|
||||
// let default_client = api::JellyfinClient::new_with_config(default_config);
|
||||
|
||||
(
|
||||
State::new(),
|
||||
Task::perform(
|
||||
async move {
|
||||
let config_str = std::fs::read_to_string("config.toml")
|
||||
.map_err(|e| api::JellyfinApiError::IoError(e))?;
|
||||
let config: api::JellyfinConfig = toml::from_str(&config_str).map_err(|e| {
|
||||
api::JellyfinApiError::IoError(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
e,
|
||||
))
|
||||
})?;
|
||||
|
||||
// Try to load cached token and authenticate
|
||||
match std::fs::read_to_string(".session") {
|
||||
Ok(token) => {
|
||||
let client = api::JellyfinClient::pre_authenticated(token.trim(), config)?;
|
||||
Ok((client, true))
|
||||
}
|
||||
Err(_) => {
|
||||
// No cached token, create unauthenticated client
|
||||
let client = api::JellyfinClient::new_with_config(config);
|
||||
Ok((client, false))
|
||||
}
|
||||
}
|
||||
},
|
||||
|result: Result<_, api::JellyfinApiError>| match result {
|
||||
// Ok((client, is_authenticated)) => Message::LoadedClient(client, is_authenticated),
|
||||
Err(e) => Message::Error(format!("Initialization failed: {}", e)),
|
||||
_ => Message::Error("Login Unimplemented".to_string()),
|
||||
},
|
||||
)
|
||||
.chain(Task::done(Message::Refresh)),
|
||||
)
|
||||
(State::new(), Task::done(Message::Refresh))
|
||||
}
|
||||
|
||||
pub fn ui() -> iced::Result {
|
||||
|
||||
Reference in New Issue
Block a user