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