]> git.notmuchmail.org Git - notmuch/blob - lib/open.cc
lib/open: add support for config profiles and default locations
[notmuch] / lib / open.cc
1 #include <unistd.h>
2 #include "database-private.h"
3 #include "parse-time-vrp.h"
4
5 #if HAVE_XAPIAN_DB_RETRY_LOCK
6 #define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
7 #else
8 #define DB_ACTION Xapian::DB_CREATE_OR_OPEN
9 #endif
10
11 notmuch_status_t
12 notmuch_database_open (const char *path,
13                        notmuch_database_mode_t mode,
14                        notmuch_database_t **database)
15 {
16     char *status_string = NULL;
17     notmuch_status_t status;
18
19     status = notmuch_database_open_verbose (path, mode, database,
20                                             &status_string);
21
22     if (status_string) {
23         fputs (status_string, stderr);
24         free (status_string);
25     }
26
27     return status;
28 }
29
30 notmuch_status_t
31 notmuch_database_open_verbose (const char *path,
32                                notmuch_database_mode_t mode,
33                                notmuch_database_t **database,
34                                char **status_string)
35 {
36     return notmuch_database_open_with_config (path, mode, "", NULL,
37                                               database, status_string);
38 }
39
40 static const char *
41 _xdg_dir (void *ctx,
42           const char *xdg_root_variable,
43           const char *xdg_prefix,
44           const char *profile_name)
45 {
46     const char *xdg_root = getenv (xdg_root_variable);
47
48     if (! xdg_root) {
49         const char *home = getenv ("HOME");
50
51         if (! home) return NULL;
52
53         xdg_root = talloc_asprintf (ctx,
54                                     "%s/%s",
55                                     home,
56                                     xdg_prefix);
57     }
58
59     if (! profile_name)
60         profile_name = getenv ("NOTMUCH_PROFILE");
61
62     if (! profile_name)
63         profile_name = "default";
64
65     return talloc_asprintf (ctx,
66                             "%s/notmuch/%s",
67                             xdg_root,
68                             profile_name);
69 }
70
71 static notmuch_status_t
72 _load_key_file (const char *path,
73                 const char *profile,
74                 GKeyFile **key_file)
75 {
76     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
77     void *local = talloc_new (NULL);
78
79     if (path && EMPTY_STRING (path))
80         goto DONE;
81
82     if (! path)
83         path = getenv ("NOTMUCH_CONFIG");
84
85     if (! path) {
86         const char *dir = _xdg_dir (local, "XDG_CONFIG_HOME", ".config", profile);
87
88         if (dir) {
89             path = talloc_asprintf (local, "%s/config", dir);
90             if (access (path, R_OK) !=0)
91                 path = NULL;
92         }
93     }
94
95     if (! path) {
96         const char *home = getenv ("HOME");
97
98         path = talloc_asprintf (local, "%s/.notmuch-config", home);
99
100         if (! profile)
101             profile = getenv ("NOTMUCH_PROFILE");
102
103         if (profile)
104             path = talloc_asprintf (local, "%s.%s", path, profile);
105     }
106
107     *key_file = g_key_file_new ();
108     if (! g_key_file_load_from_file (*key_file, path, G_KEY_FILE_NONE, NULL)) {
109         status = NOTMUCH_STATUS_FILE_ERROR;
110     }
111
112 DONE:
113     talloc_free (local);
114     return status;
115 }
116
117 notmuch_status_t
118 notmuch_database_open_with_config (const char *database_path,
119                                    notmuch_database_mode_t mode,
120                                    const char *config_path,
121                                    unused(const char *profile),
122                                    notmuch_database_t **database,
123                                    char **status_string)
124 {
125     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
126     void *local = talloc_new (NULL);
127     notmuch_database_t *notmuch = NULL;
128     char *notmuch_path, *xapian_path, *incompat_features;
129     char *message = NULL;
130     struct stat st;
131     int err;
132     unsigned int version;
133     GKeyFile *key_file = NULL;
134     static int initialized = 0;
135
136     status = _load_key_file (config_path, profile, &key_file);
137     if (status) {
138         message = strdup ("Error: cannot load config file");
139         goto DONE;
140     }
141         
142     if (! database_path && key_file)
143         database_path = g_key_file_get_value (key_file, "database", "path", NULL);
144
145     if (database_path == NULL) {
146         message = strdup ("Error: Cannot open a database for a NULL path.\n");
147         status = NOTMUCH_STATUS_NULL_POINTER;
148         goto DONE;
149     }
150
151     if (database_path[0] != '/') {
152         message = strdup ("Error: Database path must be absolute.\n");
153         status = NOTMUCH_STATUS_PATH_ERROR;
154         goto DONE;
155     }
156
157     if (! (notmuch_path = talloc_asprintf (local, "%s/%s", database_path, ".notmuch"))) {
158         message = strdup ("Out of memory\n");
159         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
160         goto DONE;
161     }
162
163     err = stat (notmuch_path, &st);
164     if (err) {
165         IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
166                                  notmuch_path, strerror (errno)));
167         status = NOTMUCH_STATUS_FILE_ERROR;
168         goto DONE;
169     }
170
171     if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
172         message = strdup ("Out of memory\n");
173         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
174         goto DONE;
175     }
176
177     /* Initialize the GLib type system and threads */
178 #if ! GLIB_CHECK_VERSION (2, 35, 1)
179     g_type_init ();
180 #endif
181
182     /* Initialize gmime */
183     if (! initialized) {
184         g_mime_init ();
185         initialized = 1;
186     }
187
188     notmuch = talloc_zero (NULL, notmuch_database_t);
189     notmuch->exception_reported = false;
190     notmuch->status_string = NULL;
191     notmuch->path = talloc_strdup (notmuch, database_path);
192
193     strip_trailing (notmuch->path, '/');
194
195     notmuch->writable_xapian_db = NULL;
196     notmuch->atomic_nesting = 0;
197     notmuch->view = 1;
198     try {
199         std::string last_thread_id;
200         std::string last_mod;
201
202         if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
203             notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path,
204                                                                         DB_ACTION);
205             notmuch->xapian_db = notmuch->writable_xapian_db;
206         } else {
207             notmuch->xapian_db = new Xapian::Database (xapian_path);
208         }
209
210         /* Check version.  As of database version 3, we represent
211          * changes in terms of features, so assume a version bump
212          * means a dramatically incompatible change. */
213         version = notmuch_database_get_version (notmuch);
214         if (version > NOTMUCH_DATABASE_VERSION) {
215             IGNORE_RESULT (asprintf (&message,
216                                      "Error: Notmuch database at %s\n"
217                                      "       has a newer database format version (%u) than supported by this\n"
218                                      "       version of notmuch (%u).\n",
219                                      notmuch_path, version, NOTMUCH_DATABASE_VERSION));
220             notmuch_database_destroy (notmuch);
221             notmuch = NULL;
222             status = NOTMUCH_STATUS_FILE_ERROR;
223             goto DONE;
224         }
225
226         /* Check features. */
227         incompat_features = NULL;
228         notmuch->features = _notmuch_database_parse_features (
229             local, notmuch->xapian_db->get_metadata ("features").c_str (),
230             version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
231             &incompat_features);
232         if (incompat_features) {
233             IGNORE_RESULT (asprintf (&message,
234                                      "Error: Notmuch database at %s\n"
235                                      "       requires features (%s)\n"
236                                      "       not supported by this version of notmuch.\n",
237                                      notmuch_path, incompat_features));
238             notmuch_database_destroy (notmuch);
239             notmuch = NULL;
240             status = NOTMUCH_STATUS_FILE_ERROR;
241             goto DONE;
242         }
243
244         notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
245         last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
246         if (last_thread_id.empty ()) {
247             notmuch->last_thread_id = 0;
248         } else {
249             const char *str;
250             char *end;
251
252             str = last_thread_id.c_str ();
253             notmuch->last_thread_id = strtoull (str, &end, 16);
254             if (*end != '\0')
255                 INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
256         }
257
258         /* Get current highest revision number. */
259         last_mod = notmuch->xapian_db->get_value_upper_bound (
260             NOTMUCH_VALUE_LAST_MOD);
261         if (last_mod.empty ())
262             notmuch->revision = 0;
263         else
264             notmuch->revision = Xapian::sortable_unserialise (last_mod);
265         notmuch->uuid = talloc_strdup (
266             notmuch, notmuch->xapian_db->get_uuid ().c_str ());
267
268         notmuch->query_parser = new Xapian::QueryParser;
269         notmuch->term_gen = new Xapian::TermGenerator;
270         notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
271         notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
272         notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:");
273         notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
274         notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
275         notmuch->query_parser->set_database (*notmuch->xapian_db);
276         notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
277         notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
278         notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
279         notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
280         notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
281
282         /* Configuration information is needed to set up query parser */
283         status = _notmuch_config_load_from_database (notmuch);
284         if (status)
285             goto DONE;
286
287         if (key_file)
288             status = _notmuch_config_load_from_file (notmuch, key_file);
289         if (status)
290             goto DONE;
291
292         status = _notmuch_database_setup_standard_query_fields (notmuch);
293         if (status)
294             goto DONE;
295
296         status = _notmuch_database_setup_user_query_fields (notmuch);
297         if (status)
298             goto DONE;
299
300     } catch (const Xapian::Error &error) {
301         IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
302                                  error.get_msg ().c_str ()));
303         notmuch_database_destroy (notmuch);
304         notmuch = NULL;
305         status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
306     }
307
308   DONE:
309     talloc_free (local);
310
311     if (message) {
312         if (status_string)
313             *status_string = message;
314         else
315             free (message);
316     }
317
318     if (database)
319         *database = notmuch;
320     else
321         talloc_free (notmuch);
322
323     if (notmuch)
324         notmuch->open = true;
325
326     return status;
327 }