]> git.notmuchmail.org Git - notmuch/blob - lib/directory.cc
lib: Implement new notmuch_directory_t API.
[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 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_has_more (notmuch_filenames_t *filenames)
83 {
84     return (filenames->iterator != filenames->end);
85 }
86
87 const char *
88 notmuch_filenames_get (notmuch_filenames_t *filenames)
89 {
90     if (filenames->filename == NULL) {
91         std::string term = *filenames->iterator;
92
93         filenames->filename = talloc_strdup (filenames,
94                                              term.c_str () +
95                                              filenames->prefix_len);
96     }
97
98     return filenames->filename;
99 }
100
101 void
102 notmuch_filenames_advance (notmuch_filenames_t *filenames)
103 {
104     if (filenames->filename) {
105         talloc_free (filenames->filename);
106         filenames->filename = NULL;
107     }
108
109     if (filenames->iterator != filenames->end)
110         filenames->iterator++;
111 }
112
113 void
114 notmuch_filenames_destroy (notmuch_filenames_t *filenames)
115 {
116     talloc_free (filenames);
117 }
118
119 struct _notmuch_directory {
120     notmuch_database_t *notmuch;
121     Xapian::docid document_id;
122     Xapian::Document doc;
123     time_t mtime;
124 };
125
126 /* We end up having to call the destructor explicitly because we had
127  * to use "placement new" in order to initialize C++ objects within a
128  * block that we allocated with talloc. So C++ is making talloc
129  * slightly less simple to use, (we wouldn't need
130  * talloc_set_destructor at all otherwise).
131  */
132 static int
133 _notmuch_directory_destructor (notmuch_directory_t *directory)
134 {
135     directory->doc.~Document ();
136
137     return 0;
138 }
139
140 static notmuch_private_status_t
141 find_directory_document (notmuch_database_t *notmuch,
142                          const char *db_path,
143                          Xapian::Document *document)
144 {
145     notmuch_private_status_t status;
146     Xapian::docid doc_id;
147
148     status = _notmuch_database_find_unique_doc_id (notmuch, "directory",
149                                                    db_path, &doc_id);
150     if (status) {
151         *document = Xapian::Document ();
152         return status;
153     }
154
155     *document = notmuch->xapian_db->get_document (doc_id);
156     return NOTMUCH_PRIVATE_STATUS_SUCCESS;
157 }
158
159 notmuch_directory_t *
160 _notmuch_directory_create (notmuch_database_t *notmuch,
161                            const char *path,
162                            notmuch_status_t *status_ret)
163 {
164     Xapian::WritableDatabase *db;
165     notmuch_directory_t *directory;
166     notmuch_private_status_t private_status;
167     const char *db_path;
168
169     *status_ret = NOTMUCH_STATUS_SUCCESS;
170
171     path = _notmuch_database_relative_path (notmuch, path);
172
173     if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
174         fprintf (stderr, "Attempted to update a read-only database.\n");
175         *status_ret = NOTMUCH_STATUS_READONLY_DATABASE;
176         return NULL;
177     }
178
179     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
180
181     directory = talloc (notmuch, notmuch_directory_t);
182     if (unlikely (directory == NULL))
183         return NULL;
184
185     directory->notmuch = notmuch;
186
187     /* "placement new"---not actually allocating memory */
188     new (&directory->doc) Xapian::Document;
189
190     talloc_set_destructor (directory, _notmuch_directory_destructor);
191
192     db_path = _notmuch_database_get_directory_db_path (path);
193
194     try {
195         Xapian::TermIterator i, end;
196
197         private_status = find_directory_document (notmuch, db_path,
198                                                   &directory->doc);
199         directory->document_id = directory->doc.get_docid ();
200
201         if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
202             void *local = talloc_new (directory);
203             const char *parent, *basename;
204             Xapian::docid parent_id;
205             char *term = talloc_asprintf (local, "%s%s",
206                                           _find_prefix ("directory"), db_path);
207             directory->doc.add_term (term);
208
209             directory->doc.set_data (path);
210
211             _notmuch_database_split_path (local, path, &parent, &basename);
212
213             _notmuch_database_find_directory_id (notmuch, parent, &parent_id);
214
215             if (basename) {
216                 term = talloc_asprintf (local, "%s%u:%s",
217                                         _find_prefix ("directory-direntry"),
218                                         parent_id, basename);
219                 directory->doc.add_term (term);
220             }
221
222             directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
223                                       Xapian::sortable_serialise (0));
224
225             directory->document_id = db->add_document (directory->doc);
226             talloc_free (local);
227         }
228
229         directory->mtime = Xapian::sortable_unserialise (
230             directory->doc.get_value (NOTMUCH_VALUE_TIMESTAMP));
231     } catch (const Xapian::Error &error) {
232         fprintf (stderr,
233                  "A Xapian exception occurred creating a directory: %s.\n",
234                  error.get_msg().c_str());
235         notmuch->exception_reported = TRUE;
236         notmuch_directory_destroy (directory);
237         *status_ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
238         return NULL;
239     }
240
241     if (db_path != path)
242         free ((char *) db_path);
243
244     return directory;
245 }
246
247 unsigned int
248 _notmuch_directory_get_document_id (notmuch_directory_t *directory)
249 {
250     return directory->document_id;
251 }
252
253 notmuch_status_t
254 notmuch_directory_set_mtime (notmuch_directory_t *directory,
255                              time_t mtime)
256 {
257     notmuch_database_t *notmuch = directory->notmuch;
258     Xapian::WritableDatabase *db;
259
260     if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
261         fprintf (stderr, "Attempted to update a read-only database.\n");
262         return NOTMUCH_STATUS_READONLY_DATABASE;
263     }
264
265     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
266
267     try {
268         directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
269                                    Xapian::sortable_serialise (mtime));
270
271         db->replace_document (directory->document_id, directory->doc);
272     } catch (const Xapian::Error &error) {
273         fprintf (stderr,
274                  "A Xapian exception occurred setting directory mtime: %s.\n",
275                  error.get_msg().c_str());
276         notmuch->exception_reported = TRUE;
277         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
278     }
279
280     return NOTMUCH_STATUS_SUCCESS;
281 }
282
283 time_t
284 notmuch_directory_get_mtime (notmuch_directory_t *directory)
285 {
286     return directory->mtime;
287 }
288
289 notmuch_filenames_t *
290 notmuch_directory_get_child_files (notmuch_directory_t *directory)
291 {
292     char *term;
293     notmuch_filenames_t *child_files;
294
295     term = talloc_asprintf (directory, "%s%u:",
296                             _find_prefix ("file-direntry"),
297                             directory->document_id);
298
299     child_files = _notmuch_filenames_create (directory,
300                                              directory->notmuch, term);
301
302     talloc_free (term);
303
304     return child_files;
305 }
306
307 notmuch_filenames_t *
308 notmuch_directory_get_child_directories (notmuch_directory_t *directory)
309 {
310     char *term;
311     notmuch_filenames_t *child_directories;
312
313     term = talloc_asprintf (directory, "%s%u:",
314                             _find_prefix ("directory-direntry"),
315                             directory->document_id);
316
317     child_directories = _notmuch_filenames_create (directory,
318                                                    directory->notmuch, term);
319
320     talloc_free (term);
321
322     return child_directories;
323 }
324
325 void
326 notmuch_directory_destroy (notmuch_directory_t *directory)
327 {
328     talloc_free (directory);
329 }