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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {