@@ -9,7 +9,6 @@ use std::env;
99use std:: fs;
1010use std:: io;
1111use std:: path:: Path ;
12- use std:: time:: { SystemTime , UNIX_EPOCH } ;
1312
1413/// Application-wide configuration stored in config.toml.
1514#[ derive( Debug , Serialize , Deserialize , Clone ) ]
@@ -43,21 +42,6 @@ pub fn default_config_path() -> Option<Utf8PathBuf> {
4342 config_path ( "config.toml" )
4443}
4544
46- /// Loads the application configuration from config.toml.
47- /// Returns default configuration if file doesn't exist or cannot be parsed.
48- pub fn load_config ( ) -> AppConfig {
49- if let Some ( path) = default_config_path ( ) {
50- if Path :: new ( path. as_str ( ) ) . exists ( ) {
51- if let Ok ( content) = fs:: read_to_string ( path. as_str ( ) ) {
52- if let Ok ( cfg) = toml:: from_str ( & content) {
53- return cfg;
54- }
55- }
56- }
57- }
58- AppConfig :: default ( )
59- }
60-
6145/// Normalizes a path to use forward slashes
6246fn normalize_path ( path : & Utf8PathBuf ) -> Utf8PathBuf {
6347 Utf8PathBuf :: from ( path. as_str ( ) . replace ( '\\' , "/" ) )
@@ -82,16 +66,24 @@ pub fn save_config(cfg: &AppConfig) -> io::Result<()> {
8266}
8367
8468/// Loads existing configuration or creates a new one with defaults.
69+ /// Missing fields in the config file are filled with default values.
8570pub fn load_or_create_config ( ) -> Result < ( AppConfig , Utf8PathBuf ) > {
8671 let path = default_config_path ( ) . ok_or ( miette:: miette!( "Could not determine config path" ) ) ?;
8772
8873 if Path :: new ( path. as_str ( ) ) . exists ( ) {
8974 let content = fs:: read_to_string ( path. as_str ( ) )
9075 . into_diagnostic ( )
9176 . wrap_err ( "Failed to read config file" ) ?;
92- let cfg = toml:: from_str ( & content)
77+ let mut cfg: AppConfig = toml:: from_str ( & content)
9378 . into_diagnostic ( )
9479 . wrap_err ( "Failed to parse config file" ) ?;
80+
81+ // Fill in defaults for missing optional fields
82+ let defaults = AppConfig :: default ( ) ;
83+ if cfg. hashtable_dir . is_none ( ) {
84+ cfg. hashtable_dir = defaults. hashtable_dir ;
85+ }
86+
9587 Ok ( ( cfg, path) )
9688 } else {
9789 let cfg = AppConfig :: default ( ) ;
@@ -102,38 +94,61 @@ pub fn load_or_create_config() -> Result<(AppConfig, Utf8PathBuf)> {
10294 }
10395}
10496
105- /// Reads JSON from a path into type T. Returns Ok(None) if file cannot be read or parsed.
106- pub fn read_json < T : serde:: de:: DeserializeOwned > ( path : & Path ) -> io:: Result < Option < T > > {
107- match fs:: read ( path) {
108- Ok ( bytes) => match serde_json:: from_slice :: < T > ( & bytes) {
109- Ok ( v) => Ok ( Some ( v) ) ,
110- Err ( _) => Ok ( None ) ,
111- } ,
112- Err ( _) => Ok ( None ) ,
113- }
114- }
97+ /// Loads configuration as a raw TOML table for flexible editing.
98+ pub fn load_config_as_table ( ) -> Result < toml:: Table > {
99+ let path = default_config_path ( ) . ok_or ( miette:: miette!( "Could not determine config path" ) ) ?;
100+
101+ if Path :: new ( path. as_str ( ) ) . exists ( ) {
102+ let content = fs:: read_to_string ( path. as_str ( ) )
103+ . into_diagnostic ( )
104+ . wrap_err ( "Failed to read config file" ) ?;
115105
116- /// Writes pretty-formatted JSON to the given path.
117- pub fn write_json_pretty < T : serde:: Serialize > ( path : & Path , value : & T ) -> io:: Result < ( ) > {
118- let data = serde_json:: to_vec_pretty ( value) . unwrap_or_else ( |_| b"{}" . to_vec ( ) ) ;
119- fs:: write ( path, data)
106+ toml:: from_str ( & content)
107+ . into_diagnostic ( )
108+ . wrap_err ( "Failed to parse config file" )
109+ } else {
110+ let cfg = AppConfig :: default ( ) ;
111+ let content = toml:: to_string_pretty ( & cfg)
112+ . into_diagnostic ( )
113+ . wrap_err ( "Failed to serialize default config" ) ?;
114+ toml:: from_str ( & content)
115+ . into_diagnostic ( )
116+ . wrap_err ( "Failed to parse default config" )
117+ }
120118}
121119
122- /// Returns current UNIX epoch seconds.
123- pub fn now_epoch_secs ( ) -> u64 {
124- SystemTime :: now ( )
125- . duration_since ( UNIX_EPOCH )
126- . unwrap_or_default ( )
127- . as_secs ( )
120+ /// Saves a raw TOML table to the config file.
121+ pub fn save_config_table ( table : & toml:: Table ) -> io:: Result < ( ) > {
122+ if let Some ( path) = default_config_path ( ) {
123+ let content = toml:: to_string_pretty ( table) . map_err ( io:: Error :: other) ?;
124+ fs:: write ( path. as_str ( ) , content)
125+ } else {
126+ Err ( io:: Error :: new (
127+ io:: ErrorKind :: NotFound ,
128+ "Could not determine config path" ,
129+ ) )
130+ }
128131}
129132
130133/// Returns the default directory where wad hashtables should be looked up.
131134/// Uses the user's Documents folder: Documents/LeagueToolkit/bin_hashtables
135+ /// Falls back to ~/.local/share/LeagueToolkit/bin_hashtables on Linux if Documents isn't available
132136pub fn default_hashtable_dir ( ) -> Option < Utf8PathBuf > {
133- let user_dirs = directories_next:: UserDirs :: new ( ) ?;
134- let doc_dir = user_dirs. document_dir ( ) ?;
135- let mut path = doc_dir. to_path_buf ( ) ;
136- path. push ( "LeagueToolkit" ) ;
137+ // Try Documents folder first (Windows, macOS, and some Linux setups)
138+ if let Some ( doc_dir) =
139+ directories_next:: UserDirs :: new ( ) . and_then ( |u| u. document_dir ( ) . map ( |p| p. to_path_buf ( ) ) )
140+ {
141+ let mut path = doc_dir;
142+ path. push ( "LeagueToolkit" ) ;
143+ path. push ( "bin_hashtables" ) ;
144+ if let Ok ( utf8_path) = Utf8PathBuf :: from_path_buf ( path) {
145+ return Some ( utf8_path) ;
146+ }
147+ }
148+
149+ // Fallback: use data directory (~/.local/share on Linux, AppData on Windows)
150+ let data_dirs = directories_next:: ProjectDirs :: from ( "" , "" , "LeagueToolkit" ) ?;
151+ let mut path = data_dirs. data_dir ( ) . to_path_buf ( ) ;
137152 path. push ( "bin_hashtables" ) ;
138153 Utf8PathBuf :: from_path_buf ( path) . ok ( )
139154}
0 commit comments