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);