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