f576e33673bec51e2c5912c6a78447634416ba71
[notmuch] / lib / directory.cc
1 /* directory.cc - Results of directory-based searches from a notmuch database
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-private.h"
22 #include "database-private.h"
23
24 #include <xapian.h>
25
26 struct _notmuch_filenames {
27     Xapian::TermIterator iterator;
28     Xapian::TermIterator end;
29     int prefix_len;
30     char *filename;
31 };
32
33 /* We end up having to call the destructors explicitly because we had
34  * to use "placement new" in order to initialize C++ objects within a
35  * block that we allocated with talloc. So C++ is making talloc
36  * slightly less simple to use, (we wouldn't need
37  * talloc_set_destructor at all otherwise).
38  */
39 static int
40 _notmuch_filenames_destructor (notmuch_filenames_t *filenames)
41 {
42     filenames->iterator.~TermIterator ();
43     filenames->end.~TermIterator ();
44
45     return 0;
46 }
47
48 /* Create an iterator to iterate over the basenames of files (or
49  * directories) that all share a common parent directory.
50  *
51  * The code here is general enough to be reused for any case of
52  * iterating over the non-prefixed portion of terms sharing a common
53  * prefix.
54  */
55 static notmuch_filenames_t *
56 _notmuch_filenames_create (void *ctx,
57                            notmuch_database_t *notmuch,
58                            const char *prefix)
59 {
60     notmuch_filenames_t *filenames;
61
62     filenames = talloc (ctx, notmuch_filenames_t);
63     if (unlikely (filenames == NULL))
64         return NULL;
65
66     new (&filenames->iterator) Xapian::TermIterator ();
67     new (&filenames->end) Xapian::TermIterator ();
68
69     talloc_set_destructor (filenames, _notmuch_filenames_destructor);
70
71     filenames->iterator = notmuch->xapian_db->allterms_begin (prefix);
72     filenames->end = notmuch->xapian_db->allterms_end (prefix);
73
74     filenames->prefix_len = strlen (prefix);
75
76     filenames->filename = NULL;
77
78     return filenames;
79 }
80
81 notmuch_bool_t
82 notmuch_filenames_valid (notmuch_filenames_t *filenames)
83 {
84     if (filenames == NULL)
85         return NULL;
86
87     return (filenames->iterator != filenames->end);
88 }
89
90 const char *
91 notmuch_filenames_get (notmuch_filenames_t *filenames)
92 {
93     if (filenames == NULL || filenames->iterator == filenames->end)
94         return NULL;
95
96     if (filenames->filename == NULL) {
97         std::string term = *filenames->iterator;
98
99         filenames->filename = talloc_strdup (filenames,
100                                              term.c_str () +
101                                              filenames->prefix_len);
102     }
103
104     return filenames->filename;
105 }
106
107 void
108 notmuch_filenames_move_to_next (notmuch_filenames_t *filenames)
109 {
110     if (filenames == NULL)
111         return;
112
113     if (filenames->filename) {
114         talloc_free (filenames->filename);
115         filenames->filename = NULL;
116     }
117
118     if (filenames->iterator != filenames->end)
119         filenames->iterator++;
120 }
121
122 void
123 notmuch_filenames_destroy (notmuch_filenames_t *filenames)
124 {
125     if (filenames == NULL)
126         return;
127
128     talloc_free (filenames);
129 }
130
131 struct _notmuch_directory {
132     notmuch_database_t *notmuch;
133     Xapian::docid document_id;
134     Xapian::Document doc;
135     time_t mtime;
136 };
137
138 /* We end up having to call the destructor explicitly because we had
139  * to use "placement new" in order to initialize C++ objects within a
140  * block that we allocated with talloc. So C++ is making talloc
141  * slightly less simple to use, (we wouldn't need
142  * talloc_set_destructor at all otherwise).
143  */
144 static int
145 _notmuch_directory_destructor (notmuch_directory_t *directory)
146 {
147     directory->doc.~Document ();
148
149     return 0;
150 }
151
152 static notmuch_private_status_t
153 find_directory_document (notmuch_database_t *notmuch,
154                          const char *db_path,
155                          Xapian::Document *document)
156 {
157     notmuch_private_status_t status;
158     Xapian::docid doc_id;
159
160     status = _notmuch_database_find_unique_doc_id (notmuch, "directory",
161                                                    db_path, &doc_id);
162     if (status) {
163         *document = Xapian::Document ();
164         return status;
165     }
166
167     *document = notmuch->xapian_db->get_document (doc_id);
168     return NOTMUCH_PRIVATE_STATUS_SUCCESS;
169 }
170
171 notmuch_directory_t *
172 _notmuch_directory_create (notmuch_database_t *notmuch,
173                            const char *path,
174                            notmuch_status_t *status_ret)
175 {
176     Xapian::WritableDatabase *db;
177     notmuch_directory_t *directory;
178     notmuch_private_status_t private_status;
179     const char *db_path;
180
181     *status_ret = NOTMUCH_STATUS_SUCCESS;
182
183     path = _notmuch_database_relative_path (notmuch, path);
184
185     if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
186         INTERNAL_ERROR ("Failure to ensure database is writable");
187
188     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
189
190     directory = talloc (notmuch, notmuch_directory_t);
191     if (unlikely (directory == NULL))
192         return NULL;
193
194     directory->notmuch = notmuch;
195
196     /* "placement new"---not actually allocating memory */
197     new (&directory->doc) Xapian::Document;
198
199     talloc_set_destructor (directory, _notmuch_directory_destructor);
200
201     db_path = _notmuch_database_get_directory_db_path (path);
202
203     try {
204         Xapian::TermIterator i, end;
205
206         private_status = find_directory_document (notmuch, db_path,
207                                                   &directory->doc);
208         directory->document_id = directory->doc.get_docid ();
209
210         if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
211             void *local = talloc_new (directory);
212             const char *parent, *basename;
213             Xapian::docid parent_id;
214             char *term = talloc_asprintf (local, "%s%s",
215                                           _find_prefix ("directory"), db_path);
216             directory->doc.add_term (term, 0);
217
218             directory->doc.set_data (path);
219
220             _notmuch_database_split_path (local, path, &parent, &basename);
221
222             _notmuch_database_find_directory_id (notmuch, parent, &parent_id);
223
224             if (basename) {
225                 term = talloc_asprintf (local, "%s%u:%s",
226                                         _find_prefix ("directory-direntry"),
227                                         parent_id, basename);
228                 directory->doc.add_term (term, 0);
229             }
230
231             directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
232                                       Xapian::sortable_serialise (0));
233
234             directory->document_id = _notmuch_database_generate_doc_id (notmuch);
235             db->replace_document (directory->document_id, directory->doc);
236             talloc_free (local);
237         }
238
239         directory->mtime = Xapian::sortable_unserialise (
240             directory->doc.get_value (NOTMUCH_VALUE_TIMESTAMP));
241     } catch (const Xapian::Error &error) {
242         fprintf (stderr,
243                  "A Xapian exception occurred creating a directory: %s.\n",
244                  error.get_msg().c_str());
245         notmuch->exception_reported = TRUE;
246         notmuch_directory_destroy (directory);
247         *status_ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
248         return NULL;
249     }
250
251     if (db_path != path)
252         free ((char *) db_path);
253
254     return directory;
255 }
256
257 unsigned int
258 _notmuch_directory_get_document_id (notmuch_directory_t *directory)
259 {
260     return directory->document_id;
261 }
262
263 notmuch_status_t
264 notmuch_directory_set_mtime (notmuch_directory_t *directory,
265                              time_t mtime)
266 {
267     notmuch_database_t *notmuch = directory->notmuch;
268     Xapian::WritableDatabase *db;
269     notmuch_status_t status;
270
271     status = _notmuch_database_ensure_writable (notmuch);
272     if (status)
273         return status;
274
275     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
276
277     try {
278         directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
279                                    Xapian::sortable_serialise (mtime));
280
281         db->replace_document (directory->document_id, directory->doc);
282     } catch (const Xapian::Error &error) {
283         fprintf (stderr,
284                  "A Xapian exception occurred setting directory mtime: %s.\n",
285                  error.get_msg().c_str());
286         notmuch->exception_reported = TRUE;
287         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
288     }
289
290     return NOTMUCH_STATUS_SUCCESS;
291 }
292
293 time_t
294 notmuch_directory_get_mtime (notmuch_directory_t *directory)
295 {
296     return directory->mtime;
297 }
298
299 notmuch_filenames_t *
300 notmuch_directory_get_child_files (notmuch_directory_t *directory)
301 {
302     char *term;
303     notmuch_filenames_t *child_files;
304
305     term = talloc_asprintf (directory, "%s%u:",
306                             _find_prefix ("file-direntry"),
307                             directory->document_id);
308
309     child_files = _notmuch_filenames_create (directory,
310                                              directory->notmuch, term);
311
312     talloc_free (term);
313
314     return child_files;
315 }
316
317 notmuch_filenames_t *
318 notmuch_directory_get_child_directories (notmuch_directory_t *directory)
319 {
320     char *term;
321     notmuch_filenames_t *child_directories;
322
323     term = talloc_asprintf (directory, "%s%u:",
324                             _find_prefix ("directory-direntry"),
325                             directory->document_id);
326
327     child_directories = _notmuch_filenames_create (directory,
328                                                    directory->notmuch, term);
329
330     talloc_free (term);
331
332     return child_directories;
333 }
334
335 void
336 notmuch_directory_destroy (notmuch_directory_t *directory)
337 {
338     talloc_free (directory);
339 }