1 /* LIBGIMP - The GIMP Library
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * GimpConfigWriter
5 * Copyright (C) 2003 Sven Neumann <sven@gimp.org>
6 *
7 * This library is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 3 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library. If not, see
19 * <https://www.gnu.org/licenses/>.
20 */
21
22 #include "config.h"
23
24 #include <string.h>
25
26 #include <gio/gio.h>
27
28 #ifdef G_OS_WIN32
29 #include <gio/gwin32outputstream.h>
30 #else
31 #include <gio/gunixoutputstream.h>
32 #endif
33
34 #include "libgimpbase/gimpbase.h"
35
36 #include "gimpconfigtypes.h"
37
38 #include "gimpconfigwriter.h"
39 #include "gimpconfig-iface.h"
40 #include "gimpconfig-error.h"
41 #include "gimpconfig-serialize.h"
42 #include "gimpconfig-utils.h"
43
44 #include "libgimp/libgimp-intl.h"
45
46
47 /**
48 * SECTION: gimpconfigwriter
49 * @title: GimpConfigWriter
50 * @short_description: Functions for writing config info to a file for
51 * libgimpconfig.
52 *
53 * Functions for writing config info to a file for libgimpconfig.
54 **/
55
56
57 struct _GimpConfigWriter
58 {
59 GOutputStream *output;
60 GFile *file;
61 GError *error;
62 GString *buffer;
63 gboolean comment;
64 gint depth;
65 gint marker;
66 };
67
68
69 static inline void gimp_config_writer_flush (GimpConfigWriter *writer);
70 static inline void gimp_config_writer_newline (GimpConfigWriter *writer);
71 static gboolean gimp_config_writer_close_output (GimpConfigWriter *writer,
72 GError **error);
73
74 static inline void
gimp_config_writer_flush(GimpConfigWriter * writer)75 gimp_config_writer_flush (GimpConfigWriter *writer)
76 {
77 GError *error = NULL;
78
79 if (! writer->output)
80 return;
81
82 if (! g_output_stream_write_all (writer->output,
83 writer->buffer->str,
84 writer->buffer->len,
85 NULL, NULL, &error))
86 {
87 g_set_error (&writer->error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_WRITE,
88 _("Error writing to '%s': %s"),
89 writer->file ?
90 gimp_file_get_utf8_name (writer->file) : "output stream",
91 error->message);
92 g_clear_error (&error);
93 }
94
95 g_string_truncate (writer->buffer, 0);
96 }
97
98 static inline void
gimp_config_writer_newline(GimpConfigWriter * writer)99 gimp_config_writer_newline (GimpConfigWriter *writer)
100 {
101 gint i;
102
103 g_string_append_c (writer->buffer, '\n');
104
105 if (writer->comment)
106 g_string_append_len (writer->buffer, "# ", 2);
107
108 for (i = 0; i < writer->depth; i++)
109 g_string_append_len (writer->buffer, " ", 4);
110 }
111
112 /**
113 * gimp_config_writer_new_file:
114 * @filename: a filename
115 * @atomic: if %TRUE the file is written atomically
116 * @header: text to include as comment at the top of the file
117 * @error: return location for errors
118 *
119 * Creates a new #GimpConfigWriter and sets it up to write to
120 * @filename. If @atomic is %TRUE, a temporary file is used to avoid
121 * possible race conditions. The temporary file is then moved to
122 * @filename when the writer is closed.
123 *
124 * Return value: a new #GimpConfigWriter or %NULL in case of an error
125 *
126 * Since: 2.4
127 **/
128 GimpConfigWriter *
gimp_config_writer_new_file(const gchar * filename,gboolean atomic,const gchar * header,GError ** error)129 gimp_config_writer_new_file (const gchar *filename,
130 gboolean atomic,
131 const gchar *header,
132 GError **error)
133 {
134 GimpConfigWriter *writer;
135 GFile *file;
136
137 g_return_val_if_fail (filename != NULL, NULL);
138 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
139
140 file = g_file_new_for_path (filename);
141
142 writer = gimp_config_writer_new_gfile (file, atomic, header, error);
143
144 g_object_unref (file);
145
146 return writer;
147 }
148
149 /**
150 * gimp_config_writer_new_gfile:
151 * @file: a #GFile
152 * @atomic: if %TRUE the file is written atomically
153 * @header: text to include as comment at the top of the file
154 * @error: return location for errors
155 *
156 * Creates a new #GimpConfigWriter and sets it up to write to
157 * @file. If @atomic is %TRUE, a temporary file is used to avoid
158 * possible race conditions. The temporary file is then moved to @file
159 * when the writer is closed.
160 *
161 * Return value: a new #GimpConfigWriter or %NULL in case of an error
162 *
163 * Since: 2.10
164 **/
165 GimpConfigWriter *
gimp_config_writer_new_gfile(GFile * file,gboolean atomic,const gchar * header,GError ** error)166 gimp_config_writer_new_gfile (GFile *file,
167 gboolean atomic,
168 const gchar *header,
169 GError **error)
170 {
171 GimpConfigWriter *writer;
172 GOutputStream *output;
173 GFile *dir;
174
175 g_return_val_if_fail (G_IS_FILE (file), NULL);
176 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
177
178 dir = g_file_get_parent (file);
179 if (dir && ! g_file_query_exists (dir, NULL))
180 {
181 if (! g_file_make_directory_with_parents (dir, NULL, error))
182 g_prefix_error (error,
183 _("Could not create directory '%s' for '%s': "),
184 gimp_file_get_utf8_name (dir),
185 gimp_file_get_utf8_name (file));
186 }
187 g_object_unref (dir);
188
189 if (error && *error)
190 return NULL;
191
192 if (atomic)
193 {
194 output = G_OUTPUT_STREAM (g_file_replace (file,
195 NULL, FALSE, G_FILE_CREATE_NONE,
196 NULL, error));
197 if (! output)
198 g_prefix_error (error,
199 _("Could not create temporary file for '%s': "),
200 gimp_file_get_utf8_name (file));
201 }
202 else
203 {
204 output = G_OUTPUT_STREAM (g_file_replace (file,
205 NULL, FALSE,
206 G_FILE_CREATE_REPLACE_DESTINATION,
207 NULL, error));
208 }
209
210 if (! output)
211 return NULL;
212
213 writer = g_slice_new0 (GimpConfigWriter);
214
215 writer->output = output;
216 writer->file = g_object_ref (file);
217 writer->buffer = g_string_new (NULL);
218
219 if (header)
220 {
221 gimp_config_writer_comment (writer, header);
222 gimp_config_writer_linefeed (writer);
223 }
224
225 return writer;
226 }
227
228 /**
229 * gimp_config_writer_new_stream:
230 * @output: a #GOutputStream
231 * @header: text to include as comment at the top of the file
232 * @error: return location for errors
233 *
234 * Creates a new #GimpConfigWriter and sets it up to write to
235 * @output.
236 *
237 * Return value: a new #GimpConfigWriter or %NULL in case of an error
238 *
239 * Since: 2.10
240 **/
241 GimpConfigWriter *
gimp_config_writer_new_stream(GOutputStream * output,const gchar * header,GError ** error)242 gimp_config_writer_new_stream (GOutputStream *output,
243 const gchar *header,
244 GError **error)
245 {
246 GimpConfigWriter *writer;
247
248 g_return_val_if_fail (G_IS_OUTPUT_STREAM (output), NULL);
249 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
250
251 writer = g_slice_new0 (GimpConfigWriter);
252
253 writer->output = g_object_ref (output);
254 writer->buffer = g_string_new (NULL);
255
256 if (header)
257 {
258 gimp_config_writer_comment (writer, header);
259 gimp_config_writer_linefeed (writer);
260 }
261
262 return writer;
263 }
264
265 /**
266 * gimp_config_writer_new_fd:
267 * @fd:
268 *
269 * Return value: a new #GimpConfigWriter or %NULL in case of an error
270 *
271 * Since: 2.4
272 **/
273 GimpConfigWriter *
gimp_config_writer_new_fd(gint fd)274 gimp_config_writer_new_fd (gint fd)
275 {
276 GimpConfigWriter *writer;
277
278 g_return_val_if_fail (fd > 0, NULL);
279
280 writer = g_slice_new0 (GimpConfigWriter);
281
282 #ifdef G_OS_WIN32
283 writer->output = g_win32_output_stream_new ((gpointer) fd, FALSE);
284 #else
285 writer->output = g_unix_output_stream_new (fd, FALSE);
286 #endif
287
288 writer->buffer = g_string_new (NULL);
289
290 return writer;
291 }
292
293 /**
294 * gimp_config_writer_new_string:
295 * @string:
296 *
297 * Return value: a new #GimpConfigWriter or %NULL in case of an error
298 *
299 * Since: 2.4
300 **/
301 GimpConfigWriter *
gimp_config_writer_new_string(GString * string)302 gimp_config_writer_new_string (GString *string)
303 {
304 GimpConfigWriter *writer;
305
306 g_return_val_if_fail (string != NULL, NULL);
307
308 writer = g_slice_new0 (GimpConfigWriter);
309
310 writer->buffer = string;
311
312 return writer;
313 }
314
315 /**
316 * gimp_config_writer_comment_mode:
317 * @writer: a #GimpConfigWriter
318 * @enable: %TRUE to enable comment mode, %FALSE to disable it
319 *
320 * This function toggles whether the @writer should create commented
321 * or uncommented output. This feature is used to generate the
322 * system-wide installed gimprc that documents the default settings.
323 *
324 * Since comments have to start at the beginning of a line, this
325 * function will insert a newline if necessary.
326 *
327 * Since: 2.4
328 **/
329 void
gimp_config_writer_comment_mode(GimpConfigWriter * writer,gboolean enable)330 gimp_config_writer_comment_mode (GimpConfigWriter *writer,
331 gboolean enable)
332 {
333 g_return_if_fail (writer != NULL);
334
335 if (writer->error)
336 return;
337
338 enable = (enable ? TRUE : FALSE);
339
340 if (writer->comment == enable)
341 return;
342
343 writer->comment = enable;
344
345 if (enable)
346 {
347 if (writer->buffer->len == 0)
348 g_string_append_len (writer->buffer, "# ", 2);
349 else
350 gimp_config_writer_newline (writer);
351 }
352 }
353
354
355 /**
356 * gimp_config_writer_open:
357 * @writer: a #GimpConfigWriter
358 * @name: name of the element to open
359 *
360 * This function writes the opening parenthesis followed by @name.
361 * It also increases the indentation level and sets a mark that
362 * can be used by gimp_config_writer_revert().
363 *
364 * Since: 2.4
365 **/
366 void
gimp_config_writer_open(GimpConfigWriter * writer,const gchar * name)367 gimp_config_writer_open (GimpConfigWriter *writer,
368 const gchar *name)
369 {
370 g_return_if_fail (writer != NULL);
371 g_return_if_fail (name != NULL);
372
373 if (writer->error)
374 return;
375
376 /* store the current buffer length so we can revert to this state */
377 writer->marker = writer->buffer->len;
378
379 if (writer->depth > 0)
380 gimp_config_writer_newline (writer);
381
382 writer->depth++;
383
384 g_string_append_printf (writer->buffer, "(%s", name);
385 }
386
387 /**
388 * gimp_config_writer_print:
389 * @writer: a #GimpConfigWriter
390 * @string: a string to write
391 * @len: number of bytes from @string or -1 if @string is NUL-terminated.
392 *
393 * Appends a space followed by @string to the @writer. Note that string
394 * must not contain any special characters that might need to be escaped.
395 *
396 * Since: 2.4
397 **/
398 void
gimp_config_writer_print(GimpConfigWriter * writer,const gchar * string,gint len)399 gimp_config_writer_print (GimpConfigWriter *writer,
400 const gchar *string,
401 gint len)
402 {
403 g_return_if_fail (writer != NULL);
404 g_return_if_fail (len == 0 || string != NULL);
405
406 if (writer->error)
407 return;
408
409 if (len < 0)
410 len = strlen (string);
411
412 if (len)
413 {
414 g_string_append_c (writer->buffer, ' ');
415 g_string_append_len (writer->buffer, string, len);
416 }
417 }
418
419 /**
420 * gimp_config_writer_printf:
421 * @writer: a #GimpConfigWriter
422 * @format: a format string as described for g_strdup_printf().
423 * @...: list of arguments according to @format
424 *
425 * A printf-like function for #GimpConfigWriter.
426 *
427 * Since: 2.4
428 **/
429 void
gimp_config_writer_printf(GimpConfigWriter * writer,const gchar * format,...)430 gimp_config_writer_printf (GimpConfigWriter *writer,
431 const gchar *format,
432 ...)
433 {
434 gchar *buffer;
435 va_list args;
436
437 g_return_if_fail (writer != NULL);
438 g_return_if_fail (format != NULL);
439
440 if (writer->error)
441 return;
442
443 va_start (args, format);
444 buffer = g_strdup_vprintf (format, args);
445 va_end (args);
446
447 g_string_append_c (writer->buffer, ' ');
448 g_string_append (writer->buffer, buffer);
449
450 g_free (buffer);
451 }
452
453 /**
454 * gimp_config_writer_string:
455 * @writer: a #GimpConfigWriter
456 * @string: a NUL-terminated string
457 *
458 * Writes a string value to @writer. The @string is quoted and special
459 * characters are escaped.
460 *
461 * Since: 2.4
462 **/
463 void
gimp_config_writer_string(GimpConfigWriter * writer,const gchar * string)464 gimp_config_writer_string (GimpConfigWriter *writer,
465 const gchar *string)
466 {
467 g_return_if_fail (writer != NULL);
468
469 if (writer->error)
470 return;
471
472 g_string_append_c (writer->buffer, ' ');
473 gimp_config_string_append_escaped (writer->buffer, string);
474 }
475
476 /**
477 * gimp_config_writer_identifier:
478 * @writer: a #GimpConfigWriter
479 * @identifier: a NUL-terminated string
480 *
481 * Writes an identifier to @writer. The @string is *not* quoted and special
482 * characters are *not* escaped.
483 *
484 * Since: 2.4
485 **/
486 void
gimp_config_writer_identifier(GimpConfigWriter * writer,const gchar * identifier)487 gimp_config_writer_identifier (GimpConfigWriter *writer,
488 const gchar *identifier)
489 {
490 g_return_if_fail (writer != NULL);
491 g_return_if_fail (identifier != NULL);
492
493 if (writer->error)
494 return;
495
496 g_string_append_printf (writer->buffer, " %s", identifier);
497 }
498
499
500 /**
501 * gimp_config_writer_data:
502 * @writer: a #GimpConfigWriter
503 * @length:
504 * @data:
505 *
506 * Since: 2.4
507 **/
508 void
gimp_config_writer_data(GimpConfigWriter * writer,gint length,const guint8 * data)509 gimp_config_writer_data (GimpConfigWriter *writer,
510 gint length,
511 const guint8 *data)
512 {
513 gint i;
514
515 g_return_if_fail (writer != NULL);
516 g_return_if_fail (length >= 0);
517 g_return_if_fail (data != NULL || length == 0);
518
519 if (writer->error)
520 return;
521
522 g_string_append (writer->buffer, " \"");
523
524 for (i = 0; i < length; i++)
525 {
526 if (g_ascii_isalpha (data[i]))
527 g_string_append_c (writer->buffer, data[i]);
528 else
529 g_string_append_printf (writer->buffer, "\\%o", data[i]);
530 }
531
532 g_string_append (writer->buffer, "\"");
533 }
534
535 /**
536 * gimp_config_writer_revert:
537 * @writer: a #GimpConfigWriter
538 *
539 * Reverts all changes to @writer that were done since the last call
540 * to gimp_config_writer_open(). This can only work if you didn't call
541 * gimp_config_writer_close() yet.
542 *
543 * Since: 2.4
544 **/
545 void
gimp_config_writer_revert(GimpConfigWriter * writer)546 gimp_config_writer_revert (GimpConfigWriter *writer)
547 {
548 g_return_if_fail (writer != NULL);
549
550 if (writer->error)
551 return;
552
553 g_return_if_fail (writer->depth > 0);
554 g_return_if_fail (writer->marker != -1);
555
556 g_string_truncate (writer->buffer, writer->marker);
557
558 writer->depth--;
559 writer->marker = -1;
560 }
561
562 /**
563 * gimp_config_writer_close:
564 * @writer: a #GimpConfigWriter
565 *
566 * Closes an element opened with gimp_config_writer_open().
567 *
568 * Since: 2.4
569 **/
570 void
gimp_config_writer_close(GimpConfigWriter * writer)571 gimp_config_writer_close (GimpConfigWriter *writer)
572 {
573 g_return_if_fail (writer != NULL);
574
575 if (writer->error)
576 return;
577
578 g_return_if_fail (writer->depth > 0);
579
580 g_string_append_c (writer->buffer, ')');
581
582 if (--writer->depth == 0)
583 {
584 g_string_append_c (writer->buffer, '\n');
585
586 gimp_config_writer_flush (writer);
587 }
588 }
589
590 /**
591 * gimp_config_writer_finish:
592 * @writer: a #GimpConfigWriter
593 * @footer: text to include as comment at the bottom of the file
594 * @error: return location for possible errors
595 *
596 * This function finishes the work of @writer and frees it afterwards.
597 * It closes all open elements, appends an optional comment and
598 * releases all resources allocated by @writer. You must not access
599 * the @writer afterwards.
600 *
601 * Return value: %TRUE if everything could be successfully written,
602 * %FALSE otherwise
603 *
604 * Since: 2.4
605 **/
606 gboolean
gimp_config_writer_finish(GimpConfigWriter * writer,const gchar * footer,GError ** error)607 gimp_config_writer_finish (GimpConfigWriter *writer,
608 const gchar *footer,
609 GError **error)
610 {
611 gboolean success = TRUE;
612
613 g_return_val_if_fail (writer != NULL, FALSE);
614 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
615
616 if (writer->depth < 0)
617 {
618 g_warning ("gimp_config_writer_finish: depth < 0 !!");
619 }
620 else
621 {
622 while (writer->depth)
623 gimp_config_writer_close (writer);
624 }
625
626 if (footer)
627 {
628 gimp_config_writer_linefeed (writer);
629 gimp_config_writer_comment (writer, footer);
630 }
631
632 if (writer->output)
633 {
634 success = gimp_config_writer_close_output (writer, error);
635
636 if (writer->file)
637 g_object_unref (writer->file);
638
639 g_string_free (writer->buffer, TRUE);
640 }
641
642 if (writer->error)
643 {
644 if (error && *error == NULL)
645 g_propagate_error (error, writer->error);
646 else
647 g_clear_error (&writer->error);
648
649 success = FALSE;
650 }
651
652 g_slice_free (GimpConfigWriter, writer);
653
654 return success;
655 }
656
657 void
gimp_config_writer_linefeed(GimpConfigWriter * writer)658 gimp_config_writer_linefeed (GimpConfigWriter *writer)
659 {
660 g_return_if_fail (writer != NULL);
661
662 if (writer->error)
663 return;
664
665 if (writer->output && writer->buffer->len == 0 && !writer->comment)
666 {
667 GError *error = NULL;
668
669 if (! g_output_stream_write_all (writer->output, "\n", 1,
670 NULL, NULL, &error))
671 {
672 g_set_error (&writer->error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_WRITE,
673 _("Error writing to '%s': %s"),
674 writer->file ?
675 gimp_file_get_utf8_name (writer->file) : "output stream",
676 error->message);
677 g_clear_error (&error);
678 }
679 }
680 else
681 {
682 gimp_config_writer_newline (writer);
683 }
684 }
685
686 /**
687 * gimp_config_writer_comment:
688 * @writer: a #GimpConfigWriter
689 * @comment: the comment to write (ASCII only)
690 *
691 * Appends the @comment to @str and inserts linebreaks and hash-marks to
692 * format it as a comment. Note that this function does not handle non-ASCII
693 * characters.
694 *
695 * Since: 2.4
696 **/
697 void
gimp_config_writer_comment(GimpConfigWriter * writer,const gchar * comment)698 gimp_config_writer_comment (GimpConfigWriter *writer,
699 const gchar *comment)
700 {
701 const gchar *s;
702 gboolean comment_mode;
703 gint i, len, space;
704
705 #define LINE_LENGTH 75
706
707 g_return_if_fail (writer != NULL);
708
709 if (writer->error)
710 return;
711
712 g_return_if_fail (writer->depth == 0);
713
714 if (!comment)
715 return;
716
717 comment_mode = writer->comment;
718 gimp_config_writer_comment_mode (writer, TRUE);
719
720 len = strlen (comment);
721
722 while (len > 0)
723 {
724 for (s = comment, i = 0, space = 0;
725 *s != '\n' && (i <= LINE_LENGTH || space == 0) && i < len;
726 s++, i++)
727 {
728 if (g_ascii_isspace (*s))
729 space = i;
730 }
731
732 if (i > LINE_LENGTH && space && *s != '\n')
733 i = space;
734
735 g_string_append_len (writer->buffer, comment, i);
736
737 i++;
738
739 comment += i;
740 len -= i;
741
742 if (len > 0)
743 gimp_config_writer_newline (writer);
744 }
745
746 gimp_config_writer_comment_mode (writer, comment_mode);
747 gimp_config_writer_newline (writer);
748
749 if (writer->depth == 0)
750 gimp_config_writer_flush (writer);
751
752 #undef LINE_LENGTH
753 }
754
755 static gboolean
gimp_config_writer_close_output(GimpConfigWriter * writer,GError ** error)756 gimp_config_writer_close_output (GimpConfigWriter *writer,
757 GError **error)
758 {
759 g_return_val_if_fail (writer->output != NULL, FALSE);
760
761 if (writer->error)
762 {
763 GCancellable *cancellable = g_cancellable_new ();
764
765 /* Cancel the overwrite initiated by g_file_replace(). */
766 g_cancellable_cancel (cancellable);
767 g_output_stream_close (writer->output, cancellable, NULL);
768 g_object_unref (cancellable);
769
770 g_object_unref (writer->output);
771 writer->output = NULL;
772
773 return FALSE;
774 }
775
776 if (writer->file)
777 {
778 GError *my_error = NULL;
779
780 if (! g_output_stream_close (writer->output, NULL, &my_error))
781 {
782 g_set_error (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_WRITE,
783 _("Error writing '%s': %s"),
784 gimp_file_get_utf8_name (writer->file),
785 my_error->message);
786 g_clear_error (&my_error);
787
788 g_object_unref (writer->output);
789 writer->output = NULL;
790
791 return FALSE;
792 }
793 }
794
795 g_object_unref (writer->output);
796 writer->output = NULL;
797
798 return TRUE;
799 }
800