aeee9caf067580aaef1c5d309eb18b185e3078a3
[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  * The code here is general enough to be reused for any case of
28  * iterating over the non-prefixed portion of terms sharing a common
29  * prefix.
30  */
31 static notmuch_filenames_t *
32 _create_filenames_for_terms_with_prefix (void *ctx,
33                                          notmuch_database_t *notmuch,
34                                          const char *prefix)
35 {
36     notmuch_string_list_t *filename_list;
37     Xapian::TermIterator i, end;
38     int prefix_len = strlen (prefix);
39
40     filename_list = _notmuch_string_list_create (ctx);
41     if (unlikely (filename_list == NULL))
42         return NULL;
43
44     end = notmuch->xapian_db->allterms_end (prefix);
45
46     for (i = notmuch->xapian_db->allterms_begin (prefix); i != end; i++)
47     {
48         std::string term = *i;
49
50         _notmuch_string_list_append (filename_list, term.c_str () + prefix_len);
51     }
52
53     return _notmuch_filenames_create (ctx, filename_list);
54 }
55
56 struct _notmuch_directory {
57     notmuch_database_t *notmuch;
58     Xapian::docid document_id;
59     Xapian::Document doc;
60     time_t mtime;
61 };
62
63 /* We end up having to call the destructor explicitly because we had
64  * to use "placement new" in order to initialize C++ objects within a
65  * block that we allocated with talloc. So C++ is making talloc
66  * slightly less simple to use, (we wouldn't need
67  * talloc_set_destructor at all otherwise).
68  */
69 static int
70 _notmuch_directory_destructor (notmuch_directory_t *directory)
71 {
72     directory->doc.~Document ();
73
74     return 0;
75 }
76
77 static notmuch_private_status_t
78 find_directory_document (notmuch_database_t *notmuch,
79                          const char *db_path,
80                          Xapian::Document *document)
81 {
82     notmuch_private_status_t status;
83     Xapian::docid doc_id;
84
85     status = _notmuch_database_find_unique_doc_id (notmuch, "directory",
86                                                    db_path, &doc_id);
87     if (status) {
88         *document = Xapian::Document ();
89         return status;
90     }
91
92     *document = notmuch->xapian_db->get_document (doc_id);
93     return NOTMUCH_PRIVATE_STATUS_SUCCESS;
94 }
95
96 notmuch_directory_t *
97 _notmuch_directory_create (notmuch_database_t *notmuch,
98                            const char *path,
99                            notmuch_status_t *status_ret)
100 {
101     Xapian::WritableDatabase *db;
102     notmuch_directory_t *directory;
103     notmuch_private_status_t private_status;
104     const char *db_path;
105
106     *status_ret = NOTMUCH_STATUS_SUCCESS;
107
108     path = _notmuch_database_relative_path (notmuch, path);
109
110     if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY)
111         INTERNAL_ERROR ("Failure to ensure database is writable");
112
113     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
114
115     directory = talloc (notmuch, notmuch_directory_t);
116     if (unlikely (directory == NULL))
117         return NULL;
118
119     directory->notmuch = notmuch;
120
121     /* "placement new"---not actually allocating memory */
122     new (&directory->doc) Xapian::Document;
123
124     talloc_set_destructor (directory, _notmuch_directory_destructor);
125
126     db_path = _notmuch_database_get_directory_db_path (path);
127
128     try {
129         Xapian::TermIterator i, end;
130
131         private_status = find_directory_document (notmuch, db_path,
132                                                   &directory->doc);
133         directory->document_id = directory->doc.get_docid ();
134
135         if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
136             void *local = talloc_new (directory);
137             const char *parent, *basename;
138             Xapian::docid parent_id;
139             char *term = talloc_asprintf (local, "%s%s",
140                                           _find_prefix ("directory"), db_path);
141             directory->doc.add_term (term, 0);
142
143             directory->doc.set_data (path);
144
145             _notmuch_database_split_path (local, path, &parent, &basename);
146
147             _notmuch_database_find_directory_id (notmuch, parent, &parent_id);
148
149             if (basename) {
150                 term = talloc_asprintf (local, "%s%u:%s",
151                                         _find_prefix ("directory-direntry"),
152                                         parent_id, basename);
153                 directory->doc.add_term (term, 0);
154             }
155
156             directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
157                                       Xapian::sortable_serialise (0));
158
159             directory->document_id = _notmuch_database_generate_doc_id (notmuch);
160             db->replace_document (directory->document_id, directory->doc);
161             talloc_free (local);
162         }
163
164         directory->mtime = Xapian::sortable_unserialise (
165             directory->doc.get_value (NOTMUCH_VALUE_TIMESTAMP));
166     } catch (const Xapian::Error &error) {
167         fprintf (stderr,
168                  "A Xapian exception occurred creating a directory: %s.\n",
169                  error.get_msg().c_str());
170         notmuch->exception_reported = TRUE;
171         notmuch_directory_destroy (directory);
172         *status_ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
173         return NULL;
174     }
175
176     if (db_path != path)
177         free ((char *) db_path);
178
179     return directory;
180 }
181
182 unsigned int
183 _notmuch_directory_get_document_id (notmuch_directory_t *directory)
184 {
185     return directory->document_id;
186 }
187
188 notmuch_status_t
189 notmuch_directory_set_mtime (notmuch_directory_t *directory,
190                              time_t mtime)
191 {
192     notmuch_database_t *notmuch = directory->notmuch;
193     Xapian::WritableDatabase *db;
194     notmuch_status_t status;
195
196     status = _notmuch_database_ensure_writable (notmuch);
197     if (status)
198         return status;
199
200     db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
201
202     try {
203         directory->doc.add_value (NOTMUCH_VALUE_TIMESTAMP,
204                                    Xapian::sortable_serialise (mtime));
205
206         db->replace_document (directory->document_id, directory->doc);
207     } catch (const Xapian::Error &error) {
208         fprintf (stderr,
209                  "A Xapian exception occurred setting directory mtime: %s.\n",
210                  error.get_msg().c_str());
211         notmuch->exception_reported = TRUE;
212         return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
213     }
214
215     return NOTMUCH_STATUS_SUCCESS;
216 }
217
218 time_t
219 notmuch_directory_get_mtime (notmuch_directory_t *directory)
220 {
221     return directory->mtime;
222 }
223
224 notmuch_filenames_t *
225 notmuch_directory_get_child_files (notmuch_directory_t *directory)
226 {
227     char *term;
228     notmuch_filenames_t *child_files;
229
230     term = talloc_asprintf (directory, "%s%u:",
231                             _find_prefix ("file-direntry"),
232                             directory->document_id);
233
234     child_files = _create_filenames_for_terms_with_prefix (directory,
235                                                            directory->notmuch,
236                                                            term);
237
238     talloc_free (term);
239
240     return child_files;
241 }
242
243 notmuch_filenames_t *
244 notmuch_directory_get_child_directories (notmuch_directory_t *directory)
245 {
246     char *term;
247     notmuch_filenames_t *child_directories;
248
249     term = talloc_asprintf (directory, "%s%u:",
250                             _find_prefix ("directory-direntry"),
251                             directory->document_id);
252
253     child_directories = _create_filenames_for_terms_with_prefix (directory,
254                                                  directory->notmuch, term);
255
256     talloc_free (term);
257
258     return child_directories;
259 }
260
261 void
262 notmuch_directory_destroy (notmuch_directory_t *directory)
263 {
264     talloc_free (directory);
265 }