From 9e808c57b4dcc678f561b032194614656c192637 Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Mon, 16 Mar 2026 22:45:30 -0600 Subject: [PATCH] refactor: coord prefs/theme saves in one staged path; drop broken manager wrappers --- src/common/cfgfiles.c | 128 +++++++++++++++++--------- src/common/cfgfiles.h | 3 + src/fe-gtk/fe-gtk.vcxproj | 2 + src/fe-gtk/fe-gtk.vcxproj.filters | 8 +- src/fe-gtk/maingui.c | 31 ++++++- src/fe-gtk/meson.build | 1 + src/fe-gtk/preferences-persistence.c | 66 ++++++++++++++ src/fe-gtk/preferences-persistence.h | 18 ++++ src/fe-gtk/setup.c | 20 ++++- src/fe-gtk/theme/theme-runtime.c | 130 ++++++++++++++++++++++++--- src/fe-gtk/theme/theme-runtime.h | 3 + 11 files changed, 346 insertions(+), 64 deletions(-) create mode 100644 src/fe-gtk/preferences-persistence.c create mode 100644 src/fe-gtk/preferences-persistence.h diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c index 64d9ca4a..e4b16956 100644 --- a/src/common/cfgfiles.c +++ b/src/common/cfgfiles.c @@ -1009,15 +1009,51 @@ load_config (void) return 0; } -int -save_config (void) +static int +save_config_write_to_fd (int fh) { - int fh, i; - char *config, *new_config; + int i; + + if (!cfg_put_str (fh, "version", PACKAGE_VERSION)) + return 0; + + i = 0; + do + { + switch (vars[i].type) + { + case TYPE_STR: + if (!cfg_put_str (fh, vars[i].name, (char *) &prefs + vars[i].offset)) + return 0; + break; + case TYPE_INT: + case TYPE_BOOL: + if (!cfg_put_int (fh, *((int *) &prefs + vars[i].offset), vars[i].name)) + return 0; + } + + if (vars[i].after_update != NULL) + vars[i].after_update(); + i++; + } + while (vars[i].name); + + return 1; +} + +int +save_config_prepare (char **temp_path) +{ + int fh; + char *config; + char *new_config; if (check_config_dir () != 0) make_config_dirs (); + if (!temp_path) + return 0; + config = default_file (); new_config = g_strconcat (config, ".new", NULL); @@ -1028,63 +1064,67 @@ save_config (void) return 0; } - if (!cfg_put_str (fh, "version", PACKAGE_VERSION)) + if (!save_config_write_to_fd (fh)) { close (fh); g_free (new_config); return 0; } - i = 0; - do - { - switch (vars[i].type) - { - case TYPE_STR: - if (!cfg_put_str (fh, vars[i].name, (char *) &prefs + vars[i].offset)) - { - close (fh); - g_free (new_config); - return 0; - } - break; - case TYPE_INT: - case TYPE_BOOL: - if (!cfg_put_int (fh, *((int *) &prefs + vars[i].offset), vars[i].name)) - { - close (fh); - g_free (new_config); - return 0; - } - } - - if (vars[i].after_update != NULL) - { - vars[i].after_update(); - } - i++; - } - while (vars[i].name); - if (close (fh) == -1) { g_free (new_config); return 0; } -#ifdef WIN32 - g_unlink (config); /* win32 can't rename to an existing file */ -#endif - if (g_rename (new_config, config) == -1) - { - g_free (new_config); + *temp_path = new_config; + return 1; +} + +int +save_config_finalize (const char *temp_path) +{ + char *config; + + if (!temp_path) + return 0; + + config = default_file (); +#ifdef WIN32 + g_unlink (config); +#endif + if (g_rename (temp_path, config) == -1) return 0; - } - g_free (new_config); return 1; } +void +save_config_discard (const char *temp_path) +{ + if (!temp_path) + return; + + g_unlink (temp_path); +} + +int +save_config (void) +{ + char *temp_path = NULL; + int result; + + if (!save_config_prepare (&temp_path)) + return 0; + + result = save_config_finalize (temp_path); + if (!result) + save_config_discard (temp_path); + g_free (temp_path); + + return result; +} + static void set_showval (session *sess, const struct prefs *var, char *tbuf) { diff --git a/src/common/cfgfiles.h b/src/common/cfgfiles.h index eaac2f32..55dbc38d 100644 --- a/src/common/cfgfiles.h +++ b/src/common/cfgfiles.h @@ -43,6 +43,9 @@ int make_config_dirs (void); int make_dcc_dirs (void); int load_config (void); int save_config (void); +int save_config_prepare (char **temp_path); +int save_config_finalize (const char *temp_path); +void save_config_discard (const char *temp_path); void list_free (GSList ** list); void list_loadconf (char *file, GSList ** list, char *defaultconf); int list_delentry (GSList ** list, char *name); diff --git a/src/fe-gtk/fe-gtk.vcxproj b/src/fe-gtk/fe-gtk.vcxproj index 63fc5cf1..a53f9314 100644 --- a/src/fe-gtk/fe-gtk.vcxproj +++ b/src/fe-gtk/fe-gtk.vcxproj @@ -61,6 +61,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out- + @@ -107,6 +108,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out- + diff --git a/src/fe-gtk/fe-gtk.vcxproj.filters b/src/fe-gtk/fe-gtk.vcxproj.filters index 0267bc56..66288085 100644 --- a/src/fe-gtk/fe-gtk.vcxproj.filters +++ b/src/fe-gtk/fe-gtk.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -51,6 +51,9 @@ Header Files + + Header Files + Header Files @@ -182,6 +185,9 @@ Source Files + + Source Files + Source Files diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index 0d76f325..5a520b7a 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -52,6 +52,7 @@ #include "theme/theme-palette.h" #include "maingui.h" #include "menu.h" +#include "preferences-persistence.h" #include "fkeys.h" #include "userlistgui.h" #include "chanview.h" @@ -178,15 +179,37 @@ static void mg_apply_emoji_fallback_widget (GtkWidget *widget); static guint mg_config_save_source_id = 0; static gboolean mg_config_prefs_dirty = FALSE; +static void +mg_show_save_failure (const PreferencesPersistenceResult *save_result) +{ + char buffer[192]; + + if (!save_result || save_result->success) + return; + + if (save_result->partial_failure) + { + fe_message (_("Could not fully save preferences. zoitechat.conf was written, but colors.conf failed. Retry is possible."), FE_MSG_ERROR); + return; + } + + g_snprintf (buffer, sizeof (buffer), _("Could not save preferences (%s). Retry is possible."), save_result->failed_file ? save_result->failed_file : _("unknown file")); + fe_message (buffer, FE_MSG_ERROR); +} + static gboolean mg_config_save_timeout_cb (gpointer userdata) { + PreferencesPersistenceResult save_result; + mg_config_save_source_id = 0; if (!mg_config_prefs_dirty) return G_SOURCE_REMOVE; - save_config (); + save_result = preferences_persistence_save_all (); + if (!save_result.success) + mg_show_save_failure (&save_result); mg_config_prefs_dirty = FALSE; return G_SOURCE_REMOVE; @@ -209,6 +232,8 @@ mg_schedule_config_save (void) static void mg_flush_config_save (void) { + PreferencesPersistenceResult save_result; + if (mg_config_save_source_id != 0) { g_source_remove (mg_config_save_source_id); @@ -217,7 +242,9 @@ mg_flush_config_save (void) if (mg_config_prefs_dirty) { - save_config (); + save_result = preferences_persistence_save_all (); + if (!save_result.success) + mg_show_save_failure (&save_result); mg_config_prefs_dirty = FALSE; } } diff --git a/src/fe-gtk/meson.build b/src/fe-gtk/meson.build index 90fe134e..561016ae 100644 --- a/src/fe-gtk/meson.build +++ b/src/fe-gtk/meson.build @@ -28,6 +28,7 @@ zoitechat_gtk_sources = [ 'maingui.c', 'notifygui.c', 'pixmaps.c', + 'preferences-persistence.c', 'plugin-tray.c', 'plugin-notification.c', 'rawlog.c', diff --git a/src/fe-gtk/preferences-persistence.c b/src/fe-gtk/preferences-persistence.c new file mode 100644 index 00000000..aa6ac597 --- /dev/null +++ b/src/fe-gtk/preferences-persistence.c @@ -0,0 +1,66 @@ +#include "preferences-persistence.h" + +#include "../common/cfgfiles.h" +#include "theme/theme-runtime.h" + +PreferencesPersistenceResult +preferences_persistence_save_all (void) +{ + PreferencesPersistenceResult result; + char *config_temp; + char *theme_temp; + + result.success = FALSE; + result.retry_possible = TRUE; + result.partial_failure = FALSE; + result.config_failed = FALSE; + result.theme_failed = FALSE; + result.failed_file = NULL; + config_temp = NULL; + theme_temp = NULL; + + if (!save_config_prepare (&config_temp)) + { + result.config_failed = TRUE; + goto done; + } + + if (!theme_runtime_save_prepare (&theme_temp)) + { + result.theme_failed = TRUE; + goto done; + } + + if (!save_config_finalize (config_temp)) + { + result.config_failed = TRUE; + goto done; + } + + if (!theme_runtime_save_finalize (theme_temp)) + { + result.theme_failed = TRUE; + result.partial_failure = TRUE; + goto done; + } + + result.success = TRUE; + + done: + if (!result.success) + { + if (result.config_failed && result.theme_failed) + result.failed_file = "zoitechat.conf and colors.conf"; + else if (result.config_failed) + result.failed_file = "zoitechat.conf"; + else if (result.theme_failed) + result.failed_file = "colors.conf"; + } + + save_config_discard (config_temp); + theme_runtime_save_discard (theme_temp); + g_free (config_temp); + g_free (theme_temp); + + return result; +} diff --git a/src/fe-gtk/preferences-persistence.h b/src/fe-gtk/preferences-persistence.h new file mode 100644 index 00000000..9a4c1f5a --- /dev/null +++ b/src/fe-gtk/preferences-persistence.h @@ -0,0 +1,18 @@ +#ifndef ZOITECHAT_PREFERENCES_PERSISTENCE_H +#define ZOITECHAT_PREFERENCES_PERSISTENCE_H + +#include + +typedef struct +{ + gboolean success; + gboolean retry_possible; + gboolean partial_failure; + gboolean config_failed; + gboolean theme_failed; + const char *failed_file; +} PreferencesPersistenceResult; + +PreferencesPersistenceResult preferences_persistence_save_all (void); + +#endif diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index d980aa70..3a73edfe 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -40,6 +40,7 @@ #include "maingui.h" #include "pixmaps.h" #include "menu.h" +#include "preferences-persistence.h" #include "plugin-tray.h" #include "notifications/notification-backend.h" @@ -2191,12 +2192,23 @@ setup_apply (struct zoitechatprefs *pr) static void setup_ok_cb (GtkWidget *but, GtkWidget *win) { + PreferencesPersistenceResult save_result; + char buffer[192]; + gtk_widget_destroy (win); setup_apply (&setup_prefs); - if (!save_config ()) - fe_message (_("Could not save zoitechat.conf."), FE_MSG_ERROR); - if (!theme_manager_save_preferences ()) - fe_message (_("Could not save colors.conf."), FE_MSG_ERROR); + save_result = preferences_persistence_save_all (); + if (save_result.success) + return; + + if (save_result.partial_failure) + { + fe_message (_("Preferences were partially saved. zoitechat.conf succeeded, colors.conf failed. Retry is possible."), FE_MSG_ERROR); + return; + } + + g_snprintf (buffer, sizeof (buffer), _("Could not save preferences (%s). Retry is possible."), save_result.failed_file ? save_result.failed_file : _("unknown file")); + fe_message (buffer, FE_MSG_ERROR); } static GtkWidget * diff --git a/src/fe-gtk/theme/theme-runtime.c b/src/fe-gtk/theme/theme-runtime.c index 4f548ae6..f7765399 100644 --- a/src/fe-gtk/theme/theme-runtime.c +++ b/src/fe-gtk/theme/theme-runtime.c @@ -449,12 +449,11 @@ theme_runtime_load (void) user_colors_valid = TRUE; } -gboolean -theme_runtime_save (void) +static gboolean +theme_runtime_save_to_fd (int fh) { size_t i; size_t j; - int fh; ThemePalettePersistenceMode modes[] = { { "light", "color", &light_palette, &user_colors_valid }, { "dark", "dark_color", &dark_palette, &dark_user_colors_valid }, @@ -485,15 +484,8 @@ theme_runtime_save (void) else modes[1].palette = &dark_palette; - fh = zoitechat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); - if (fh == -1) - return FALSE; - if (!cfg_put_int (fh, THEME_PALETTE_MIGRATION_MARKER_VALUE, (char *) THEME_PALETTE_MIGRATION_MARKER_KEY)) - { - close (fh); return FALSE; - } for (i = 0; i < mode_count; i++) { @@ -512,19 +504,131 @@ theme_runtime_save (void) continue; g_assert (theme_palette_get_color (modes[i].palette, def->token, &color)); if (!palette_write_token_color (fh, modes[i].mode_name, def, &color)) - { - close (fh); return FALSE; - } } } + return TRUE; +} + +gboolean +theme_runtime_save_prepare (char **temp_path) +{ + int fh; + const char *temp_name; + + if (!temp_path) + return FALSE; + + temp_name = "colors.conf.new"; + fh = zoitechat_open_file (temp_name, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh == -1) + return FALSE; + + if (!theme_runtime_save_to_fd (fh)) + { + close (fh); + return FALSE; + } + if (close (fh) == -1) return FALSE; + *temp_path = g_strdup (temp_name); return TRUE; } +gboolean +theme_runtime_save_finalize (const char *temp_path) +{ + int src_fh; + int dst_fh; + char buffer[4096]; + ssize_t read_len; + + if (!temp_path) + return FALSE; + + src_fh = zoitechat_open_file (temp_path, O_RDONLY, 0, XOF_DOMODE); + if (src_fh == -1) + return FALSE; + + dst_fh = zoitechat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (dst_fh == -1) + { + close (src_fh); + return FALSE; + } + + while ((read_len = read (src_fh, buffer, sizeof (buffer))) > 0) + { + ssize_t offset; + + offset = 0; + while (offset < read_len) + { + ssize_t written; + + written = write (dst_fh, buffer + offset, (size_t) (read_len - offset)); + if (written <= 0) + { + close (src_fh); + close (dst_fh); + return FALSE; + } + offset += written; + } + } + + if (read_len < 0) + { + close (src_fh); + close (dst_fh); + return FALSE; + } + + if (close (src_fh) == -1) + { + close (dst_fh); + return FALSE; + } + + if (close (dst_fh) == -1) + return FALSE; + + return TRUE; +} + +void +theme_runtime_save_discard (const char *temp_path) +{ + int fh; + + if (!temp_path) + return; + + fh = zoitechat_open_file (temp_path, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh != -1) + close (fh); +} + +gboolean +theme_runtime_save (void) +{ + char *temp_path = NULL; + gboolean result; + + if (!theme_runtime_save_prepare (&temp_path)) + return FALSE; + + result = theme_runtime_save_finalize (temp_path); + if (!result) + theme_runtime_save_discard (temp_path); + g_free (temp_path); + + return result; +} + static gboolean palette_color_eq (const GdkRGBA *a, const GdkRGBA *b) { diff --git a/src/fe-gtk/theme/theme-runtime.h b/src/fe-gtk/theme/theme-runtime.h index fb8f7eb0..d20aa6b2 100644 --- a/src/fe-gtk/theme/theme-runtime.h +++ b/src/fe-gtk/theme/theme-runtime.h @@ -19,6 +19,9 @@ typedef struct void theme_runtime_load (void); gboolean theme_runtime_save (void); +gboolean theme_runtime_save_prepare (char **temp_path); +gboolean theme_runtime_save_finalize (const char *temp_path); +void theme_runtime_save_discard (const char *temp_path); gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *palette_changed); gboolean theme_runtime_apply_dark_mode (gboolean enable); void theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *col);