22//!
33//! Loads application settings from config.json located next to the executable.
44
5+ use serde:: Deserialize ;
56use std:: fs;
6- use std:: path:: PathBuf ;
7+ use std:: path:: { Path , PathBuf } ;
78use std:: process:: Command ;
8- use serde:: Deserialize ;
99
1010/// Application configuration loaded from config.json
1111#[ derive( Debug , Deserialize , Clone ) ]
@@ -16,13 +16,20 @@ pub struct Config {
1616 pub hourly_link : String ,
1717 /// URL to open for 30-minute check-ins
1818 pub thirty_link : String ,
19- /// Audio files loaded from the songs folder
19+
20+ /// Folder containing audio files (can be anywhere).
21+ ///
22+ /// If relative, it is resolved relative to the executable's directory.
2023 #[ serde( default ) ]
24+ pub songs_dir : PathBuf ,
25+
26+ /// Audio files discovered from `songs_dir`
27+ #[ serde( skip) ]
2128 pub songs : Vec < PathBuf > ,
2229}
2330
2431impl Config {
25- /// Loads configuration from config.json and discovers audio files from songs folder
32+ /// Loads configuration from config.json and discovers audio files from songs_dir
2633 pub fn load ( ) -> Option < Self > {
2734 let exe_dir: PathBuf = std:: env:: current_exe ( )
2835 . expect ( "Failed to get executable path" )
@@ -32,71 +39,54 @@ impl Config {
3239
3340 let path = exe_dir. join ( "config.json" ) ;
3441
35- let raw = fs:: read_to_string ( & path)
36- . unwrap ( ) ;
42+ let raw = fs:: read_to_string ( & path) . unwrap ( ) ;
43+
44+ let mut config: Config =
45+ serde_json:: from_str ( & raw ) . expect ( "Invalid JSON in config.json" ) ;
3746
38- let mut config: Config = serde_json:: from_str ( & raw )
39- . expect ( "Invalid JSON in config.json" ) ;
47+ // Resolve songs_dir:
48+ // - if missing/empty => default to <exe_dir>/songs
49+ // - if relative => resolve relative to exe_dir
50+ if config. songs_dir . as_os_str ( ) . is_empty ( ) {
51+ config. songs_dir = exe_dir. join ( "songs" ) ;
52+ } else if config. songs_dir . is_relative ( ) {
53+ config. songs_dir = exe_dir. join ( & config. songs_dir ) ;
54+ }
4055
41- config. songs = Self :: load_songs ( ) ;
56+ config. songs = Self :: load_songs_from ( & config . songs_dir ) ;
4257
4358 Some ( config)
4459 }
4560
46- /// Scans the songs folder for supported audio files (.mp3, .wav, .ogg, .flac)
47- fn load_songs ( ) -> Vec < PathBuf > {
48- let mut dir = std:: env:: current_exe ( ) . unwrap ( ) ;
49- dir. pop ( ) ;
50- dir. push ( "songs" ) ;
51-
52- let entries = match fs:: read_dir ( & dir) {
61+ /// Scans a folder for supported audio files (.mp3, .wav, .ogg, .flac)
62+ fn load_songs_from ( dir : & Path ) -> Vec < PathBuf > {
63+ let entries = match fs:: read_dir ( dir) {
5364 Ok ( entries) => entries,
54- Err ( _) => return Vec :: new ( ) , // songs folder missing -> no sounds
65+ Err ( _) => return Vec :: new ( ) , // folder missing/unreadable -> no sounds
5566 } ;
5667
5768 entries
5869 . filter_map ( |e| e. ok ( ) )
5970 . map ( |e| e. path ( ) )
6071 . filter ( |p| {
6172 matches ! (
62- p. extension( ) . and_then( |e| e. to_str( ) ) ,
63- Some ( "mp3" | "wav" | "ogg" | "flac" )
64- )
73+ p. extension( ) . and_then( |e| e. to_str( ) ) ,
74+ Some ( "mp3" | "wav" | "ogg" | "flac" )
75+ )
6576 } )
6677 . collect ( )
6778 }
6879
69- /// ```rust
70- /// Opens the "songs" folder located in the same directory as the executable.
71- ///
72- /// This function uses platform-specific commands to open the "songs" folder:
73- /// - On **Windows**, it uses the `explorer` command.
74- /// - On **macOS**, it uses the `open` command.
75- /// - On **Linux**, it uses the `xdg-open` command.
76- ///
77- /// If the platform is unsupported or an error occurs while spawning the command,
78- /// a message will be printed to the console.
79- ///
80- /// This will open the "songs" folder in the default file explorer, provided the
81- /// folder exists and the platform is supported.
82- /// ```
83- pub fn open_songs_folder ( ) {
84- let mut dir = std:: env:: current_exe ( ) . unwrap ( ) ;
85- dir. pop ( ) ;
86- dir. push ( "songs" ) ;
80+ /// Opens the configured songs folder in the OS file explorer.
81+ pub fn open_songs_folder ( & self ) {
82+ let dir = & self . songs_dir ;
83+
8784 let spawn_result = if cfg ! ( target_os = "windows" ) {
88- Command :: new ( "explorer" )
89- . arg ( dir)
90- . spawn ( )
85+ Command :: new ( "explorer" ) . arg ( dir) . spawn ( )
9186 } else if cfg ! ( target_os = "macos" ) {
92- Command :: new ( "open" )
93- . arg ( dir)
94- . spawn ( )
87+ Command :: new ( "open" ) . arg ( dir) . spawn ( )
9588 } else if cfg ! ( target_os = "linux" ) {
96- // xdg-open is a common utility on Linux to open files/urls with the default app
97- Command :: new ( "xdg-open" )
98- . arg ( dir)
99- . spawn ( )
89+ Command :: new ( "xdg-open" ) . arg ( dir) . spawn ( )
10090 } else {
10191 println ! ( "Unsupported operating system for opening file explorer automatically." ) ;
10292 return ;
@@ -105,7 +95,5 @@ impl Config {
10595 if let Err ( e) = spawn_result {
10696 eprintln ! ( "Failed to open songs folder: {e}" ) ;
10797 }
108-
109- }
110-
111- }
98+ }
99+ }
0 commit comments