From f96a84dbecdec6fae9d4536448aaad05f52d7fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Tr=C3=B6ger?= Date: Mon, 22 Mar 2021 08:50:59 +0100 Subject: [PATCH 1/6] SaveActions: Add configurable target directory for instantly saved files Closes #640. --- plugins/saveactions.c | 94 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 19 deletions(-) diff --git a/plugins/saveactions.c b/plugins/saveactions.c index a0a7fdc427..770b92d9f2 100644 --- a/plugins/saveactions.c +++ b/plugins/saveactions.c @@ -63,6 +63,7 @@ static struct GtkWidget *autosave_save_all_radio2; GtkWidget *instantsave_ft_combo; + GtkWidget *instantsave_entry_dir; GtkWidget *backupcopy_entry_dir; GtkWidget *backupcopy_entry_time; @@ -82,6 +83,7 @@ static gboolean autosave_save_all; static guint autosave_src_id = 0; static gchar *instantsave_default_ft; +static gchar *instantsave_target_dir; static gchar *backupcopy_backup_dir; /* path to an existing directory in locale encoding */ static gchar *backupcopy_time_fmt; @@ -91,11 +93,17 @@ static gchar *config_file; /* Ensures utf8_dir exists and is writable and - * set backup_dir to the locale encoded form of utf8_dir */ -static gboolean backupcopy_set_backup_dir(const gchar *utf8_dir) + * set target to the locale encoded form of utf8_dir */ +static gboolean store_target_directory(const gchar *utf8_dir, gchar **target) { gchar *tmp; + if (target == NULL) + { + SETPTR(*target, NULL); + return TRUE; + } + if (G_UNLIKELY(EMPTY(utf8_dir))) return FALSE; @@ -110,7 +118,7 @@ static gboolean backupcopy_set_backup_dir(const gchar *utf8_dir) } /** TODO add utils_is_file_writeable() to the plugin API and make use of it **/ - SETPTR(backupcopy_backup_dir, tmp); + SETPTR(*target, tmp); return TRUE; } @@ -260,11 +268,14 @@ static void instantsave_document_new_cb(GObject *obj, GeanyDocument *doc, gpoint { if (enable_instantsave && doc->file_name == NULL) { + const gchar *directory; gchar *new_filename; gint fd; GeanyFiletype *ft = doc->file_type; - fd = g_file_open_tmp("gis_XXXXXX", &new_filename, NULL); + directory = !EMPTY(instantsave_target_dir) ? instantsave_target_dir : g_get_tmp_dir(); + new_filename = g_build_filename(directory, "gis_XXXXXX", NULL); + fd = g_mkstemp(new_filename); if (fd != -1) close(fd); /* close the returned file descriptor as we only need the filename */ @@ -407,6 +418,9 @@ void plugin_init(GeanyData *data) instantsave_default_ft = utils_get_setting_string(config, "instantsave", "default_ft", filetypes[GEANY_FILETYPES_NONE]->name); + tmp = utils_get_setting_string(config, "instantsave", "target_dir", NULL); + store_target_directory(tmp, &instantsave_target_dir); + g_free(tmp); autosave_src_id = 0; /* mark as invalid */ autosave_interval = utils_get_setting_integer(config, "autosave", "interval", 300); @@ -419,21 +433,15 @@ void plugin_init(GeanyData *data) backupcopy_time_fmt = utils_get_setting_string( config, "backupcopy", "time_fmt", "%Y-%m-%d-%H-%M-%S"); tmp = utils_get_setting_string(config, "backupcopy", "backup_dir", g_get_tmp_dir()); - backupcopy_set_backup_dir(tmp); + store_target_directory(tmp, &backupcopy_backup_dir); + g_free(tmp); g_key_file_free(config); - g_free(tmp); } -static void backupcopy_dir_button_clicked_cb(GtkButton *button, gpointer item) +static void target_directory_button_clicked_cb(GtkButton *button, gpointer item) { - /** TODO add win32_show_pref_file_dialog to the plugin API and use it **/ -/* -#ifdef G_OS_WIN32 - win32_show_pref_file_dialog(item); -#else -*/ GtkWidget *dialog; gchar *text; @@ -471,7 +479,7 @@ static void configure_response_cb(GtkDialog *dialog, gint response, G_GNUC_UNUSE { GKeyFile *config = g_key_file_new(); gchar *str; - const gchar *text_dir, *text_time; + const gchar *backupcopy_text_dir, *instantsave_text_dir, *text_time; gchar *config_dir = g_path_get_dirname(config_file); enable_autosave = gtk_toggle_button_get_active( @@ -493,8 +501,9 @@ static void configure_response_cb(GtkDialog *dialog, gint response, G_GNUC_UNUSE g_free(instantsave_default_ft); instantsave_default_ft = gtk_combo_box_text_get_active_text( GTK_COMBO_BOX_TEXT(pref_widgets.instantsave_ft_combo)); + instantsave_text_dir = gtk_entry_get_text(GTK_ENTRY(pref_widgets.instantsave_entry_dir)); - text_dir = gtk_entry_get_text(GTK_ENTRY(pref_widgets.backupcopy_entry_dir)); + backupcopy_text_dir = gtk_entry_get_text(GTK_ENTRY(pref_widgets.backupcopy_entry_dir)); text_time = gtk_entry_get_text(GTK_ENTRY(pref_widgets.backupcopy_entry_time)); backupcopy_dir_levels = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON(pref_widgets.backupcopy_spin_dir_levels)); @@ -513,15 +522,28 @@ static void configure_response_cb(GtkDialog *dialog, gint response, G_GNUC_UNUSE if (instantsave_default_ft != NULL) g_key_file_set_string(config, "instantsave", "default_ft", instantsave_default_ft); + if (enable_instantsave && !EMPTY(instantsave_text_dir)) + { + if (store_target_directory(instantsave_text_dir, &instantsave_target_dir)) + { + g_key_file_set_string(config, "instantsave", "target_dir", instantsave_target_dir); + } + else + { + dialogs_show_msgbox(GTK_MESSAGE_ERROR, + _("Instantsave directory does not exist or is not writable.")); + } + } g_key_file_set_integer(config, "backupcopy", "dir_levels", backupcopy_dir_levels); g_key_file_set_string(config, "backupcopy", "time_fmt", text_time); SETPTR(backupcopy_time_fmt, g_strdup(text_time)); if (enable_backupcopy) { - if (!EMPTY(text_dir) && backupcopy_set_backup_dir(text_dir)) + if (!EMPTY(backupcopy_text_dir) && store_target_directory( + backupcopy_text_dir, &backupcopy_backup_dir)) { - g_key_file_set_string(config, "backupcopy", "backup_dir", text_dir); + g_key_file_set_string(config, "backupcopy", "backup_dir", backupcopy_text_dir); } else { @@ -666,9 +688,10 @@ GtkWidget *plugin_configure(GtkDialog *dialog) * Instant Save */ { - GtkWidget *combo; + GtkWidget *combo, *hbox, *entry_dir, *button, *image, *help_label; guint i; const GSList *node; + gchar *entry_dir_label_text; notebook_vbox = gtk_vbox_new(FALSE, 2); inner_vbox = gtk_vbox_new(FALSE, 5); @@ -704,6 +727,38 @@ GtkWidget *plugin_configure(GtkDialog *dialog) gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(combo), 3); gtk_label_set_mnemonic_widget(GTK_LABEL(label), combo); gtk_box_pack_start(GTK_BOX(inner_vbox), combo, FALSE, FALSE, 0); + + entry_dir_label_text = g_strdup_printf( + _("_Directory to save files in (leave empty to use the default: %s):"), g_get_tmp_dir()); + label = gtk_label_new_with_mnemonic(entry_dir_label_text); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_box_pack_start(GTK_BOX(inner_vbox), label, FALSE, FALSE, 0); + g_free(entry_dir_label_text); + + pref_widgets.instantsave_entry_dir = entry_dir = gtk_entry_new(); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry_dir); + if (!EMPTY(instantsave_target_dir)) + gtk_entry_set_text(GTK_ENTRY(entry_dir), instantsave_target_dir); + + button = gtk_button_new(); + g_signal_connect(button, "clicked", + G_CALLBACK(target_directory_button_clicked_cb), entry_dir); + + image = gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_BUTTON); + gtk_container_add(GTK_CONTAINER(button), image); + + hbox = gtk_hbox_new(FALSE, 6); + gtk_box_pack_start(GTK_BOX(hbox), entry_dir, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(inner_vbox), hbox, FALSE, FALSE, 0); + + help_label = gtk_label_new( + _("If you set the Instant Save directory to a directory " + "which is not automatically cleared,\nyou will need to cleanup instantly saved files " + "manually. The Instant Save plugin will not delete the created files.")); + gtk_label_set_use_markup(GTK_LABEL(help_label), TRUE); + gtk_misc_set_alignment(GTK_MISC(help_label), 0, 0.5); + gtk_box_pack_start(GTK_BOX(inner_vbox), help_label, FALSE, FALSE, 0); } /* * Backup Copy @@ -737,7 +792,7 @@ GtkWidget *plugin_configure(GtkDialog *dialog) button = gtk_button_new(); g_signal_connect(button, "clicked", - G_CALLBACK(backupcopy_dir_button_clicked_cb), entry_dir); + G_CALLBACK(target_directory_button_clicked_cb), entry_dir); image = gtk_image_new_from_stock(GTK_STOCK_OPEN, GTK_ICON_SIZE_BUTTON); gtk_container_add(GTK_CONTAINER(button), image); @@ -793,6 +848,7 @@ void plugin_cleanup(void) g_source_remove(autosave_src_id); g_free(instantsave_default_ft); + g_free(instantsave_target_dir); g_free(backupcopy_backup_dir); g_free(backupcopy_time_fmt); From ead067be9153871713813c501439b0200ccf9af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Tr=C3=B6ger?= Date: Mon, 22 Mar 2021 09:03:27 +0100 Subject: [PATCH 2/6] SaveActions: Fix trailing dot on InstantSave filenames for filetype None --- plugins/saveactions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/saveactions.c b/plugins/saveactions.c index 770b92d9f2..6e7d5f2905 100644 --- a/plugins/saveactions.c +++ b/plugins/saveactions.c @@ -284,7 +284,7 @@ static void instantsave_document_new_cb(GObject *obj, GeanyDocument *doc, gpoint * configured default file type */ ft = filetypes_lookup_by_name(instantsave_default_ft); - if (ft != NULL) + if (ft != NULL && !EMPTY(ft->extension)) /* add the filetype's default extension to the new filename */ SETPTR(new_filename, g_strconcat(new_filename, ".", ft->extension, NULL)); From e74d71c1a8e0e4a97f0c816ae90c351f2b448042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Tr=C3=B6ger?= Date: Mon, 29 Mar 2021 23:36:03 +0200 Subject: [PATCH 3/6] SaveActions: Fix resetting Instant Save target directory --- plugins/saveactions.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/plugins/saveactions.c b/plugins/saveactions.c index 6e7d5f2905..b11ec0c3c7 100644 --- a/plugins/saveactions.c +++ b/plugins/saveactions.c @@ -98,13 +98,7 @@ static gboolean store_target_directory(const gchar *utf8_dir, gchar **target) { gchar *tmp; - if (target == NULL) - { - SETPTR(*target, NULL); - return TRUE; - } - - if (G_UNLIKELY(EMPTY(utf8_dir))) + if (G_UNLIKELY(EMPTY(utf8_dir)) || target == NULL) return FALSE; tmp = utils_get_locale_from_utf8(utf8_dir); @@ -522,9 +516,14 @@ static void configure_response_cb(GtkDialog *dialog, gint response, G_GNUC_UNUSE if (instantsave_default_ft != NULL) g_key_file_set_string(config, "instantsave", "default_ft", instantsave_default_ft); - if (enable_instantsave && !EMPTY(instantsave_text_dir)) + if (enable_instantsave) { - if (store_target_directory(instantsave_text_dir, &instantsave_target_dir)) + if (EMPTY(instantsave_text_dir)) + { + g_key_file_set_string(config, "instantsave", "target_dir", ""); + SETPTR(instantsave_target_dir, NULL); + } + else if (store_target_directory(instantsave_text_dir, &instantsave_target_dir)) { g_key_file_set_string(config, "instantsave", "target_dir", instantsave_target_dir); } From 0c4a35692cf9fdbfa300df0570cfa2426e3be391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Tr=C3=B6ger?= Date: Mon, 29 Mar 2021 23:37:18 +0200 Subject: [PATCH 4/6] SaveActions: Handle g_mkstemp() errors and prevent duplicate files --- plugins/saveactions.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/plugins/saveactions.c b/plugins/saveactions.c index b11ec0c3c7..4026c8d9d0 100644 --- a/plugins/saveactions.c +++ b/plugins/saveactions.c @@ -267,21 +267,31 @@ static void instantsave_document_new_cb(GObject *obj, GeanyDocument *doc, gpoint gint fd; GeanyFiletype *ft = doc->file_type; - directory = !EMPTY(instantsave_target_dir) ? instantsave_target_dir : g_get_tmp_dir(); - new_filename = g_build_filename(directory, "gis_XXXXXX", NULL); - fd = g_mkstemp(new_filename); - if (fd != -1) - close(fd); /* close the returned file descriptor as we only need the filename */ - if (ft == NULL || ft->id == GEANY_FILETYPES_NONE) /* ft is NULL when a new file without template was opened, so use the * configured default file type */ ft = filetypes_lookup_by_name(instantsave_default_ft); + /* construct filename */ + directory = !EMPTY(instantsave_target_dir) ? instantsave_target_dir : g_get_tmp_dir(); + new_filename = g_build_filename(directory, "gis_XXXXXX", NULL); if (ft != NULL && !EMPTY(ft->extension)) - /* add the filetype's default extension to the new filename */ SETPTR(new_filename, g_strconcat(new_filename, ".", ft->extension, NULL)); + /* create new file */ + fd = g_mkstemp(new_filename); + if (fd == -1) + { + gchar *message = g_strdup_printf( + _("Instant Save filename could not be generated (%s)."), g_strerror(errno)); + ui_set_statusbar(TRUE, "%s", message); + g_warning("%s", message); + g_free(message); + return; + } + + close(fd); /* close the returned file descriptor as we only need the filename */ + doc->file_name = new_filename; if (doc->file_type->id == GEANY_FILETYPES_NONE) From f64374ec57c9e704b0f1d588d24edce2194da249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Tr=C3=B6ger?= Date: Sun, 4 Apr 2021 23:21:14 +0200 Subject: [PATCH 5/6] SaveActions: Fix memory leak and possible invalid access --- plugins/saveactions.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/saveactions.c b/plugins/saveactions.c index 4026c8d9d0..cf191b2ac8 100644 --- a/plugins/saveactions.c +++ b/plugins/saveactions.c @@ -287,6 +287,7 @@ static void instantsave_document_new_cb(GObject *obj, GeanyDocument *doc, gpoint ui_set_statusbar(TRUE, "%s", message); g_warning("%s", message); g_free(message); + g_free(new_filename); return; } @@ -294,7 +295,7 @@ static void instantsave_document_new_cb(GObject *obj, GeanyDocument *doc, gpoint doc->file_name = new_filename; - if (doc->file_type->id == GEANY_FILETYPES_NONE) + if (doc->file_type && doc->file_type->id == GEANY_FILETYPES_NONE) document_set_filetype(doc, filetypes_lookup_by_name(instantsave_default_ft)); /* force saving the file to enable all the related actions(tab name, filetype, etc.) */ From e024932856886f8beca486434fbc244d97217eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Tr=C3=B6ger?= Date: Sun, 30 May 2021 11:57:26 +0200 Subject: [PATCH 6/6] Use "ft" variable consistently --- plugins/saveactions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/saveactions.c b/plugins/saveactions.c index cf191b2ac8..b76b239aa2 100644 --- a/plugins/saveactions.c +++ b/plugins/saveactions.c @@ -295,7 +295,7 @@ static void instantsave_document_new_cb(GObject *obj, GeanyDocument *doc, gpoint doc->file_name = new_filename; - if (doc->file_type && doc->file_type->id == GEANY_FILETYPES_NONE) + if (ft != NULL && ft->id == GEANY_FILETYPES_NONE) document_set_filetype(doc, filetypes_lookup_by_name(instantsave_default_ft)); /* force saving the file to enable all the related actions(tab name, filetype, etc.) */