git/grrs/main

refactor: 2024 editioin styling, pedantic clippy fixes, taplo sorting

configured some taplo rules for toml sorting, defines some more necessary vars in cargo.toml. replaced forced unwraps with expects, per clippy. auto fixed nested ifs, map closures, inlined strings with clippy. applied 2024 style edition rules via rustfmt
diff --git a/.taplo.toml b/.taplo.toml
index 9325585..766ac34 100644
--- a/.taplo.toml
+++ b/.taplo.toml
@@ -1,6 +1,10 @@
+[formatting]
+column_width = 120
+reorder_arrays = true
+reorder_keys = true
+trailing_newline = true
+
[[rule]]
include = ["Cargo.toml"]
-
[rule.formatting]
-column_width = 120
inline_table_expand = false
diff --git a/Cargo.toml b/Cargo.toml
index 8cf2555..d7122a1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,34 +1,35 @@
[package]
-name = "myn-grrs"
-version = "0.1.7"
-edition = "2024"
-repository = "https://git.myn.sh/grrs.git"
description = "a minimal tui for gensokyo radio"
+edition = "2024"
license = "Unlicense"
+name = "myn-grrs"
+repository = "https://git.myn.sh/grrs.git"
+rust-version = "1.95.0"
+version = "0.1.7"
[profile.release]
-opt-level = "z"
lto = true
-strip = true
+opt-level = "z"
panic = "abort"
+strip = true
[dependencies]
-ratatui = "0.30"
-ratatui-image = { version = "11.0", features = ["crossterm"] }
+arboard = { version = "3.6.1", features = ["wayland-data-control"] }
crossterm = "0.29"
-tokio = { version = "1.37", features = ["rt-multi-thread", "macros", "net", "time", "fs", "io-util"] }
futures-util = "0.3"
+image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp"] }
+notify-rust = "4.18.0"
+ratatui = "0.30"
+ratatui-image = { version = "11.0", features = ["crossterm"] }
reqwest = { version = "0.13", features = ["json"] }
-tokio-tungstenite = { version = "0.30", features = ["native-tls"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
-toml = "1.1.4"
-image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp"] }
souvlaki = "0.8.3"
-arboard = { version = "3.6.1", features = ["wayland-data-control"] }
-webbrowser = "1.2.4"
+tokio = { version = "1.37", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "time"] }
+tokio-tungstenite = { version = "0.30", features = ["native-tls"] }
+toml = "1.1.4"
urlencoding = "2.1.3"
-notify-rust = "4.18.0"
+webbrowser = "1.2.4"
[dev-dependencies]
pretty_assertions = "1.4.1"
@@ -36,3 +37,13 @@ pretty_assertions = "1.4.1"
[[bin]]
name = "grrs"
path = "src/main.rs"
+
+[lints.clippy]
+cast_lossless = "allow"
+cast_possible_truncation = "allow"
+cast_precision_loss = "allow"
+needless_pass_by_value = "allow"
+pedantic = { level = "warn", priority = -1 }
+similar_names = "allow"
+too_many_lines = "allow"
+unwrap_used = "warn"
diff --git a/pkg/grrs-git/PKGBUILD b/pkg/grrs-git/PKGBUILD
index 9dacd90..c3bdb97 100644
--- a/pkg/grrs-git/PKGBUILD
+++ b/pkg/grrs-git/PKGBUILD
@@ -1,7 +1,7 @@
# Maintainer: Myn <hello@myn.sh>
pkgname=grrs-git
-pkgver=r24.2e6584d
+pkgver=r28.c52b71d
pkgrel=1
pkgdesc="A minimal TUI for Gensokyo Radio"
arch=('x86_64')
diff --git a/rustfmt.toml b/rustfmt.toml
index 88ccf2a..0ea179f 100644
--- a/rustfmt.toml
+++ b/rustfmt.toml
@@ -1,13 +1,17 @@
+blank_lines_upper_bound = 1
+comment_width = 80
edition = "2024"
-max_width = 120
-use_small_heuristics = "Max"
-imports_granularity = "Crate"
-group_imports = "StdExternalCrate"
format_code_in_doc_comments = true
-wrap_comments = true
-reorder_impl_items = true
-normalize_comments = true
-reorder_modules = true
+format_macro_bodies = true
+format_macro_matchers = true
+group_imports = "StdExternalCrate"
+imports_granularity = "Crate"
match_block_trailing_comma = true
-blank_lines_upper_bound = 1
+max_width = 120
newline_style = "Unix"
+normalize_comments = true
+reorder_impl_items = true
+reorder_modules = true
+style_edition = "2024"
+use_small_heuristics = "Max"
+wrap_comments = true
diff --git a/src/app.rs b/src/app.rs
index 38d1915..86609a7 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -72,8 +72,7 @@ impl AppState {
let home = env::var("HOME").unwrap_or_default();
let config_dir = env::var("XDG_CONFIG_HOME")
- .map(PathBuf::from)
- .unwrap_or_else(|_| PathBuf::from(&home).join(".config"))
+ .map_or_else(|_| PathBuf::from(&home).join(".config"), PathBuf::from)
.join("grrs");
let config_file = config_dir.join("config.toml");
@@ -88,25 +87,22 @@ impl AppState {
};
if config.system.data_dir.starts_with("~/") {
- config.system.data_dir = config.system.data_dir.replacen("~", &home, 1);
+ config.system.data_dir = config.system.data_dir.replacen('~', &home, 1);
}
if config.system.socket_path.starts_with("~/") {
- config.system.socket_path = config.system.socket_path.replacen("~", &home, 1);
+ config.system.socket_path = config.system.socket_path.replacen('~', &home, 1);
}
- let data_dir = if !config.system.data_dir.is_empty() {
- PathBuf::from(&config.system.data_dir)
- } else {
+ let data_dir = if config.system.data_dir.is_empty() {
env::var("XDG_DATA_HOME")
- .map(PathBuf::from)
- .unwrap_or_else(|_| PathBuf::from(&home).join(".local/share"))
+ .map_or_else(|_| PathBuf::from(&home).join(".local/share"), PathBuf::from)
.join("grrs")
+ } else {
+ PathBuf::from(&config.system.data_dir)
};
- let cache_dir = env::var("XDG_CACHE_HOME")
- .map(PathBuf::from)
- .unwrap_or_else(|_| PathBuf::from(&home).join(".cache"))
- .join("grrs");
+ let cache_dir =
+ env::var("XDG_CACHE_HOME").map_or_else(|_| PathBuf::from(&home).join(".cache"), PathBuf::from).join("grrs");
fs::create_dir_all(&data_dir).ok();
fs::create_dir_all(&cache_dir).ok();
@@ -183,7 +179,7 @@ impl AppState {
return;
}
if self.likes.remove(&self.song.id).is_none() {
- let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
+ let ts = SystemTime::now().duration_since(UNIX_EPOCH).expect("system time went backwards").as_secs();
self.likes.insert(
self.song.id,
LikedSong {
@@ -212,7 +208,7 @@ impl AppState {
}
pub fn art_path(&self, id: i64) -> PathBuf {
- self.likes_file.parent().unwrap().join("art").join(format!("{}.jpg", id))
+ self.likes_file.parent().expect("likes_file has no parent directory").join("art").join(format!("{id}.jpg"))
}
pub fn matches_mpv(&self) -> bool {
@@ -235,6 +231,6 @@ impl AppState {
}
pub fn is_refreshing(&self) -> bool {
- self.refresh_until.map(|t| Instant::now() < t).unwrap_or(false)
+ self.refresh_until.is_some_and(|t| Instant::now() < t)
}
}
diff --git a/src/config.rs b/src/config.rs
index c4a7ab1..b0f1b04 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
+#[derive(Default)]
pub struct AppConfig {
pub player: PlayerConfig,
pub ui: UiConfig,
@@ -70,20 +71,15 @@ impl Default for SystemConfig {
}
}
-impl Default for AppConfig {
- fn default() -> Self {
- Self { player: PlayerConfig::default(), ui: UiConfig::default(), system: SystemConfig::default() }
- }
-}
impl AppConfig {
pub fn load_or_create(config_path: &PathBuf) -> Result<Self, String> {
if config_path.exists() {
- let contents = fs::read_to_string(config_path).map_err(|e| format!("Failed to read file: {}", e))?;
+ let contents = fs::read_to_string(config_path).map_err(|e| format!("Failed to read file: {e}"))?;
match toml::from_str::<AppConfig>(&contents) {
Ok(cfg) => return Ok(cfg),
- Err(e) => return Err(format!("TOML syntax error: {}", e)),
+ Err(e) => return Err(format!("TOML syntax error: {e}")),
}
}
diff --git a/src/input.rs b/src/input.rs
index bf84066..a5d231b 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -139,7 +139,7 @@ pub async fn handle_input(
} else if key.code == KeyCode::Char('o') || key.code == KeyCode::Enter {
if !state.song.album.is_empty() {
let query = urlencoding::encode(&state.song.album);
- let url = format!("https://touhoudb.com/Search?searchType=Album&filter={}", query);
+ let url = format!("https://touhoudb.com/Search?searchType=Album&filter={query}");
if webbrowser::open(&url).is_ok() {
state.status_message = "opened touhoudb in browser!".to_string();
} else {
@@ -151,7 +151,8 @@ pub async fn handle_input(
let was_liked = state.likes.contains_key(&state.song.id);
state.toggle_like();
if state.likes.contains_key(&state.song.id) && !was_liked && !state.song.art_url.is_empty() {
- let art_dir = state.likes_file.parent().unwrap().join("art");
+ let art_dir =
+ state.likes_file.parent().expect("likes_file has no parent directory").join("art");
fs::create_dir_all(&art_dir).ok();
let url = if state.song.art_url.starts_with("http") {
state.song.art_url.clone()
@@ -161,10 +162,10 @@ pub async fn handle_input(
let path = state.art_path(state.song.id);
let client = http_client.clone();
tokio::spawn(async move {
- if let Ok(resp) = client.get(&url).send().await {
- if let Ok(bytes) = resp.bytes().await {
- let _ = tokio::fs::write(path, bytes).await;
- }
+ if let Ok(resp) = client.get(&url).send().await
+ && let Ok(bytes) = resp.bytes().await
+ {
+ let _ = tokio::fs::write(path, bytes).await;
}
});
} else if !state.likes.contains_key(&state.song.id) && was_liked {
@@ -173,28 +174,24 @@ pub async fn handle_input(
let _ = tokio::fs::remove_file(path_to_remove).await;
});
}
- } else if key.code == KeyCode::Char('c') || key.code == KeyCode::Char('C') {
- if let Ok(mut clipboard) = arboard::Clipboard::new() {
- let text = if key.code == KeyCode::Char('c') {
- format!("{} - {}", state.song.artist, state.song.title)
- } else {
- format!(
- "Title: {}\nArtist: {}\nAlbum: {}\nCircle: {}\nYear: {}",
- state.song.title,
- state.song.artist,
- state.song.album,
- state.song.circle,
- state.song.year
- )
- };
- let _ = clipboard.set_text(text);
- state.status_message = if key.code == KeyCode::Char('c') {
- "copied track/artist!".to_string()
- } else {
- "copied full metadata!".to_string()
- };
- state.status_message_time = Some(std::time::Instant::now());
- }
+ } else if (key.code == KeyCode::Char('c') || key.code == KeyCode::Char('C'))
+ && let Ok(mut clipboard) = arboard::Clipboard::new()
+ {
+ let text = if key.code == KeyCode::Char('c') {
+ format!("{} - {}", state.song.artist, state.song.title)
+ } else {
+ format!(
+ "Title: {}\nArtist: {}\nAlbum: {}\nCircle: {}\nYear: {}",
+ state.song.title, state.song.artist, state.song.album, state.song.circle, state.song.year
+ )
+ };
+ let _ = clipboard.set_text(text);
+ state.status_message = if key.code == KeyCode::Char('c') {
+ "copied track/artist!".to_string()
+ } else {
+ "copied full metadata!".to_string()
+ };
+ state.status_message_time = Some(std::time::Instant::now());
}
} else if state.mode == AppMode::LikedSongs {
if key.code == KeyCode::Tab || key.code == KeyCode::Esc {
@@ -233,24 +230,24 @@ pub async fn handle_input(
} else if key.code == KeyCode::Char('c') || key.code == KeyCode::Char('C') {
if let Some(idx) = state.liked_list_state.selected() {
let sorted_likes = state.get_sorted_likes();
- if let Some(song) = sorted_likes.get(idx) {
- if let Ok(mut clipboard) = arboard::Clipboard::new() {
- let text = if key.code == KeyCode::Char('c') {
- format!("{} - {}", song.artist, song.title)
- } else {
- format!(
- "Title: {}\nArtist: {}\nAlbum: {}\nCircle: {}\nYear: {}",
- song.title, song.artist, song.album, song.circle, song.year
- )
- };
- let _ = clipboard.set_text(text);
- state.status_message = if key.code == KeyCode::Char('c') {
- "copied track/artist!".to_string()
- } else {
- "copied full metadata!".to_string()
- };
- state.status_message_time = Some(std::time::Instant::now());
- }
+ if let Some(song) = sorted_likes.get(idx)
+ && let Ok(mut clipboard) = arboard::Clipboard::new()
+ {
+ let text = if key.code == KeyCode::Char('c') {
+ format!("{} - {}", song.artist, song.title)
+ } else {
+ format!(
+ "Title: {}\nArtist: {}\nAlbum: {}\nCircle: {}\nYear: {}",
+ song.title, song.artist, song.album, song.circle, song.year
+ )
+ };
+ let _ = clipboard.set_text(text);
+ state.status_message = if key.code == KeyCode::Char('c') {
+ "copied track/artist!".to_string()
+ } else {
+ "copied full metadata!".to_string()
+ };
+ state.status_message_time = Some(std::time::Instant::now());
}
}
} else if key.code == KeyCode::Char('l') {
@@ -260,32 +257,33 @@ pub async fn handle_input(
state.show_unlike_confirm = Some(song.id);
}
}
- } else if key.code == KeyCode::Char('o') || key.code == KeyCode::Enter {
- if let Some(idx) = state.liked_list_state.selected() {
- let sorted_likes = state.get_sorted_likes();
- if let Some(song) = sorted_likes.get(idx) {
- let query = urlencoding::encode(&song.album);
- let url = format!("https://touhoudb.com/Search?searchType=Album&filter={}", query);
- if webbrowser::open(&url).is_ok() {
- state.status_message = "opened touhoudb in browser!".to_string();
- } else {
- state.status_message = "failed to open browser".to_string();
- }
- state.status_message_time = Some(std::time::Instant::now());
+ } else if (key.code == KeyCode::Char('o') || key.code == KeyCode::Enter)
+ && let Some(idx) = state.liked_list_state.selected()
+ {
+ let sorted_likes = state.get_sorted_likes();
+ if let Some(song) = sorted_likes.get(idx) {
+ let query = urlencoding::encode(&song.album);
+ let url = format!("https://touhoudb.com/Search?searchType=Album&filter={query}");
+ if webbrowser::open(&url).is_ok() {
+ state.status_message = "opened touhoudb in browser!".to_string();
+ } else {
+ state.status_message = "failed to open browser".to_string();
}
+ state.status_message_time = Some(std::time::Instant::now());
}
}
}
},
CEvent::Mouse(mouse) => {
- if mouse.kind == MouseEventKind::Down(MouseButton::Left) {
- if let Ok(size) = crossterm::terminal::size() {
- if mouse.row == size.1.saturating_sub(3) && mouse.column >= 2 && mouse.column <= 10 {
- state.show_help_overlay = !state.show_help_overlay;
- if !state.show_help_overlay {
- clear = true;
- }
- }
+ if mouse.kind == MouseEventKind::Down(MouseButton::Left)
+ && let Ok(size) = crossterm::terminal::size()
+ && mouse.row == size.1.saturating_sub(3)
+ && mouse.column >= 2
+ && mouse.column <= 10
+ {
+ state.show_help_overlay = !state.show_help_overlay;
+ if !state.show_help_overlay {
+ clear = true;
}
}
},
diff --git a/src/main.rs b/src/main.rs
index ca7774e..6b9c792 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -59,7 +59,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
"--really-quiet",
"--no-terminal",
&volume_arg,
- &format!("--input-ipc-server={}", socket_path),
+ &format!("--input-ipc-server={socket_path}"),
&stream_url,
])
.stdout(Stdio::null())
@@ -96,19 +96,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let picker = Picker::from_query_stdio().expect("failed to query terminal for image support");
let loop_picker = picker.clone();
- let http_client = reqwest::Client::builder().timeout(Duration::from_secs(5)).build().unwrap();
+ let http_client =
+ reqwest::Client::builder().timeout(Duration::from_secs(5)).build().expect("failed to build http client");
let tx_input = tx.clone();
std::thread::spawn(move || {
loop {
- if event::poll(Duration::from_millis(250)).unwrap_or(false) {
- if let Ok(evt) = event::read() {
- if matches!(evt, CEvent::Key(_) | CEvent::Mouse(_)) {
- if tx_input.blocking_send(AppEvent::Input(evt)).is_err() {
- break;
- }
- }
- }
+ if event::poll(Duration::from_millis(250)).unwrap_or(false)
+ && let Ok(evt) = event::read()
+ && matches!(evt, CEvent::Key(_) | CEvent::Mouse(_))
+ && tx_input.blocking_send(AppEvent::Input(evt)).is_err()
+ {
+ break;
}
if tx_input.blocking_send(AppEvent::Tick).is_err() {
break;
@@ -126,19 +125,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_client = http_client.clone();
tokio::spawn(async move {
loop {
- if let Ok(resp) = api_client.get("https://gensokyoradio.net/api/station/playing/").send().await {
- if let Ok(text) = resp.text().await {
- if let Ok(info) = serde_json::from_str::<ApiInfo>(&text) {
- let _ = tx_api
- .send(AppEvent::ApiUpdate {
- listeners: info.server_info.listeners,
- circle_link: info.misc.circle_link,
- rating: info.song_data.rating,
- times_rated: info.song_data.times_rated,
- })
- .await;
- }
- }
+ if let Ok(resp) = api_client.get("https://gensokyoradio.net/api/station/playing/").send().await
+ && let Ok(text) = resp.text().await
+ && let Ok(info) = serde_json::from_str::<ApiInfo>(&text)
+ {
+ let _ = tx_api
+ .send(AppEvent::ApiUpdate {
+ listeners: info.server_info.listeners,
+ circle_link: info.misc.circle_link,
+ rating: info.song_data.rating,
+ times_rated: info.song_data.times_rated,
+ })
+ .await;
}
tokio::time::sleep(Duration::from_secs(15)).await;
}
@@ -148,71 +146,65 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let art_tx_ws = art_tx.clone();
tokio::spawn(async move {
loop {
- match connect_async("wss://gensokyoradio.net/wss").await {
- Ok((mut ws_stream, _)) => {
- if tx_ws.send(AppEvent::ConnectionStatus("".to_string())).await.is_err() {
- return;
- }
- let _ = ws_stream
- .send(Message::Text(
- serde_json::json!({"message": "grInitialConnection"}).to_string().into(),
- ))
- .await;
-
- loop {
- tokio::select! {
- msg = ws_stream.next() => {
- match msg {
- Some(Ok(Message::Text(text))) => {
- if let Ok(payload) = serde_json::from_str::<WsPayload>(&text) {
- if payload.songid != 0 {
- let year = payload.year.and_then(|y| if y.is_number() { y.as_i64().map(|n| n.to_string()) } else { y.as_str().map(|s| s.to_string()) }).unwrap_or_default();
- let played = payload.played.and_then(|p| if p.is_number() { p.as_u64() } else if let Some(s) = p.as_str() { s.parse().ok() } else { None }).unwrap_or(0);
-
- let song = SongData {
- id: payload.songid,
- title: payload.title.unwrap_or_else(|| "Unknown".to_string()),
- artist: payload.artist.unwrap_or_else(|| "Unknown".to_string()),
- album: payload.album.unwrap_or_else(|| "Unknown".to_string()),
- circle: payload.circle.unwrap_or_default(),
- circle_link: "".to_string(),
- vocal: payload.vocal.unwrap_or_default(),
- original: payload.original.unwrap_or_default(),
- year,
- duration: payload.duration.unwrap_or(0),
- played,
- art_url: payload.albumart.clone().unwrap_or_default(),
- listeners: 0,
- rating: "".to_string(),
- times_rated: 0,
- };
-
- let _ = art_tx_ws.send((song.id, song.art_url.clone(), song.title.clone(), song.artist.clone()));
- if tx_ws.send(AppEvent::SongUpdate(song)).await.is_err() { return; }
- }
+ if let Ok((mut ws_stream, _)) = connect_async("wss://gensokyoradio.net/wss").await {
+ if tx_ws.send(AppEvent::ConnectionStatus(String::new())).await.is_err() {
+ return;
+ }
+ let _ = ws_stream
+ .send(Message::Text(serde_json::json!({"message": "grInitialConnection"}).to_string().into()))
+ .await;
+
+ loop {
+ tokio::select! {
+ msg = ws_stream.next() => {
+ match msg {
+ Some(Ok(Message::Text(text))) => {
+ if let Ok(payload) = serde_json::from_str::<WsPayload>(&text)
+ && payload.songid != 0 {
+ let year = payload.year.and_then(|y| if y.is_number() { y.as_i64().map(|n| n.to_string()) } else { y.as_str().map(std::string::ToString::to_string) }).unwrap_or_default();
+ let played = payload.played.and_then(|p| if p.is_number() { p.as_u64() } else if let Some(s) = p.as_str() { s.parse().ok() } else { None }).unwrap_or(0);
+
+ let song = SongData {
+ id: payload.songid,
+ title: payload.title.unwrap_or_else(|| "Unknown".to_string()),
+ artist: payload.artist.unwrap_or_else(|| "Unknown".to_string()),
+ album: payload.album.unwrap_or_else(|| "Unknown".to_string()),
+ circle: payload.circle.unwrap_or_default(),
+ circle_link: String::new(),
+ vocal: payload.vocal.unwrap_or_default(),
+ original: payload.original.unwrap_or_default(),
+ year,
+ duration: payload.duration.unwrap_or(0),
+ played,
+ art_url: payload.albumart.clone().unwrap_or_default(),
+ listeners: 0,
+ rating: String::new(),
+ times_rated: 0,
+ };
+
+ let _ = art_tx_ws.send((song.id, song.art_url.clone(), song.title.clone(), song.artist.clone()));
+ if tx_ws.send(AppEvent::SongUpdate(song)).await.is_err() { return; }
}
- }
- Some(_) => {}
- None => break,
}
- }
- res = ws_nudge_rx.changed() => {
- if res.is_err() { return; }
- break;
+ Some(_) => {}
+ None => break,
}
}
+ res = ws_nudge_rx.changed() => {
+ if res.is_err() { return; }
+ break;
+ }
}
- },
- Err(_) => {
- if tx_ws
- .send(AppEvent::ConnectionStatus("websocket dropped. reconnecting...".to_string()))
- .await
- .is_err()
- {
- return;
- }
- tokio::time::sleep(Duration::from_secs(5)).await;
- },
+ }
+ } else {
+ if tx_ws
+ .send(AppEvent::ConnectionStatus("websocket dropped. reconnecting...".to_string()))
+ .await
+ .is_err()
+ {
+ return;
+ }
+ tokio::time::sleep(Duration::from_secs(5)).await;
}
}
});
@@ -237,26 +229,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut ok = false;
for _ in 0..3 {
- if let Ok(resp) = art_client.get(&url).send().await {
- if let Ok(bytes) = resp.bytes().await {
- let b = bytes.to_vec();
- let picker_clone = art_picker.clone();
- let tx_art_local = tx_art.clone();
-
- let decode_result = tokio::task::spawn_blocking(move || {
- if let Ok(img) = image::load_from_memory(&b) {
- let protocol = picker_clone.new_resize_protocol(img);
- return Some((protocol, b));
- }
- None
- })
- .await;
-
- if let Ok(Some((protocol, original_bytes))) = decode_result {
- let _ = tx_art_local.send(AppEvent::ArtUpdate(protocol, original_bytes)).await;
- ok = true;
- break;
+ if let Ok(resp) = art_client.get(&url).send().await
+ && let Ok(bytes) = resp.bytes().await
+ {
+ let b = bytes.to_vec();
+ let picker_clone = art_picker.clone();
+ let tx_art_local = tx_art.clone();
+
+ let decode_result = tokio::task::spawn_blocking(move || {
+ if let Ok(img) = image::load_from_memory(&b) {
+ let protocol = picker_clone.new_resize_protocol(img);
+ return Some((protocol, b));
}
+ None
+ })
+ .await;
+
+ if let Ok(Some((protocol, original_bytes))) = decode_result {
+ let _ = tx_art_local.send(AppEvent::ArtUpdate(protocol, original_bytes)).await;
+ ok = true;
+ break;
}
}
tokio::time::sleep(Duration::from_secs(2)).await;
@@ -291,14 +283,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
break;
}
- if let Ok(value) = serde_json::from_str::<serde_json::Value>(&line) {
- if value.get("event").and_then(|v| v.as_str()) == Some("property-change") {
- if let Some(title) = value.get("data").and_then(|v| v.as_str()) {
- if let Some(mpv_song) = parse_mpv_media_title(title) {
- let _ = tx_patchouli.send(AppEvent::MpvUpdate(mpv_song)).await;
- }
- }
- }
+ if let Ok(value) = serde_json::from_str::<serde_json::Value>(&line)
+ && value.get("event").and_then(|v| v.as_str()) == Some("property-change")
+ && let Some(title) = value.get("data").and_then(|v| v.as_str())
+ && let Some(mpv_song) = parse_mpv_media_title(title)
+ {
+ let _ = tx_patchouli.send(AppEvent::MpvUpdate(mpv_song)).await;
}
line.clear();
}
@@ -309,7 +299,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
});
}
- let mut last_nudge = Instant::now() - Duration::from_secs(10);
+ let mut last_nudge = Instant::now().checked_sub(Duration::from_secs(10)).unwrap_or_else(Instant::now);
let mut hide_art = false;
loop {
@@ -356,26 +346,28 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tokio::spawn(async move {
if let Ok(resp) =
quick_client.get("https://gensokyoradio.net/api/station/playing/").send().await
+ && let Ok(text) = resp.text().await
+ && let Ok(info) = serde_json::from_str::<ApiInfo>(&text)
{
- if let Ok(text) = resp.text().await {
- if let Ok(info) = serde_json::from_str::<ApiInfo>(&text) {
- let _ = tx_api_quick
- .send(AppEvent::ApiUpdate {
- listeners: info.server_info.listeners,
- circle_link: info.misc.circle_link,
- rating: info.song_data.rating,
- times_rated: info.song_data.times_rated,
- })
- .await;
- }
- }
+ let _ = tx_api_quick
+ .send(AppEvent::ApiUpdate {
+ listeners: info.server_info.listeners,
+ circle_link: info.misc.circle_link,
+ rating: info.song_data.rating,
+ times_rated: info.song_data.times_rated,
+ })
+ .await;
}
});
if state.config.system.notifications && song.id != 0 {
let title = song.title.clone();
let artist = song.artist.clone();
- let art_path = state.likes_file.parent().unwrap().join("current_art.jpg");
+ let art_path = state
+ .likes_file
+ .parent()
+ .expect("likes_file has no parent directory")
+ .join("current_art.jpg");
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(1500)).await;
@@ -396,11 +388,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
state.song = song;
state.song.listeners = cached_listeners;
- state.status_message = "".to_string();
+ state.status_message = String::new();
state.refresh_until = None;
if let Some(ref mut c) = controls {
- let art_path = state.likes_file.parent().unwrap().join("current_art.jpg");
+ let art_path = state
+ .likes_file
+ .parent()
+ .expect("likes_file has no parent directory")
+ .join("current_art.jpg");
let art_url = format!("file://{}", art_path.to_str().unwrap_or(""));
c.set_metadata(MediaMetadata {
title: Some(state.song.title.as_str()),
@@ -422,7 +418,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
AppEvent::ArtUpdate(protocol, bytes) => {
state.live_art_bytes = Some(bytes.clone());
state.art_protocol = Some(protocol);
- let path = state.likes_file.parent().unwrap().join("current_art.jpg");
+ let path =
+ state.likes_file.parent().expect("likes_file has no parent directory").join("current_art.jpg");
tokio::spawn(async move {
let _ = tokio::fs::write(path, bytes).await;
});
@@ -430,10 +427,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
AppEvent::LocalArtLoaded(id, protocol) => {
if let Some(idx) = state.liked_list_state.selected() {
let sorted_likes = state.get_sorted_likes();
- if let Some(song) = sorted_likes.get(idx) {
- if song.id == id {
- state.selected_art = protocol;
- }
+ if let Some(song) = sorted_likes.get(idx)
+ && song.id == id
+ {
+ state.selected_art = protocol;
}
}
},
@@ -460,16 +457,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
},
AppEvent::Tick => {
- if let Some(t) = state.status_message_time {
- if t.elapsed().as_secs() >= 3 {
- state.status_message.clear();
- state.status_message_time = None;
- }
+ if let Some(t) = state.status_message_time
+ && t.elapsed().as_secs() >= 3
+ {
+ state.status_message.clear();
+ state.status_message_time = None;
}
- if let Some(t) = state.volume_time {
- if t.elapsed().as_secs() >= 3 {
- state.volume_time = None;
- }
+ if let Some(t) = state.volume_time
+ && t.elapsed().as_secs() >= 3
+ {
+ state.volume_time = None;
}
},
}
diff --git a/src/models.rs b/src/models.rs
index b3565f7..95801c7 100644
--- a/src/models.rs
+++ b/src/models.rs
@@ -65,11 +65,7 @@ pub struct MpvSong {
pub fn parse_mpv_media_title(title: &str) -> Option<MpvSong> {
let trimmed = title.trim();
- if let Some(idx) = trimmed.find(" - ") {
- Some(MpvSong { artist: trimmed[..idx].trim().to_string(), title: trimmed[idx + 3..].trim().to_string() })
- } else {
- None
- }
+ trimmed.find(" - ").map(|idx| MpvSong { artist: trimmed[..idx].trim().to_string(), title: trimmed[idx + 3..].trim().to_string() })
}
#[derive(Default, Clone)]
diff --git a/src/ui.rs b/src/ui.rs
index 13fdb36..33aaf70 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -31,7 +31,7 @@ fn centered_rect(width: u16, height: u16, r: Rect) -> Rect {
.split(popup_layout[1])[1]
}
-fn build_status_bar<'a>(state: &'a AppState) -> Line<'a> {
+fn build_status_bar(state: &AppState) -> Line<'_> {
let mut status_spans = vec![Span::styled("[?] Help", Style::default().fg(state.secondary_color))];
if state.song.id != 0 && state.likes.contains_key(&state.song.id) {
status_spans.push(Span::styled(" [Liked]", Style::default().fg(state.theme_color)));
@@ -86,7 +86,7 @@ pub fn draw_ui(f: &mut Frame, state: &mut AppState, hide_art: bool) {
if w.contains("valid color") {
has_color_error = true;
}
- lines.push(Line::from(vec![Span::styled(format!(" - {}", w), Style::default().fg(Color::Yellow))]));
+ lines.push(Line::from(vec![Span::styled(format!(" - {w}"), Style::default().fg(Color::Yellow))]));
}
if has_color_error {