@@ -385,6 +385,83 @@ fn handle_action(action: AppAction, app: &mut App) -> Result<ControlFlow<()>> {
385385 }
386386
387387 Ok ( ControlFlow :: Continue ( ( ) ) )
388+ Ok ( false )
389+ }
390+
391+ /// Matches the TOML type of an existing item and parses the new string
392+ /// value into that same type. This prevents numeric/bool fields from
393+ /// being written back as quoted strings (e.g. port = "3333").
394+ fn typed_toml_item_like ( existing : & toml_edit:: Item , new_value : & str ) -> Result < toml_edit:: Item > {
395+ if existing. as_integer ( ) . is_some ( ) {
396+ let parsed = new_value
397+ . parse :: < i64 > ( )
398+ . map_err ( |e| anyhow:: anyhow!( "Expected integer, got '{}': {}" , new_value, e) ) ?;
399+ Ok ( toml_edit:: value ( parsed) )
400+ } else if existing. as_float ( ) . is_some ( ) {
401+ let parsed = new_value
402+ . parse :: < f64 > ( )
403+ . map_err ( |e| anyhow:: anyhow!( "Expected float, got '{}': {}" , new_value, e) ) ?;
404+ Ok ( toml_edit:: value ( parsed) )
405+ } else if existing. as_bool ( ) . is_some ( ) {
406+ let parsed = new_value. parse :: < bool > ( ) . map_err ( |e| {
407+ anyhow:: anyhow!( "Expected bool (true/false), got '{}': {}" , new_value, e)
408+ } ) ?;
409+ Ok ( toml_edit:: value ( parsed) )
410+ } else if existing. as_str ( ) . is_some ( ) {
411+ Ok ( toml_edit:: value ( new_value. to_owned ( ) ) )
412+ } else {
413+ Err ( anyhow:: anyhow!(
414+ "Unsupported TOML value type for key: {}" ,
415+ existing
416+ ) )
417+ }
418+ }
419+
420+ /// Serialize the live `P2PoolConfig` back to TOML and write it to disk.
421+ /// Saves P2Pool config by patching the original TOML file in-place.
422+ /// Uses toml_edit so comments and formatting are preserved.
423+ fn save_p2pool_config ( path : & std:: path:: Path , cfg : & P2PoolConfig ) -> Result < ( ) > {
424+ use pdm:: p2poolv2_config:: flatten_config;
425+ use toml_edit:: DocumentMut ;
426+
427+ // Read the original file so we preserve comments/ordering
428+ let original = std:: fs:: read_to_string ( path)
429+ . map_err ( |e| anyhow:: anyhow!( "Failed to read P2Pool config: {}" , e) ) ?;
430+
431+ let mut doc = original
432+ . parse :: < DocumentMut > ( )
433+ . map_err ( |e| anyhow:: anyhow!( "Failed to parse P2Pool config TOML: {}" , e) ) ?;
434+
435+ // Walk every flattened entry and patch the matching TOML key
436+ for entry in flatten_config ( cfg) {
437+ let section = entry. section . to_string ( ) ;
438+ let key = entry. key . as_str ( ) ;
439+
440+ // Skip optional fields that are unset — leave them absent in the file
441+ if !entry. enabled {
442+ continue ;
443+ }
444+
445+ if let Some ( table) = doc. get_mut ( & section) . and_then ( |v| v. as_table_mut ( ) ) {
446+ // Only update keys that already exist in the file to avoid
447+ // injecting fields the user intentionally omitted
448+ if let Some ( existing) = table. get ( key) {
449+ match typed_toml_item_like ( existing, & entry. value ) {
450+ Ok ( updated) => table[ key] = updated,
451+ Err ( e) => {
452+ // Soft error — skip this field and continue
453+ // saving the rest rather than aborting entirely
454+ eprintln ! ( "Warning: skipping {}.{}: {}" , section, key, e) ;
455+ }
456+ }
457+ }
458+ }
459+ }
460+
461+ std:: fs:: write ( path, doc. to_string ( ) )
462+ . map_err ( |e| anyhow:: anyhow!( "Failed to write P2Pool config: {}" , e) ) ?;
463+
464+ Ok ( ( ) )
388465}
389466
390467#[ cfg( test) ]
0 commit comments