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