]> git.notmuchmail.org Git - notmuch/blob - notmuch-dump.c
lib: add support for named queries
[notmuch] / notmuch-dump.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see http://www.gnu.org/licenses/ .
17  *
18  * Author: Carl Worth <cworth@cworth.org>
19  */
20
21 #include "notmuch-client.h"
22 #include "hex-escape.h"
23 #include "string-util.h"
24 #include <zlib.h>
25
26 static int
27 database_dump_config (notmuch_database_t *notmuch, gzFile output)
28 {
29     notmuch_config_list_t *list;
30     int ret = EXIT_FAILURE;
31     char *buffer = NULL;
32     size_t buffer_size = 0;
33
34     if (print_status_database ("notmuch dump", notmuch,
35                                notmuch_database_get_config_list (notmuch, NULL, &list)))
36         goto DONE;
37
38     for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
39         if (hex_encode (notmuch, notmuch_config_list_key (list),
40                         &buffer, &buffer_size) != HEX_SUCCESS) {
41             fprintf (stderr, "Error: failed to hex-encode config key %s\n",
42                      notmuch_config_list_key (list));
43             goto DONE;
44         }
45         gzprintf (output, "#@ %s", buffer);
46
47         if (hex_encode (notmuch, notmuch_config_list_value (list),
48                         &buffer, &buffer_size) != HEX_SUCCESS) {
49             fprintf (stderr, "Error: failed to hex-encode config value %s\n",
50                      notmuch_config_list_value (list) );
51             goto DONE;
52         }
53
54         gzprintf (output, " %s\n", buffer);
55     }
56
57     ret = EXIT_SUCCESS;
58
59  DONE:
60     if (list)
61         notmuch_config_list_destroy (list);
62
63     if (buffer)
64         talloc_free (buffer);
65
66     return ret;
67 }
68
69 static void
70 print_dump_header (gzFile output, int output_format, int include)
71 {
72     gzprintf (output, "#notmuch-dump %s:%d %s%s%s\n",
73               (output_format == DUMP_FORMAT_SUP) ? "sup" : "batch-tag",
74               NOTMUCH_DUMP_VERSION,
75               (include & DUMP_INCLUDE_CONFIG) ? "config" : "",
76               (include & DUMP_INCLUDE_TAGS) && (include & DUMP_INCLUDE_CONFIG) ? "," : "",
77               (include & DUMP_INCLUDE_TAGS) ? "tags" : "");
78 }
79
80 static int
81 database_dump_file (notmuch_database_t *notmuch, gzFile output,
82                     const char *query_str, int output_format, int include)
83 {
84     notmuch_query_t *query;
85     notmuch_messages_t *messages;
86     notmuch_message_t *message;
87     notmuch_tags_t *tags;
88
89     print_dump_header (output, output_format, include);
90
91     if (include & DUMP_INCLUDE_CONFIG) {
92         if (print_status_database ("notmuch dump", notmuch,
93                                    database_dump_config(notmuch,output)))
94             return EXIT_FAILURE;
95     }
96
97     if (! (include & DUMP_INCLUDE_TAGS))
98         return EXIT_SUCCESS;
99
100     if (! query_str)
101         query_str = "";
102
103     query = notmuch_query_create (notmuch, query_str);
104     if (query == NULL) {
105         fprintf (stderr, "Out of memory\n");
106         return EXIT_FAILURE;
107     }
108     /* Don't ask xapian to sort by Message-ID. Xapian optimizes returning the
109      * first results quickly at the expense of total time.
110      */
111     notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
112
113     char *buffer = NULL;
114     size_t buffer_size = 0;
115     notmuch_status_t status;
116
117     status = notmuch_query_search_messages_st (query, &messages);
118     if (print_status_query ("notmuch dump", query, status))
119         return EXIT_FAILURE;
120
121     for (;
122          notmuch_messages_valid (messages);
123          notmuch_messages_move_to_next (messages)) {
124         int first = 1;
125         const char *message_id;
126
127         message = notmuch_messages_get (messages);
128         message_id = notmuch_message_get_message_id (message);
129
130         if (output_format == DUMP_FORMAT_BATCH_TAG &&
131             strchr (message_id, '\n')) {
132             /* This will produce a line break in the output, which
133              * would be difficult to handle in tools.  However, it's
134              * also impossible to produce an email containing a line
135              * break in a message ID because of unfolding, so we can
136              * safely disallow it. */
137             fprintf (stderr, "Warning: skipping message id containing line break: \"%s\"\n", message_id);
138             notmuch_message_destroy (message);
139             continue;
140         }
141
142         if (output_format == DUMP_FORMAT_SUP) {
143             gzprintf (output, "%s (", message_id);
144         }
145
146         for (tags = notmuch_message_get_tags (message);
147              notmuch_tags_valid (tags);
148              notmuch_tags_move_to_next (tags)) {
149             const char *tag_str = notmuch_tags_get (tags);
150
151             if (! first)
152                 gzputs (output, " ");
153
154             first = 0;
155
156             if (output_format == DUMP_FORMAT_SUP) {
157                 gzputs (output, tag_str);
158             } else {
159                 if (hex_encode (notmuch, tag_str,
160                                 &buffer, &buffer_size) != HEX_SUCCESS) {
161                     fprintf (stderr, "Error: failed to hex-encode tag %s\n",
162                              tag_str);
163                     return EXIT_FAILURE;
164                 }
165                 gzprintf (output, "+%s", buffer);
166             }
167         }
168
169         if (output_format == DUMP_FORMAT_SUP) {
170             gzputs (output, ")\n");
171         } else {
172             if (make_boolean_term (notmuch, "id", message_id,
173                                    &buffer, &buffer_size)) {
174                     fprintf (stderr, "Error quoting message id %s: %s\n",
175                              message_id, strerror (errno));
176                     return EXIT_FAILURE;
177             }
178             gzprintf (output, " -- %s\n", buffer);
179         }
180
181         notmuch_message_destroy (message);
182     }
183
184     notmuch_query_destroy (query);
185
186     return EXIT_SUCCESS;
187 }
188
189 /* Dump database into output_file_name if it's non-NULL, stdout
190  * otherwise.
191  */
192 int
193 notmuch_database_dump (notmuch_database_t *notmuch,
194                        const char *output_file_name,
195                        const char *query_str,
196                        dump_format_t output_format,
197                        dump_include_t include,
198                        notmuch_bool_t gzip_output)
199 {
200     gzFile output = NULL;
201     const char *mode = gzip_output ? "w9" : "wT";
202     const char *name_for_error = output_file_name ? output_file_name : "stdout";
203
204     char *tempname = NULL;
205     int outfd = -1;
206
207     int ret = -1;
208
209     if (output_file_name) {
210         tempname = talloc_asprintf (notmuch, "%s.XXXXXX", output_file_name);
211         outfd = mkstemp (tempname);
212     } else {
213         outfd = dup (STDOUT_FILENO);
214     }
215
216     if (outfd < 0) {
217         fprintf (stderr, "Bad output file %s\n", name_for_error);
218         goto DONE;
219     }
220
221     output = gzdopen (outfd, mode);
222
223     if (output == NULL) {
224         fprintf (stderr, "Error opening %s for (gzip) writing: %s\n",
225                  name_for_error, strerror (errno));
226         if (close (outfd))
227             fprintf (stderr, "Error closing %s during shutdown: %s\n",
228                  name_for_error, strerror (errno));
229         goto DONE;
230     }
231
232     ret = database_dump_file (notmuch, output, query_str, output_format, include);
233     if (ret) goto DONE;
234
235     ret = gzflush (output, Z_FINISH);
236     if (ret) {
237         fprintf (stderr, "Error flushing output: %s\n", gzerror (output, NULL));
238         goto DONE;
239     }
240
241     if (output_file_name) {
242         ret = fsync (outfd);
243         if (ret) {
244             fprintf (stderr, "Error syncing %s to disk: %s\n",
245                      name_for_error, strerror (errno));
246             goto DONE;
247         }
248     }
249
250     if (gzclose_w (output) != Z_OK) {
251         fprintf (stderr, "Error closing %s: %s\n", name_for_error,
252                  gzerror (output, NULL));
253         ret = EXIT_FAILURE;
254         output = NULL;
255         goto DONE;
256     }
257
258     if (output_file_name) {
259         ret = rename (tempname, output_file_name);
260         if (ret) {
261             fprintf (stderr, "Error renaming %s to %s: %s\n",
262                      tempname, output_file_name, strerror (errno));
263             goto DONE;
264         }
265
266     }
267  DONE:
268     if (ret != EXIT_SUCCESS && output)
269         (void) gzclose_w (output);
270
271     if (ret != EXIT_SUCCESS && output_file_name)
272         (void) unlink (tempname);
273
274     return ret;
275 }
276
277 int
278 notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
279 {
280     notmuch_database_t *notmuch;
281     const char *query_str = NULL;
282     int ret;
283
284     if (notmuch_database_open (notmuch_config_get_database_path (config),
285                                NOTMUCH_DATABASE_MODE_READ_WRITE, &notmuch))
286         return EXIT_FAILURE;
287
288     notmuch_exit_if_unmatched_db_uuid (notmuch);
289
290     char *output_file_name = NULL;
291     int opt_index;
292
293     int output_format = DUMP_FORMAT_BATCH_TAG;
294     int include = 0;
295     notmuch_bool_t gzip_output = 0;
296
297     notmuch_opt_desc_t options[] = {
298         { NOTMUCH_OPT_KEYWORD, &output_format, "format", 'f',
299           (notmuch_keyword_t []){ { "sup", DUMP_FORMAT_SUP },
300                                   { "batch-tag", DUMP_FORMAT_BATCH_TAG },
301                                   { 0, 0 } } },
302         { NOTMUCH_OPT_KEYWORD_FLAGS, &include, "include", 'I',
303           (notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
304                                   { "tags", DUMP_INCLUDE_TAGS} } },
305         { NOTMUCH_OPT_STRING, &output_file_name, "output", 'o', 0  },
306         { NOTMUCH_OPT_BOOLEAN, &gzip_output, "gzip", 'z', 0 },
307         { NOTMUCH_OPT_INHERIT, (void *) &notmuch_shared_options, NULL, 0, 0 },
308         { 0, 0, 0, 0, 0 }
309     };
310
311     opt_index = parse_arguments (argc, argv, options, 1);
312     if (opt_index < 0)
313         return EXIT_FAILURE;
314
315     notmuch_process_shared_options (argv[0]);
316
317     if (include == 0)
318         include = DUMP_INCLUDE_CONFIG | DUMP_INCLUDE_TAGS;
319
320     if (opt_index < argc) {
321         query_str = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
322         if (query_str == NULL) {
323             fprintf (stderr, "Out of memory.\n");
324             return EXIT_FAILURE;
325         }
326     }
327
328     ret = notmuch_database_dump (notmuch, output_file_name, query_str,
329                                  output_format, include, gzip_output);
330
331     notmuch_database_destroy (notmuch);
332
333     return ret;
334 }