@@ -9,8 +9,8 @@ use pdm::app::{
99use pdm:: bitcoin_config:: {
1010 parse_config as parse_bitcoin_config, save_config as save_bitcoin_config,
1111} ;
12- use pdm:: p2poolv2_config:: { apply_edit as apply_p2pool_edit, flatten_config} ;
1312use pdm:: components:: settings_view:: { FIELDS , FieldKind } ;
13+ use pdm:: p2poolv2_config:: { apply_edit as apply_p2pool_edit, flatten_config} ;
1414use pdm:: settings:: { load_settings, save_settings} ;
1515use pdm:: ui;
1616use std:: ops:: ControlFlow ;
@@ -133,6 +133,38 @@ where
133133 }
134134 }
135135
136+ // P2Pool config
137+ CurrentScreen :: P2PoolConfig => {
138+ if app. p2pool_conf_path . is_some ( ) {
139+ if app. p2pool_config_view . sidebar_focused {
140+ match key. code {
141+ KeyCode :: Enter => {
142+ app. p2pool_config_view . sidebar_focused = false ;
143+ AppAction :: None
144+ }
145+ k => sidebar_nav ( k, app) ,
146+ }
147+ } else {
148+ // Build flat entry list and delegate to the view
149+ let entries = app
150+ . p2pool_config
151+ . as_ref ( )
152+ . map ( |cfg| flatten_config ( cfg) )
153+ . unwrap_or_default ( ) ;
154+ app. p2pool_config_view . handle_input ( key, & entries)
155+ }
156+ } else {
157+ match key. code {
158+ KeyCode :: Enter => {
159+ app. p2pool_config_view . warning_message = None ;
160+ AppAction :: OpenExplorer ( ExplorerTrigger :: P2PoolConfig )
161+ }
162+ KeyCode :: Esc => AppAction :: CloseModal ,
163+ k => sidebar_nav ( k, app) ,
164+ }
165+ }
166+ }
167+
136168 CurrentScreen :: Settings => {
137169 if app. settings_view . sidebar_focused {
138170 match key. code {
@@ -178,13 +210,19 @@ fn bootstrap_from_settings(app: &mut App) {
178210 }
179211 }
180212
181- // P2Pool config
182- if let Some ( path) = & app. settings . p2pool_conf_path {
183- app. p2pool_conf_path = Some ( path. clone ( ) ) ;
184- if let Some ( p) = path. to_str ( )
185- && let Ok ( cfg) = P2PoolConfig :: load ( p)
186- {
187- app. p2pool_config = Some ( cfg) ;
213+ // P2Pool config — only set the path when the config is actually loadable
214+ if let Some ( path) = & app. settings . p2pool_conf_path . clone ( ) {
215+ if let Some ( p) = path. to_str ( ) {
216+ match P2PoolConfig :: load ( p) {
217+ Ok ( cfg) => {
218+ app. p2pool_conf_path = Some ( path. clone ( ) ) ;
219+ app. p2pool_config = Some ( cfg) ;
220+ }
221+ Err ( e) => {
222+ eprintln ! ( "pdm: failed to load p2pool config on startup: {e}" ) ;
223+ // Leave both as None so the view prompts the user to re-select
224+ }
225+ }
188226 }
189227 }
190228}
@@ -228,11 +266,34 @@ fn handle_action(action: AppAction, app: &mut App) -> Result<ControlFlow<()>> {
228266 if let Some ( trigger) = app. explorer_trigger . take ( ) {
229267 match trigger {
230268 ExplorerTrigger :: P2PoolConfig => {
231- app. p2pool_conf_path = Some ( path. clone ( ) ) ;
232- if let Some ( p) = path. to_str ( )
233- && let Ok ( cfg) = P2PoolConfig :: load ( p)
234- {
235- app. p2pool_config = Some ( cfg) ;
269+ match P2PoolConfig :: load ( path. to_str ( ) . unwrap_or_default ( ) ) {
270+ Ok ( cfg) => {
271+ // Sanity check — a valid p2pool config must have
272+ // a stratum section with at least a hostname
273+
274+ if cfg. stratum . hostname . is_empty ( ) {
275+ app. p2pool_config_view . warning_message = Some (
276+ "Config loaded but appears invalid: stratum.hostname is empty. Select another file."
277+ . to_string ( ) ,
278+ ) ;
279+ app. p2pool_conf_path = None ;
280+ app. p2pool_config = None ;
281+ } else {
282+ app. p2pool_conf_path = Some ( path. clone ( ) ) ;
283+ app. p2pool_config = Some ( cfg) ;
284+ app. p2pool_config_view . sidebar_focused = false ;
285+ app. p2pool_config_view . warning_message = None ;
286+ app. p2pool_config_view . selected_index = 0 ;
287+ }
288+ }
289+ Err ( e) => {
290+ app. p2pool_config_view . warning_message = Some ( format ! (
291+ "Failed to load P2Pool config: {}. Select another file." ,
292+ e
293+ ) ) ;
294+ app. p2pool_conf_path = None ;
295+ app. p2pool_config = None ;
296+ }
236297 }
237298 app. current_screen = CurrentScreen :: P2PoolConfig ;
238299 app. settings . p2pool_conf_path = Some ( path. clone ( ) ) ;
@@ -310,15 +371,28 @@ fn handle_action(action: AppAction, app: &mut App) -> Result<ControlFlow<()>> {
310371 should_save = false ;
311372 }
312373 } ,
313- 1 => {
314- app. p2pool_conf_path = Some ( path. clone ( ) ) ;
315- if let Some ( p) = path. to_str ( )
316- && let Ok ( cfg) = P2PoolConfig :: load ( p)
317- {
318- app. p2pool_config = Some ( cfg) ;
374+ 1 => match P2PoolConfig :: load ( path. to_str ( ) . unwrap_or_default ( ) ) {
375+ Ok ( cfg) => {
376+ if cfg. stratum . hostname . is_empty ( ) {
377+ app. settings_view . save_error = Some (
378+ "Config appears invalid: stratum.hostname is empty."
379+ . to_string ( ) ,
380+ ) ;
381+ should_save = false ;
382+ } else {
383+ app. p2pool_config = Some ( cfg) ;
384+ app. settings . p2pool_conf_path = Some ( path. clone ( ) ) ;
385+ app. p2pool_config_view . warning_message = None ;
386+ app. p2pool_config_view . selected_index = 0 ;
387+ app. settings . p2pool_conf_path = Some ( path. clone ( ) ) ;
388+ }
319389 }
320- app. settings . p2pool_conf_path = Some ( path. clone ( ) ) ;
321- }
390+ Err ( e) => {
391+ app. settings_view . save_error =
392+ Some ( format ! ( "Failed to load P2Pool config: {}" , e) ) ;
393+ should_save = false ;
394+ }
395+ } ,
322396 2 => app. settings . ln_conf_path = Some ( path. clone ( ) ) ,
323397 3 => app. settings . shares_market_conf_path = Some ( path. clone ( ) ) ,
324398 4 => app. settings . settings_dir_override = Some ( path. clone ( ) ) ,
@@ -380,12 +454,40 @@ fn handle_action(action: AppAction, app: &mut App) -> Result<ControlFlow<()>> {
380454 app. settings_view . save_error = Some ( format ! ( "Save failed: {e}" ) ) ;
381455 }
382456 }
457+ AppAction :: CommitP2PoolEdit ( index, value) => {
458+ if let Some ( cfg) = app. p2pool_config . as_mut ( ) {
459+ match apply_p2pool_edit ( cfg, index, & value) {
460+ Ok ( ( ) ) => {
461+ app. p2pool_config_view . warning_message = None ;
462+ }
463+ Err ( e) => {
464+ app. p2pool_config_view . warning_message = Some ( e) ;
465+ }
466+ }
467+ }
468+ }
469+
470+ AppAction :: SaveP2PoolConfig => {
471+ if let ( Some ( path) , Some ( cfg) ) =
472+ ( app. p2pool_conf_path . clone ( ) , app. p2pool_config . as_ref ( ) )
473+ {
474+ match save_p2pool_config ( & path, cfg) {
475+ Ok ( ( ) ) => {
476+ app. p2pool_config_view . save_message =
477+ Some ( "Configuration correctly saved" . to_string ( ) ) ;
478+ }
479+ Err ( e) => {
480+ app. p2pool_config_view . warning_message =
481+ Some ( format ! ( "Save failed: {}" , e) ) ;
482+ }
483+ }
484+ }
485+ }
383486
384487 AppAction :: None => { }
385488 }
386489
387490 Ok ( ControlFlow :: Continue ( ( ) ) )
388- Ok ( false )
389491}
390492
391493/// Matches the TOML type of an existing item and parses the new string
@@ -1119,7 +1221,7 @@ port = 46884
11191221 // Write a minimal but syntactically valid TOML file; P2PoolConfig::load
11201222 // may fail to parse it, but bootstrap_from_settings should at least set
11211223 // app.p2pool_conf_path regardless of whether the config is parseable.
1122- std :: fs :: write ( & path, "" ) . unwrap ( ) ;
1224+ let cfg = write_valid_p2pool_toml ( & path) ;
11231225
11241226 let mut app = App :: new ( ) ;
11251227 app. settings . p2pool_conf_path = Some ( path. clone ( ) ) ;
@@ -1159,7 +1261,7 @@ port = 46884
11591261 let dir = tempdir ( ) . unwrap ( ) ;
11601262 redirect_saves_to ( & dir) ;
11611263 let path = dir. path ( ) . join ( "p2pool.toml" ) ;
1162- std :: fs :: write ( & path, "" ) . unwrap ( ) ;
1264+ let cfg = write_valid_p2pool_toml ( & path) ;
11631265
11641266 let mut app = App :: new ( ) ;
11651267 app. explorer_trigger = Some ( ExplorerTrigger :: Settings ( 1 ) ) ;
0 commit comments