1pub use crate::{
2 index::Index,
3 song::Song,
4 sqlite::{Database, State},
5 toml::{Bind, Colors, GlobalHotkey, Hotkey, Key, Modifier, Toml},
6};
7use static_init::dynamic;
8use std::path::PathBuf;
9
10mod index;
11mod song;
12mod toml;
13
14pub mod sqlite;
15
16#[dynamic]
17pub static GONK_DIR: PathBuf = {
18 let gonk = dirs::config_dir().unwrap().join("gonk");
19 if !gonk.exists() {
20 std::fs::create_dir_all(&gonk).unwrap();
21 }
22 gonk
23};
24
25#[dynamic]
26pub static DB_DIR: PathBuf = GONK_DIR.join("gonk.db");
27
28#[dynamic]
29pub static TOML_DIR: PathBuf = GONK_DIR.join("gonk.toml");
30
31#[cfg(test)]
32mod tests {
33 #[test]
39 fn bench_adding() {
40 use crate::sqlite;
41 use crate::sqlite::conn;
42 use crate::Song;
43 use jwalk::WalkDir;
44 use rayon::iter::{IntoParallelIterator, ParallelIterator};
45 use std::path::PathBuf;
46
47 unsafe {
48 sqlite::CONN = sqlite::open_database();
49 }
50
51 let paths: Vec<PathBuf> = WalkDir::new("D:\\Music")
52 .into_iter()
53 .flatten()
54 .map(|dir| dir.path())
55 .filter(|path| match path.extension() {
56 Some(ex) => {
57 matches!(ex.to_str(), Some("flac" | "mp3" | "ogg" | "wav" | "m4a"))
58 }
59 None => false,
60 })
61 .collect();
62
63 let songs: Vec<Song> = paths
64 .into_par_iter()
65 .map(|dir| Song::from(&dir))
66 .flatten()
67 .collect();
68
69 if songs.is_empty() {
70 return;
71 }
72
73 let mut stmt = String::from("BEGIN;\n");
74 stmt.push_str(&songs.iter()
75 .map(|song| {
76 let artist = song.artist.replace('\'', r"''");
77 let album = song.album.replace('\'', r"''");
78 let name = song.name.replace('\'', r"''");
79 let path = song.path.to_string_lossy().replace('\'', r"''");
80 let parent = "D:\\Music";
81 format!("INSERT OR IGNORE INTO song (number, disc, name, album, artist, path, duration, track_gain, parent) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');",
82 song.number, song.disc, name, album, artist,path, song.duration.as_secs_f64(), song.track_gain, parent)
83 })
84 .collect::<Vec<_>>().join("\n"));
85
86 stmt.push_str("COMMIT;\n");
87
88 conn().execute_batch(&stmt).unwrap();
89 }
90
91 #[test]
96 fn bench_query() {
97 use crate::sqlite;
98
99 unsafe {
100 sqlite::CONN = sqlite::open_database();
101 }
102
103 let t = std::time::Instant::now();
104
105 for _ in 0..10 {
106 let artists = sqlite::get_all_artists();
107 for artist in artists {
108 let albums = sqlite::get_all_albums_by_artist(&artist);
109 for album in albums {
110 let songs = sqlite::get_all_songs_from_album(&album, &artist);
111 assert!(!songs.is_empty());
112 }
113 }
114 }
115 eprintln!("{:0.2?}", t.elapsed())
116 }
117
118 #[cfg(windows)]
119 #[test]
120 fn windows_keybindings() {
121 use crate::{Bind, Key, Modifier};
122 use global_hotkeys::{keys, modifiers};
123
124 let b = Bind {
125 key: Key::from("A"),
126 modifiers: Some(vec![Modifier::ALT, Modifier::SHIFT]),
127 };
128 assert_eq!(Bind::new("A").key(), 'A' as u32);
129 assert_eq!(Bind::new("TAB").key(), keys::TAB);
130 assert_eq!(b.modifiers(), modifiers::ALT | modifiers::SHIFT);
131 }
132}