diff options
Diffstat (limited to '')
-rw-r--r-- | libgimpconfig/gimpconfigwriter.c | 799 |
1 files changed, 799 insertions, 0 deletions
diff --git a/libgimpconfig/gimpconfigwriter.c b/libgimpconfig/gimpconfigwriter.c new file mode 100644 index 0000000..bac83f3 --- /dev/null +++ b/libgimpconfig/gimpconfigwriter.c @@ -0,0 +1,799 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpConfigWriter + * Copyright (C) 2003 Sven Neumann <sven@gimp.org> + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gio/gio.h> + +#ifdef G_OS_WIN32 +#include <gio/gwin32outputstream.h> +#else +#include <gio/gunixoutputstream.h> +#endif + +#include "libgimpbase/gimpbase.h" + +#include "gimpconfigtypes.h" + +#include "gimpconfigwriter.h" +#include "gimpconfig-iface.h" +#include "gimpconfig-error.h" +#include "gimpconfig-serialize.h" +#include "gimpconfig-utils.h" + +#include "libgimp/libgimp-intl.h" + + +/** + * SECTION: gimpconfigwriter + * @title: GimpConfigWriter + * @short_description: Functions for writing config info to a file for + * libgimpconfig. + * + * Functions for writing config info to a file for libgimpconfig. + **/ + + +struct _GimpConfigWriter +{ + GOutputStream *output; + GFile *file; + GError *error; + GString *buffer; + gboolean comment; + gint depth; + gint marker; +}; + + +static inline void gimp_config_writer_flush (GimpConfigWriter *writer); +static inline void gimp_config_writer_newline (GimpConfigWriter *writer); +static gboolean gimp_config_writer_close_output (GimpConfigWriter *writer, + GError **error); + +static inline void +gimp_config_writer_flush (GimpConfigWriter *writer) +{ + GError *error = NULL; + + if (! writer->output) + return; + + if (! g_output_stream_write_all (writer->output, + writer->buffer->str, + writer->buffer->len, + NULL, NULL, &error)) + { + g_set_error (&writer->error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_WRITE, + _("Error writing to '%s': %s"), + writer->file ? + gimp_file_get_utf8_name (writer->file) : "output stream", + error->message); + g_clear_error (&error); + } + + g_string_truncate (writer->buffer, 0); +} + +static inline void +gimp_config_writer_newline (GimpConfigWriter *writer) +{ + gint i; + + g_string_append_c (writer->buffer, '\n'); + + if (writer->comment) + g_string_append_len (writer->buffer, "# ", 2); + + for (i = 0; i < writer->depth; i++) + g_string_append_len (writer->buffer, " ", 4); +} + +/** + * gimp_config_writer_new_file: + * @filename: a filename + * @atomic: if %TRUE the file is written atomically + * @header: text to include as comment at the top of the file + * @error: return location for errors + * + * Creates a new #GimpConfigWriter and sets it up to write to + * @filename. If @atomic is %TRUE, a temporary file is used to avoid + * possible race conditions. The temporary file is then moved to + * @filename when the writer is closed. + * + * Return value: a new #GimpConfigWriter or %NULL in case of an error + * + * Since: 2.4 + **/ +GimpConfigWriter * +gimp_config_writer_new_file (const gchar *filename, + gboolean atomic, + const gchar *header, + GError **error) +{ + GimpConfigWriter *writer; + GFile *file; + + g_return_val_if_fail (filename != NULL, NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + file = g_file_new_for_path (filename); + + writer = gimp_config_writer_new_gfile (file, atomic, header, error); + + g_object_unref (file); + + return writer; +} + +/** + * gimp_config_writer_new_gfile: + * @file: a #GFile + * @atomic: if %TRUE the file is written atomically + * @header: text to include as comment at the top of the file + * @error: return location for errors + * + * Creates a new #GimpConfigWriter and sets it up to write to + * @file. If @atomic is %TRUE, a temporary file is used to avoid + * possible race conditions. The temporary file is then moved to @file + * when the writer is closed. + * + * Return value: a new #GimpConfigWriter or %NULL in case of an error + * + * Since: 2.10 + **/ +GimpConfigWriter * +gimp_config_writer_new_gfile (GFile *file, + gboolean atomic, + const gchar *header, + GError **error) +{ + GimpConfigWriter *writer; + GOutputStream *output; + GFile *dir; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + dir = g_file_get_parent (file); + if (dir && ! g_file_query_exists (dir, NULL)) + { + if (! g_file_make_directory_with_parents (dir, NULL, error)) + g_prefix_error (error, + _("Could not create directory '%s' for '%s': "), + gimp_file_get_utf8_name (dir), + gimp_file_get_utf8_name (file)); + } + g_object_unref (dir); + + if (error && *error) + return NULL; + + if (atomic) + { + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (! output) + g_prefix_error (error, + _("Could not create temporary file for '%s': "), + gimp_file_get_utf8_name (file)); + } + else + { + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, + NULL, error)); + } + + if (! output) + return NULL; + + writer = g_slice_new0 (GimpConfigWriter); + + writer->output = output; + writer->file = g_object_ref (file); + writer->buffer = g_string_new (NULL); + + if (header) + { + gimp_config_writer_comment (writer, header); + gimp_config_writer_linefeed (writer); + } + + return writer; +} + +/** + * gimp_config_writer_new_stream: + * @output: a #GOutputStream + * @header: text to include as comment at the top of the file + * @error: return location for errors + * + * Creates a new #GimpConfigWriter and sets it up to write to + * @output. + * + * Return value: a new #GimpConfigWriter or %NULL in case of an error + * + * Since: 2.10 + **/ +GimpConfigWriter * +gimp_config_writer_new_stream (GOutputStream *output, + const gchar *header, + GError **error) +{ + GimpConfigWriter *writer; + + g_return_val_if_fail (G_IS_OUTPUT_STREAM (output), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + writer = g_slice_new0 (GimpConfigWriter); + + writer->output = g_object_ref (output); + writer->buffer = g_string_new (NULL); + + if (header) + { + gimp_config_writer_comment (writer, header); + gimp_config_writer_linefeed (writer); + } + + return writer; +} + +/** + * gimp_config_writer_new_fd: + * @fd: + * + * Return value: a new #GimpConfigWriter or %NULL in case of an error + * + * Since: 2.4 + **/ +GimpConfigWriter * +gimp_config_writer_new_fd (gint fd) +{ + GimpConfigWriter *writer; + + g_return_val_if_fail (fd > 0, NULL); + + writer = g_slice_new0 (GimpConfigWriter); + +#ifdef G_OS_WIN32 + writer->output = g_win32_output_stream_new ((gpointer) fd, FALSE); +#else + writer->output = g_unix_output_stream_new (fd, FALSE); +#endif + + writer->buffer = g_string_new (NULL); + + return writer; +} + +/** + * gimp_config_writer_new_string: + * @string: + * + * Return value: a new #GimpConfigWriter or %NULL in case of an error + * + * Since: 2.4 + **/ +GimpConfigWriter * +gimp_config_writer_new_string (GString *string) +{ + GimpConfigWriter *writer; + + g_return_val_if_fail (string != NULL, NULL); + + writer = g_slice_new0 (GimpConfigWriter); + + writer->buffer = string; + + return writer; +} + +/** + * gimp_config_writer_comment_mode: + * @writer: a #GimpConfigWriter + * @enable: %TRUE to enable comment mode, %FALSE to disable it + * + * This function toggles whether the @writer should create commented + * or uncommented output. This feature is used to generate the + * system-wide installed gimprc that documents the default settings. + * + * Since comments have to start at the beginning of a line, this + * function will insert a newline if necessary. + * + * Since: 2.4 + **/ +void +gimp_config_writer_comment_mode (GimpConfigWriter *writer, + gboolean enable) +{ + g_return_if_fail (writer != NULL); + + if (writer->error) + return; + + enable = (enable ? TRUE : FALSE); + + if (writer->comment == enable) + return; + + writer->comment = enable; + + if (enable) + { + if (writer->buffer->len == 0) + g_string_append_len (writer->buffer, "# ", 2); + else + gimp_config_writer_newline (writer); + } +} + + +/** + * gimp_config_writer_open: + * @writer: a #GimpConfigWriter + * @name: name of the element to open + * + * This function writes the opening parenthesis followed by @name. + * It also increases the indentation level and sets a mark that + * can be used by gimp_config_writer_revert(). + * + * Since: 2.4 + **/ +void +gimp_config_writer_open (GimpConfigWriter *writer, + const gchar *name) +{ + g_return_if_fail (writer != NULL); + g_return_if_fail (name != NULL); + + if (writer->error) + return; + + /* store the current buffer length so we can revert to this state */ + writer->marker = writer->buffer->len; + + if (writer->depth > 0) + gimp_config_writer_newline (writer); + + writer->depth++; + + g_string_append_printf (writer->buffer, "(%s", name); +} + +/** + * gimp_config_writer_print: + * @writer: a #GimpConfigWriter + * @string: a string to write + * @len: number of bytes from @string or -1 if @string is NUL-terminated. + * + * Appends a space followed by @string to the @writer. Note that string + * must not contain any special characters that might need to be escaped. + * + * Since: 2.4 + **/ +void +gimp_config_writer_print (GimpConfigWriter *writer, + const gchar *string, + gint len) +{ + g_return_if_fail (writer != NULL); + g_return_if_fail (len == 0 || string != NULL); + + if (writer->error) + return; + + if (len < 0) + len = strlen (string); + + if (len) + { + g_string_append_c (writer->buffer, ' '); + g_string_append_len (writer->buffer, string, len); + } +} + +/** + * gimp_config_writer_printf: + * @writer: a #GimpConfigWriter + * @format: a format string as described for g_strdup_printf(). + * @...: list of arguments according to @format + * + * A printf-like function for #GimpConfigWriter. + * + * Since: 2.4 + **/ +void +gimp_config_writer_printf (GimpConfigWriter *writer, + const gchar *format, + ...) +{ + gchar *buffer; + va_list args; + + g_return_if_fail (writer != NULL); + g_return_if_fail (format != NULL); + + if (writer->error) + return; + + va_start (args, format); + buffer = g_strdup_vprintf (format, args); + va_end (args); + + g_string_append_c (writer->buffer, ' '); + g_string_append (writer->buffer, buffer); + + g_free (buffer); +} + +/** + * gimp_config_writer_string: + * @writer: a #GimpConfigWriter + * @string: a NUL-terminated string + * + * Writes a string value to @writer. The @string is quoted and special + * characters are escaped. + * + * Since: 2.4 + **/ +void +gimp_config_writer_string (GimpConfigWriter *writer, + const gchar *string) +{ + g_return_if_fail (writer != NULL); + + if (writer->error) + return; + + g_string_append_c (writer->buffer, ' '); + gimp_config_string_append_escaped (writer->buffer, string); +} + +/** + * gimp_config_writer_identifier: + * @writer: a #GimpConfigWriter + * @identifier: a NUL-terminated string + * + * Writes an identifier to @writer. The @string is *not* quoted and special + * characters are *not* escaped. + * + * Since: 2.4 + **/ +void +gimp_config_writer_identifier (GimpConfigWriter *writer, + const gchar *identifier) +{ + g_return_if_fail (writer != NULL); + g_return_if_fail (identifier != NULL); + + if (writer->error) + return; + + g_string_append_printf (writer->buffer, " %s", identifier); +} + + +/** + * gimp_config_writer_data: + * @writer: a #GimpConfigWriter + * @length: + * @data: + * + * Since: 2.4 + **/ +void +gimp_config_writer_data (GimpConfigWriter *writer, + gint length, + const guint8 *data) +{ + gint i; + + g_return_if_fail (writer != NULL); + g_return_if_fail (length >= 0); + g_return_if_fail (data != NULL || length == 0); + + if (writer->error) + return; + + g_string_append (writer->buffer, " \""); + + for (i = 0; i < length; i++) + { + if (g_ascii_isalpha (data[i])) + g_string_append_c (writer->buffer, data[i]); + else + g_string_append_printf (writer->buffer, "\\%o", data[i]); + } + + g_string_append (writer->buffer, "\""); +} + +/** + * gimp_config_writer_revert: + * @writer: a #GimpConfigWriter + * + * Reverts all changes to @writer that were done since the last call + * to gimp_config_writer_open(). This can only work if you didn't call + * gimp_config_writer_close() yet. + * + * Since: 2.4 + **/ +void +gimp_config_writer_revert (GimpConfigWriter *writer) +{ + g_return_if_fail (writer != NULL); + + if (writer->error) + return; + + g_return_if_fail (writer->depth > 0); + g_return_if_fail (writer->marker != -1); + + g_string_truncate (writer->buffer, writer->marker); + + writer->depth--; + writer->marker = -1; +} + +/** + * gimp_config_writer_close: + * @writer: a #GimpConfigWriter + * + * Closes an element opened with gimp_config_writer_open(). + * + * Since: 2.4 + **/ +void +gimp_config_writer_close (GimpConfigWriter *writer) +{ + g_return_if_fail (writer != NULL); + + if (writer->error) + return; + + g_return_if_fail (writer->depth > 0); + + g_string_append_c (writer->buffer, ')'); + + if (--writer->depth == 0) + { + g_string_append_c (writer->buffer, '\n'); + + gimp_config_writer_flush (writer); + } +} + +/** + * gimp_config_writer_finish: + * @writer: a #GimpConfigWriter + * @footer: text to include as comment at the bottom of the file + * @error: return location for possible errors + * + * This function finishes the work of @writer and frees it afterwards. + * It closes all open elements, appends an optional comment and + * releases all resources allocated by @writer. You must not access + * the @writer afterwards. + * + * Return value: %TRUE if everything could be successfully written, + * %FALSE otherwise + * + * Since: 2.4 + **/ +gboolean +gimp_config_writer_finish (GimpConfigWriter *writer, + const gchar *footer, + GError **error) +{ + gboolean success = TRUE; + + g_return_val_if_fail (writer != NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + if (writer->depth < 0) + { + g_warning ("gimp_config_writer_finish: depth < 0 !!"); + } + else + { + while (writer->depth) + gimp_config_writer_close (writer); + } + + if (footer) + { + gimp_config_writer_linefeed (writer); + gimp_config_writer_comment (writer, footer); + } + + if (writer->output) + { + success = gimp_config_writer_close_output (writer, error); + + if (writer->file) + g_object_unref (writer->file); + + g_string_free (writer->buffer, TRUE); + } + + if (writer->error) + { + if (error && *error == NULL) + g_propagate_error (error, writer->error); + else + g_clear_error (&writer->error); + + success = FALSE; + } + + g_slice_free (GimpConfigWriter, writer); + + return success; +} + +void +gimp_config_writer_linefeed (GimpConfigWriter *writer) +{ + g_return_if_fail (writer != NULL); + + if (writer->error) + return; + + if (writer->output && writer->buffer->len == 0 && !writer->comment) + { + GError *error = NULL; + + if (! g_output_stream_write_all (writer->output, "\n", 1, + NULL, NULL, &error)) + { + g_set_error (&writer->error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_WRITE, + _("Error writing to '%s': %s"), + writer->file ? + gimp_file_get_utf8_name (writer->file) : "output stream", + error->message); + g_clear_error (&error); + } + } + else + { + gimp_config_writer_newline (writer); + } +} + +/** + * gimp_config_writer_comment: + * @writer: a #GimpConfigWriter + * @comment: the comment to write (ASCII only) + * + * Appends the @comment to @str and inserts linebreaks and hash-marks to + * format it as a comment. Note that this function does not handle non-ASCII + * characters. + * + * Since: 2.4 + **/ +void +gimp_config_writer_comment (GimpConfigWriter *writer, + const gchar *comment) +{ + const gchar *s; + gboolean comment_mode; + gint i, len, space; + +#define LINE_LENGTH 75 + + g_return_if_fail (writer != NULL); + + if (writer->error) + return; + + g_return_if_fail (writer->depth == 0); + + if (!comment) + return; + + comment_mode = writer->comment; + gimp_config_writer_comment_mode (writer, TRUE); + + len = strlen (comment); + + while (len > 0) + { + for (s = comment, i = 0, space = 0; + *s != '\n' && (i <= LINE_LENGTH || space == 0) && i < len; + s++, i++) + { + if (g_ascii_isspace (*s)) + space = i; + } + + if (i > LINE_LENGTH && space && *s != '\n') + i = space; + + g_string_append_len (writer->buffer, comment, i); + + i++; + + comment += i; + len -= i; + + if (len > 0) + gimp_config_writer_newline (writer); + } + + gimp_config_writer_comment_mode (writer, comment_mode); + gimp_config_writer_newline (writer); + + if (writer->depth == 0) + gimp_config_writer_flush (writer); + +#undef LINE_LENGTH +} + +static gboolean +gimp_config_writer_close_output (GimpConfigWriter *writer, + GError **error) +{ + g_return_val_if_fail (writer->output != NULL, FALSE); + + if (writer->error) + { + GCancellable *cancellable = g_cancellable_new (); + + /* Cancel the overwrite initiated by g_file_replace(). */ + g_cancellable_cancel (cancellable); + g_output_stream_close (writer->output, cancellable, NULL); + g_object_unref (cancellable); + + g_object_unref (writer->output); + writer->output = NULL; + + return FALSE; + } + + if (writer->file) + { + GError *my_error = NULL; + + if (! g_output_stream_close (writer->output, NULL, &my_error)) + { + g_set_error (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_WRITE, + _("Error writing '%s': %s"), + gimp_file_get_utf8_name (writer->file), + my_error->message); + g_clear_error (&my_error); + + g_object_unref (writer->output); + writer->output = NULL; + + return FALSE; + } + } + + g_object_unref (writer->output); + writer->output = NULL; + + return TRUE; +} |