Krolyxon
Tag v0.1.7

rmptuisrcbrowser.rs

114 lines · 3.3 KB · Rust

1use std::path::Path;
2
3use crate::{
4 connection::{Connection, Song},
5 utils::is_audio_file,
6 AppResult,
7};
8
9#[derive(Debug, Default)]
10pub struct FileBrowser {
11 pub entries: Vec<BrowserEntry>,
12 pub selected: usize,
13 pub prev_selected: usize,
14 pub path: String,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub enum BrowserEntry {
19 Directory(String),
20 Song(Song),
21}
22
23impl BrowserEntry {
24 pub fn name(&self) -> &str {
25 match self {
26 Self::Directory(name) => name,
27 Self::Song(song) => Path::new(&song.file)
28 .file_name()
29 .and_then(|name| name.to_str())
30 .unwrap_or(&song.file),
31 }
32 }
33}
34
35impl FileBrowser {
36 pub fn new() -> Self {
37 Self {
38 path: ".".to_string(),
39 ..Self::default()
40 }
41 }
42
43 pub async fn update_directory(&mut self, conn: &mut Connection) -> AppResult<()> {
44 let query_path = self.path.trim_start_matches("./").trim_matches('/');
45 let (directories, songs) = conn.client.ls_info(query_path).await?;
46 let mut entries = Vec::new();
47 for directory in directories {
48 if let Some(name) = Path::new(&directory)
49 .file_name()
50 .and_then(|name| name.to_str())
51 {
52 if !name.starts_with('.') {
53 entries.push(BrowserEntry::Directory(name.to_string()));
54 }
55 }
56 }
57
58 for song in songs {
59 let Some(name) = Path::new(&song.file)
60 .file_name()
61 .and_then(|name| name.to_str())
62 else {
63 continue;
64 };
65 if is_audio_file(name) {
66 entries.push(BrowserEntry::Song(song));
67 }
68 }
69
70 entries.sort_by(|a, b| {
71 let kind_order = |entry: &BrowserEntry| match entry {
72 BrowserEntry::Directory(_) => 0,
73 BrowserEntry::Song(_) => 1,
74 };
75 kind_order(a).cmp(&kind_order(b)).then_with(|| {
76 let name_a = a.name();
77 let name_b = b.name();
78 let number_a = name_a.parse::<u32>().unwrap_or(u32::MAX);
79 let number_b = name_b.parse::<u32>().unwrap_or(u32::MAX);
80 number_a
81 .cmp(&number_b)
82 .then_with(|| name_a.to_lowercase().cmp(&name_b.to_lowercase()))
83 })
84 });
85
86 self.entries = entries;
87 self.selected = self.selected.min(self.entries.len().saturating_sub(1));
88 Ok(())
89 }
90
91 pub fn next(&mut self) {
92 if self.selected < self.entries.len().saturating_sub(1) {
93 self.selected += 1;
94 }
95 }
96
97 pub fn prev(&mut self) {
98 self.selected = self.selected.saturating_sub(1);
99 }
100
101 pub async fn handle_go_back(&mut self, conn: &mut Connection) -> AppResult<()> {
102 if self.path != "." {
103 self.path = Path::new(&self.path)
104 .parent()
105 .and_then(Path::to_str)
106 .filter(|path| !path.is_empty())
107 .unwrap_or(".")
108 .to_string();
109 self.update_directory(conn).await?;
110 }
111 self.selected = self.prev_selected.min(self.entries.len().saturating_sub(1));
112 Ok(())
113 }
114}