search : Extend "intial burst" optimization to return all results by chunks
[notmuch] / notmuch-search.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
23 /* If the user asks for more than this number of threads, then break
24    the results down into chunks so that results start appearing
25    quickly rather than the user having to wait until all results are
26    available before anything appears.
27
28    Since each subsequent chunk ends up having to re-do threading work
29    done by all previous chunks, we double the chunk size repeatedly
30    until all desired results have been returned.
31 */
32 #define NOTMUCH_SEARCH_INITIAL_CHUNK_SIZE 100
33
34 /* Do the actual search for a chunk of threads and display the results,
35    (returning the number of threads found in this chunk). */
36 static int
37 do_search_threads (const void *ctx,
38                    notmuch_query_t *query,
39                    notmuch_sort_t sort,
40                    int first, int max_threads)
41 {
42     notmuch_thread_t *thread;
43     notmuch_threads_t *threads;
44     notmuch_tags_t *tags;
45     time_t date;
46     const char *relative_date;
47     int num_threads = 0;
48
49     for (threads = notmuch_query_search_threads (query, first, max_threads);
50          notmuch_threads_has_more (threads);
51          notmuch_threads_advance (threads))
52     {
53         int first_tag = 1;
54
55         thread = notmuch_threads_get (threads);
56         num_threads++;
57
58         if (sort == NOTMUCH_SORT_OLDEST_FIRST)
59             date = notmuch_thread_get_oldest_date (thread);
60         else
61             date = notmuch_thread_get_newest_date (thread);
62
63         relative_date = notmuch_time_relative_date (ctx, date);
64
65         printf ("thread:%s %12s [%d/%d] %s; %s",
66                 notmuch_thread_get_thread_id (thread),
67                 relative_date,
68                 notmuch_thread_get_matched_messages (thread),
69                 notmuch_thread_get_total_messages (thread),
70                 notmuch_thread_get_authors (thread),
71                 notmuch_thread_get_subject (thread));
72
73         printf (" (");
74         for (tags = notmuch_thread_get_tags (thread);
75              notmuch_tags_has_more (tags);
76              notmuch_tags_advance (tags))
77         {
78             if (! first_tag)
79                 printf (" ");
80             printf ("%s", notmuch_tags_get (tags));
81             first_tag = 0;
82         }
83         printf (")\n");
84
85         notmuch_thread_destroy (thread);
86     }
87
88     return num_threads;
89 }
90
91 int
92 notmuch_search_command (void *ctx, int argc, char *argv[])
93 {
94     notmuch_config_t *config;
95     notmuch_database_t *notmuch;
96     notmuch_query_t *query;
97     char *query_str;
98     int i, first = 0, max_threads = -1;
99     char *opt, *end;
100     notmuch_sort_t sort = NOTMUCH_SORT_NEWEST_FIRST;
101     int chunk_size;
102     int threads_in_chunk;
103
104     for (i = 0; i < argc && argv[i][0] == '-'; i++) {
105         if (strcmp (argv[i], "--") == 0) {
106             i++;
107             break;
108         }
109         if (STRNCMP_LITERAL (argv[i], "--first=") == 0) {
110             opt = argv[i] + sizeof ("--first=") - 1;
111             first = strtoul (opt, &end, 10);
112             if (*opt == '\0' || *end != '\0') {
113                 fprintf (stderr, "Invalid value for --first: %s\n", opt);
114                 return 1;
115             }
116         } else if (STRNCMP_LITERAL (argv[i], "--max-threads=") == 0) {
117             opt = argv[i] + sizeof ("--max-threads=") - 1;
118             max_threads = strtoul (opt, &end, 10);
119             if (*opt == '\0' || *end != '\0') {
120                 fprintf (stderr, "Invalid value for --max-threads: %s\n", opt);
121                 return 1;
122             }
123         } else if (STRNCMP_LITERAL (argv[i], "--sort=") == 0) {
124             opt = argv[i] + sizeof ("--sort=") - 1;
125             if (strcmp (opt, "oldest-first") == 0) {
126                 sort = NOTMUCH_SORT_OLDEST_FIRST;
127             } else if (strcmp (opt, "newest-first") == 0) {
128                 sort = NOTMUCH_SORT_NEWEST_FIRST;
129             } else {
130                 fprintf (stderr, "Invalid value for --sort: %s\n", opt);
131                 return 1;
132             }
133         } else {
134             fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
135             return 1;
136         }
137     }
138
139     argc -= i;
140     argv += i;
141
142     config = notmuch_config_open (ctx, NULL, NULL);
143     if (config == NULL)
144         return 1;
145
146     notmuch = notmuch_database_open (notmuch_config_get_database_path (config),
147                                      NOTMUCH_DATABASE_MODE_READ_ONLY);
148     if (notmuch == NULL)
149         return 1;
150
151     query_str = query_string_from_args (ctx, argc, argv);
152     if (query_str == NULL) {
153         fprintf (stderr, "Out of memory.\n");
154         return 1;
155     }
156     if (*query_str == '\0') {
157         fprintf (stderr, "Error: notmuch search requires at least one search term.\n");
158         return 1;
159     }
160
161     query = notmuch_query_create (notmuch, query_str);
162     if (query == NULL) {
163         fprintf (stderr, "Out of memory\n");
164         return 1;
165     }
166
167     notmuch_query_set_sort (query, sort);
168
169     /* If we receive a max-threads option, then the user is
170        responsible for any chunking and we return all results at
171        once. */
172     if (max_threads < 0)
173         chunk_size = NOTMUCH_SEARCH_INITIAL_CHUNK_SIZE;
174     else
175         chunk_size = max_threads;
176
177     do {
178         threads_in_chunk = do_search_threads (ctx, query, sort,
179                                               first, chunk_size);
180         if (chunk_size == max_threads)
181             break;
182
183         first += chunk_size;
184
185         chunk_size *= 2;
186
187     } while (threads_in_chunk);
188
189     notmuch_query_destroy (query);
190     notmuch_database_close (notmuch);
191
192     return 0;
193 }