diff --git a/Cargo.lock b/Cargo.lock index 19ba6d9..782efaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,7 +166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "cj-jftui" +name = "cjtui" version = "0.1.0" dependencies = [ "color-eyre", diff --git a/Cargo.toml b/Cargo.toml index f3c956f..215d20c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cj-jftui" +name = "cjtui" version = "0.1.0" edition = "2024" authors = ["Christopher Warren Johnson III"] diff --git a/README.md b/README.md index 68e94c4..bffc203 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,33 @@ +# CjTUI +*Chris Jellyfin Terminal User Interface* + This is the first actually useful programming project I have completed. I still can't write in rust because of generative AI. +Maybe one day when I am better at programming I will refactor the codebase and figure out how to hand code a transcoding option that actually is compatible with Jellyfin API. + +## Features +* **Blazingly Fast Speed** +* Online and Offline Playback +* Robust Download Connectivity +* Individual Download and Batch Download Support +* Download Queue Management +* Local Media Management +* Autoplay and Resume functionality +* Playlist and Collection Support +* Persistent Auth ## System Dependencies on **MPV** and **ARIA2C** ## Persistent Storage All application data is stored in: -~/.config/cj-tui/ +~/.config/cjtui/ * config.toml: Server URL and API key is stored. * downloads/: Local download data, used for local playback and offline navigation. * temp/: Temporary download data during ARIA2C functionality. -## Controls: +## Controls ##### General Interface Controls * **Up**/K: Up * **Right**/L/**Enter**: Drilldown/Enter diff --git a/src/core/downloads.rs b/src/core/downloads.rs index 09550c0..9d346b9 100644 --- a/src/core/downloads.rs +++ b/src/core/downloads.rs @@ -199,11 +199,11 @@ impl DownloadManager { // ---------- directory helpers ---------- -const DOWNLOAD_MARKER_EXT: &str = "cj-jftui.json"; +const DOWNLOAD_MARKER_EXT: &str = "cjtui.json"; fn marker_path_for_media(media_path: &Path) -> PathBuf { // Example: - // Movie.mkv -> Movie.mkv.cj-jftui.json + // Movie.mkv -> Movie.mkv.cjtui.json let file_name = media_path .file_name() .and_then(|s| s.to_str()) @@ -247,7 +247,7 @@ fn scan_dir_for_markers(dir: &Path, out: &mut HashSet) -> Result<(), Str continue; } - // Look for *.cj-jftui.json + // Look for *.cjtui.json let name = p.file_name().and_then(|s| s.to_str()).unwrap_or(""); if !name.ends_with(DOWNLOAD_MARKER_EXT) { continue; @@ -1115,7 +1115,7 @@ fn aria2_http_post(body: &str) -> Result { fn aria2_rpc_call(method: &str, params: Value) -> Result { let payload = json!({ "jsonrpc": "2.0", - "id": "cj-jftui", + "id": "cjtui", "method": method, "params": params, }); @@ -1178,7 +1178,7 @@ fn aria2_front_status(state: &Arc>) -> Option { /// If this item_id has a local download marker, return the local media file path. /// Marker files are written alongside the media file as: /// . -/// where DOWNLOAD_MARKER_EXT == "cj-jftui.json" +/// where DOWNLOAD_MARKER_EXT == "cjtui.json" pub fn local_media_path_for_item_id(item_id: &str) -> Option { fn walk(dir: &Path, item_id: &str) -> Option { if !dir.exists() { @@ -1229,7 +1229,7 @@ pub fn local_media_path_for_item_id(item_id: &str) -> Option { } -// Delete ALL local copies of an item by scanning *.cj-jftui.json markers recursively. +// Delete ALL local copies of an item by scanning *.cjtui.json markers recursively. // Returns how many media files we attempted to remove (count of matched markers). pub fn delete_local_by_item_id(item_id: &str) -> Result { let root = downloads_root()?; @@ -1314,7 +1314,7 @@ fn delete_in_dir_by_item_id(dir: &Path, item_id: &str, deleted: &mut usize) -> R Ok(()) } -// Reads sibling marker ".cj-jftui.json" and returns item_id if present. +// Reads sibling marker ".cjtui.json" and returns item_id if present. fn item_id_for_media_path(media_path: &Path) -> Result, String> { let fname = match media_path.file_name().and_then(|s| s.to_str()) { Some(s) => s, diff --git a/src/core/jellyfin.rs b/src/core/jellyfin.rs index 4d79b61..e8ce691 100644 --- a/src/core/jellyfin.rs +++ b/src/core/jellyfin.rs @@ -78,7 +78,7 @@ impl JellyfinClient { let url = format!("{}/Users/AuthenticateByName", self.base_url); let auth_header = format!( - "MediaBrowser Client=\"cj-jftui\", Device=\"kitty\", DeviceId=\"cj-jftui\", Version=\"{}\"", + "MediaBrowser Client=\"cjtui\", Device=\"kitty\", DeviceId=\"cjtui\", Version=\"{}\"", env!("CARGO_PKG_VERSION"), ); @@ -206,7 +206,7 @@ impl JellyfinClient { fn auth_header_value(&self) -> String { // Identify as a client and include the token. format!( - "MediaBrowser Client=\"cj-jftui\", Device=\"kitty\", DeviceId=\"cj-jftui\", Version=\"{}\", Token=\"{}\"", + "MediaBrowser Client=\"cjtui\", Device=\"kitty\", DeviceId=\"cjtui\", Version=\"{}\", Token=\"{}\"", env!("CARGO_PKG_VERSION"), self.token ) diff --git a/src/core/paths.rs b/src/core/paths.rs index 6b581f0..5233221 100644 --- a/src/core/paths.rs +++ b/src/core/paths.rs @@ -23,11 +23,11 @@ pub fn cache_dir() -> Option { } pub fn app_config_dir() -> Option { - config_dir().map(|d| d.join("cj-jftui")) + config_dir().map(|d| d.join("cjtui")) } pub fn app_cache_dir() -> Option { - cache_dir().map(|d| d.join("cj-jftui")) + cache_dir().map(|d| d.join("cjtui")) } pub fn posters_cache_dir() -> Option { diff --git a/src/main.rs b/src/main.rs index 4b4106c..3cf2c36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1848,7 +1848,7 @@ fn ui(frame: &mut Frame, app: &App) { // Top //frame.render_widget( - // Paragraph::new("cj-jftui").block(Block::bordered()), + // Paragraph::new("cjtui").block(Block::bordered()), // panes.top, //); @@ -1859,7 +1859,7 @@ fn ui(frame: &mut Frame, app: &App) { let [top_l, top_r] = Layout::horizontal([Constraint::Fill(1), Constraint::Length(40)]) .areas(top_inner); - frame.render_widget(Paragraph::new("cj-jftui"), top_l); + frame.render_widget(Paragraph::new("cjtui"), top_l); let server = app .config .as_ref() @@ -2170,7 +2170,7 @@ fn open_children(app: &mut App, title: String, query: core::jellyfin::LibraryQue fn mpv_socket_path() -> PathBuf { let mut p = std::env::temp_dir(); let pid = std::process::id(); - p.push(format!("cj-jftui-mpv-{}.sock", pid)); + p.push(format!("cjtui-mpv-{}.sock", pid)); p } @@ -3570,7 +3570,7 @@ fn offline_list_dir(dir: &std::path::Path) -> Vec { fn is_hidden_sidecar(name: &str) -> bool { - name.ends_with(".cj-jftui.json") || name.ends_with(".aria2") || name.ends_with(".part") + name.ends_with(".cjtui.json") || name.ends_with(".aria2") || name.ends_with(".part") } // Returns true if this directory contains any "visible" file or any subdir that does.