Mark plugin as GTK3 type
[claws-mail-maildir-plugin.git] / src / uiddb.c
1 /*
2  * Maildir Plugin -- Maildir++ support for Claws Mail
3  * Copyright (C) 2003-2004 Christoph Hohmann
4  * Copyright (C) 2017-2018 Ricardo Mones and the Claws Mail team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <glib.h>
21 #include <db.h>
22 #include <stdlib.h>
23
24 #include "utils.h"
25 #include "uiddb.h"
26
27 struct _UIDDB
28 {
29         DB      *db_uid;
30         DB      *db_uniq;
31         guint32  lastuid;
32 };
33
34 static gboolean initialized = FALSE;
35 static DB_ENV *dbenv;
36
37 void uiddb_init()
38 {
39         int ret;
40         int flags = DB_INIT_MPOOL | DB_INIT_CDB | DB_CREATE;
41
42         if ((ret = db_env_create(&dbenv, 0)) != 0) {
43                 g_warning("db_env_create: %s", db_strerror(ret));
44                 return;
45         }
46         if ((ret = dbenv->open(dbenv, get_tmp_dir(), flags, 0600)) != 0) {
47                 if (ret != DB_RUNRECOVERY) {
48                         g_warning("dbenv->open: %s", db_strerror(ret));
49                         return;
50                 }
51                 flags |= DB_RECOVER;
52                 if ((ret = dbenv->open(dbenv, get_tmp_dir(), flags, 0600)) != 0) {
53                         g_warning("dbenv->open (recovering): %s", db_strerror(ret));
54                         return;
55                 }
56         }
57
58         initialized = TRUE;
59 }
60
61 void uiddb_done()
62 {
63         dbenv->close(dbenv, 0);
64
65         initialized = FALSE;
66 }
67
68 int get_secondary_key(DB *dbp, const DBT *pkey, const DBT *pdata, DBT *skey)
69 {
70         gchar *uniq;
71
72         memset(skey, 0, sizeof(DBT));
73
74         uniq = pdata->data + sizeof(guint32);
75         skey->data = uniq;
76         skey->size = strlen(uniq);
77
78         return 0;
79 }
80
81 UIDDB *uiddb_open(const gchar *dbfile)
82 {
83         gint     ret;
84         DB      *db_uid, *db_uniq;
85         UIDDB   *uiddb;
86
87         g_return_val_if_fail(initialized, NULL);
88
89         /* open uid key based database */
90         if ((ret = db_create(&db_uid, dbenv, 0)) != 0) {
91                 debug_print("db_create: %s\n", db_strerror(ret));
92                 return NULL;
93         }
94         if ((ret = db_uid->open(db_uid, NULL, dbfile, "uidkey", DB_BTREE, DB_CREATE, 0600)) != 0) {
95                 debug_print("DB->open: %s\n", db_strerror(ret));
96                 db_uid->close(db_uid, 0);
97                 return NULL;
98         }
99         debug_print("UID based database opened\n");
100
101         /* open uniq key based database */
102         if ((ret = db_create(&db_uniq, dbenv, 0)) != 0) {
103                 debug_print("db_create: %s\n", db_strerror(ret));
104                 db_uid->close(db_uid, 0);
105                 return NULL;
106         }
107         if ((ret = db_uniq->open(db_uniq, NULL, dbfile, "uniqkey", DB_BTREE, DB_CREATE, 0600)) != 0) {
108                 debug_print("DB->open: %s\n", db_strerror(ret));
109                 db_uniq->close(db_uniq, 0);
110                 db_uid->close(db_uid, 0);
111                 return NULL;
112         }
113         debug_print("Uniq based database opened\n");
114
115         if ((ret = db_uid->associate(db_uid, NULL, db_uniq, get_secondary_key, 0)) != 0) {
116                 debug_print("DB->associate: %s\n", db_strerror(ret));
117                 db_uid->close(db_uid, 0);
118                 db_uniq->close(db_uniq, 0);
119                 return NULL;
120         }
121         debug_print("Databases associated\n");
122
123         uiddb = g_new0(UIDDB, 1);
124         uiddb->db_uid = db_uid;
125         uiddb->db_uniq = db_uniq;
126         uiddb->lastuid = 0;
127
128         return uiddb;
129 }
130
131 void uiddb_close(UIDDB *uiddb)
132 {
133         g_return_if_fail(uiddb != NULL);
134
135         if (uiddb->db_uid != NULL)
136                 uiddb->db_uid->close(uiddb->db_uid, 0);
137         if (uiddb->db_uniq != NULL)
138                 uiddb->db_uniq->close(uiddb->db_uniq, 0);
139 }
140
141 void uiddb_free_msgdata(MessageData *msgdata)
142 {
143         g_free(msgdata->uniq);
144         g_free(msgdata->info);
145         g_free(msgdata->dir);
146         g_free(msgdata);
147 }
148
149 static DBT marshal(MessageData *msgdata)
150 {
151         DBT dbt;
152         gpointer ptr;
153
154         memset(&dbt, 0, sizeof(dbt));
155         dbt.size = sizeof(msgdata->uid) + \
156                    strlen(msgdata->uniq) + 1 + \
157                    strlen(msgdata->info) + 1 + \
158                    strlen(msgdata->dir) + 1;
159         dbt.data = g_malloc0(dbt.size);
160
161         ptr = dbt.data;
162
163 #define ADD_DATA(dataptr, size) \
164 { \
165         memcpy(ptr, dataptr, size); \
166         ptr += size; \
167 }
168
169         ADD_DATA(&msgdata->uid, sizeof(msgdata->uid));
170         ADD_DATA(msgdata->uniq, strlen(msgdata->uniq) + 1);
171         ADD_DATA(msgdata->info, strlen(msgdata->info) + 1);
172         ADD_DATA(msgdata->dir, strlen(msgdata->dir) + 1);
173
174 #undef ADD_DATA 
175
176         return dbt;
177 }
178
179 static MessageData *unmarshal(DBT dbt)
180 {
181         gpointer ptr;
182         MessageData *msgdata;
183
184         ptr = dbt.data;
185         msgdata = g_new0(MessageData, 1);
186
187         memcpy(&msgdata->uid, ptr, sizeof(msgdata->uid));
188         ptr += sizeof(msgdata->uid);
189         msgdata->uniq = g_strdup(ptr);
190         ptr += strlen(ptr) + 1;
191         msgdata->info = g_strdup(ptr);
192         ptr += strlen(ptr) + 1;
193         msgdata->dir = g_strdup(ptr);
194
195         return msgdata;
196 }
197
198 guint32 uiddb_get_new_uid(UIDDB *uiddb)
199 {
200         DBC *cursor;
201         DBT key, data;
202         gint ret;
203         guint32 uid, lastuid = -1;
204
205         g_return_val_if_fail(uiddb != NULL, 0);
206
207         lastuid = uiddb->lastuid;
208
209         if (uiddb->lastuid > 0)
210                 return ++uiddb->lastuid;
211
212         ret = uiddb->db_uid->cursor(uiddb->db_uid, NULL, &cursor, 0);
213         if (ret != 0) {
214                 debug_print("DB->cursor: %s\n", db_strerror(ret));
215                 return -1;
216         }
217
218         memset(&key, 0, sizeof(key));
219         memset(&data, 0, sizeof(data));
220         while ((ret = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) {
221                 uid = *((guint32 *) key.data);
222
223                 if (uid > lastuid)
224                         lastuid = uid;          
225
226                 memset(&key, 0, sizeof(key));
227                 memset(&data, 0, sizeof(data));
228         }
229
230         cursor->c_close(cursor);
231
232         uiddb->lastuid = ++lastuid;
233         return lastuid;
234 }
235
236 MessageData *uiddb_get_entry_for_uid(UIDDB *uiddb, guint32 uid)
237 {
238         DBT key, data;
239
240         g_return_val_if_fail(uiddb, NULL);
241
242         memset(&key, 0, sizeof(key));
243         memset(&data, 0, sizeof(data));
244
245         key.size = sizeof(guint32);
246         key.data = &uid;
247
248         if (uiddb->db_uid->get(uiddb->db_uid, NULL, &key, &data, 0) != 0)
249                 return NULL;
250
251         return unmarshal(data);
252 }
253
254 MessageData *uiddb_get_entry_for_uniq(UIDDB *uiddb, gchar *uniq)
255 {
256         DBT key, pkey, data;
257
258         g_return_val_if_fail(uiddb, NULL);
259
260         memset(&key, 0, sizeof(key));
261         memset(&pkey, 0, sizeof(pkey));
262         memset(&data, 0, sizeof(data));
263
264         key.size = strlen(uniq);
265         key.data = uniq;
266
267         if (uiddb->db_uniq->pget(uiddb->db_uniq, NULL, &key, &pkey, &data, 0) != 0)
268                 return NULL;
269
270         return unmarshal(data);
271 }
272
273 void uiddb_delete_entry(UIDDB *uiddb, guint32 uid)
274 {
275         DBT key;
276
277         g_return_if_fail(uiddb);
278
279         memset(&key, 0, sizeof(key));
280
281         key.size = sizeof(guint32);
282         key.data = &uid;
283
284         uiddb->db_uid->del(uiddb->db_uid, NULL, &key, 0);
285 }
286
287 void uiddb_insert_entry(UIDDB *uiddb, MessageData *msgdata)
288 {
289         DBT key, data;
290         gint ret;
291
292         g_return_if_fail(uiddb);
293
294         memset(&key, 0, sizeof(key));
295         memset(&data, 0, sizeof(data));
296
297         key.size = sizeof(guint32);
298         key.data = &msgdata->uid;
299
300         data = marshal(msgdata);
301
302         ret = uiddb->db_uid->put(uiddb->db_uid, NULL, &key, &data, 0);
303         if (ret != 0)
304                 debug_print("DB->put: %s\n", db_strerror(ret));
305
306         g_free(data.data);
307 }
308
309 static int uiddb_uid_compare(const void *a, const void *b)
310 {
311     return *(guint32*)a - *(guint32*)b;
312 }
313
314 void uiddb_delete_entries_not_in_list(UIDDB *uiddb, MsgNumberList *list)
315 {
316         DBC *cursor;
317         DBT key, data;
318         gint i, uidcnt, ret;
319         guint32 *uid_sorted;
320
321         g_return_if_fail(uiddb);
322         if (list == NULL)
323                 return;
324
325         ret = uiddb->db_uid->cursor(uiddb->db_uid, NULL, &cursor, DB_WRITECURSOR);
326         if (ret != 0) {
327                 debug_print("DB->cursor: %s\n", db_strerror(ret));
328                 return;
329         }
330
331         uidcnt = g_slist_length(list);
332         uid_sorted = g_new(guint32, uidcnt);
333         for (i = 0; i < uidcnt; i++) {
334             uid_sorted[i] = GPOINTER_TO_INT(list->data);
335             list = g_slist_next(list);
336         }
337         
338         memset(&key, 0, sizeof(key));
339         memset(&data, 0, sizeof(data));
340         while ((ret = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) {
341                 guint32 uid = *((guint32 *) key.data);
342
343                 if (bsearch(&uid, uid_sorted, uidcnt, sizeof(guint32), &uiddb_uid_compare) == NULL)
344                         cursor->c_del(cursor, 0);
345
346                 memset(&key, 0, sizeof(key));
347                 memset(&data, 0, sizeof(data));
348         }
349
350         g_free(uid_sorted);
351
352         cursor->c_close(cursor);
353 }