Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-uid-cache.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-uid-cache.c: UID caching code. */
3
4 /* 
5  * Authors:
6  *  Dan Winship <danw@ximian.com>
7  *
8  * Copyright 2000 Ximian, Inc. (www.ximian.com)
9  *
10  * This program is free software; you can redistribute it and/or 
11  * modify it under the terms of version 2 of the GNU Lesser General Public 
12  * License as published by the Free Software Foundation.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
22  * USA
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <sys/stat.h>
34
35 #include <glib.h>
36 #include <glib/gstdio.h>
37
38 #include <libedataserver/e-data-server-util.h>
39
40 #include "camel-file-utils.h"
41 #include "camel-private.h"
42 #include "camel-uid-cache.h"
43
44 struct _uid_state {
45         int level;
46         gboolean save;
47 };
48
49
50 /**
51  * camel_uid_cache_new:
52  * @filename: path to load the cache from
53  *
54  * Creates a new UID cache, initialized from @filename. If @filename
55  * doesn't already exist, the UID cache will be empty. Otherwise, if
56  * it does exist but can't be read, the function will return %NULL.
57  *
58  * Return value: a new UID cache, or %NULL
59  **/
60 CamelUIDCache *
61 camel_uid_cache_new (const char *filename)
62 {
63         CamelUIDCache *cache;
64         struct stat st;
65         char *dirname, *buf, **uids;
66         int fd, i;
67         
68         dirname = g_path_get_dirname (filename);
69         if (g_mkdir_with_parents (dirname, 0777) == -1) {
70                 g_free (dirname);
71                 return NULL;
72         }
73         
74         g_free (dirname);
75         
76         if ((fd = g_open (filename, O_RDONLY | O_CREAT | O_BINARY, 0666)) == -1)
77                 return NULL;
78         
79         if (fstat (fd, &st) == -1) {
80                 close (fd);
81                 return NULL;
82         }
83         
84         buf = g_malloc (st.st_size + 1);
85         
86         if (st.st_size > 0 && camel_read (fd, buf, st.st_size) == -1) {
87                 close (fd);
88                 g_free (buf);
89                 return NULL;
90         }
91         
92         buf[st.st_size] = '\0';
93         
94         close (fd);
95         
96         cache = g_new (CamelUIDCache, 1);
97         cache->uids = g_hash_table_new (g_str_hash, g_str_equal);
98         cache->filename = g_strdup (filename);
99         cache->level = 1;
100         cache->expired = 0;
101         cache->size = 0;
102         cache->fd = -1;
103         
104         uids = g_strsplit (buf, "\n", 0);
105         g_free (buf);
106         for (i = 0; uids[i]; i++) {
107                 struct _uid_state *state;
108                 
109                 state = g_new (struct _uid_state, 1);
110                 state->level = cache->level;
111                 state->save = TRUE;
112                 
113                 g_hash_table_insert (cache->uids, uids[i], state);
114         }
115         
116         g_free (uids);
117         
118         return cache;
119 }
120
121
122 static void
123 maybe_write_uid (gpointer key, gpointer value, gpointer data)
124 {
125         CamelUIDCache *cache = data;
126         struct _uid_state *state = value;
127         
128         if (cache->fd == -1)
129                 return;
130         
131         if (state && state->level == cache->level && state->save) {
132                 if (camel_write (cache->fd, key, strlen (key)) == -1 || 
133                     camel_write (cache->fd, "\n", 1) == -1) {
134                         cache->fd = -1;
135                 } else {
136                         cache->size += strlen (key) + 1;
137                 }
138         } else {
139                 /* keep track of how much space the expired uids would
140                  * have taken up in the cache */
141                 cache->expired += strlen (key) + 1;
142         }
143 }
144
145
146 /**
147  * camel_uid_cache_save:
148  * @cache: a CamelUIDCache
149  *
150  * Attempts to save @cache back to disk.
151  *
152  * Return value: success or failure
153  **/
154 gboolean
155 camel_uid_cache_save (CamelUIDCache *cache)
156 {
157         char *filename;
158         int errnosav;
159         int fd;
160         
161         filename = g_strdup_printf ("%s~", cache->filename);
162         if ((fd = g_open (filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666)) == -1) {
163                 g_free (filename);
164                 return FALSE;
165         }
166         
167         cache->fd = fd;
168         cache->size = 0;
169         cache->expired = 0;
170         g_hash_table_foreach (cache->uids, maybe_write_uid, cache);
171         
172         if (fsync (fd) == -1)
173                 goto exception;
174         
175         close (fd);
176         fd = -1;
177         cache->fd = -1;
178
179         if (g_rename (filename, cache->filename) == -1)
180                 goto exception;
181         
182         g_free (filename);
183         
184         return TRUE;
185         
186  exception:
187         
188         errnosav = errno;
189         
190 #ifdef ENABLE_SPASMOLYTIC
191         if (fd != -1) {
192                 /*
193                  * If our new cache size is larger than the old cache,
194                  * even if we haven't finished writing it out
195                  * successfully, we should still attempt to replace
196                  * the old cache with the new cache because it will at
197                  * least avoid re-downloading a few extra messages
198                  * than if we just kept the old cache.
199                  *
200                  * Similarly, even if the new cache size is smaller
201                  * than the old cache size, but we've expired enough
202                  * uids to make up for the difference in size (or
203                  * more), then we should replace the old cache with
204                  * the new cache as well.
205                  */
206                 
207                 if (g_stat (cache->filename, &st) == 0 &&
208                     (cache->size > st.st_size || cache->size + cache->expired > st.st_size)) {
209                         if (ftruncate (fd, (off_t) cache->size) != -1) {
210                                 cache->size = 0;
211                                 cache->expired = 0;
212                                 goto overwrite; /* FIXME: no such label */
213                         }
214                 }               
215         }
216 #endif
217         if (fd != -1) {
218                 close (fd);
219                 cache->fd = -1;
220         }
221
222         g_unlink (filename);
223         g_free (filename);
224         
225         errno = errnosav;
226         
227         return FALSE;
228 }
229
230
231 static void
232 free_uid (gpointer key, gpointer value, gpointer data)
233 {
234         g_free (key);
235         g_free (value);
236 }
237
238
239 /**
240  * camel_uid_cache_destroy:
241  * @cache: a CamelUIDCache
242  *
243  * Destroys @cache and frees its data.
244  **/
245 void
246 camel_uid_cache_destroy (CamelUIDCache *cache)
247 {
248         g_hash_table_foreach (cache->uids, free_uid, NULL);
249         g_hash_table_destroy (cache->uids);
250         g_free (cache->filename);
251         g_free (cache);
252 }
253
254
255 /**
256  * camel_uid_cache_get_new_uids:
257  * @cache: a CamelUIDCache
258  * @uids: an array of UIDs
259  *
260  * Returns an array of UIDs from @uids that are not in @cache, and
261  * removes UIDs from @cache that aren't in @uids.
262  *
263  * Return value: an array of new UIDs, which must be freed with
264  * camel_uid_cache_free_uids().
265  **/
266 GPtrArray *
267 camel_uid_cache_get_new_uids (CamelUIDCache *cache, GPtrArray *uids)
268 {
269         GPtrArray *new_uids;
270         gpointer old_uid;
271         char *uid;
272         int i;
273         
274         new_uids = g_ptr_array_new ();
275         cache->level++;
276
277         for (i = 0; i < uids->len; i++) {
278                 struct _uid_state *state;
279                 
280                 uid = uids->pdata[i];
281                 if (g_hash_table_lookup_extended (cache->uids, uid, (void **)&old_uid, (void **)&state)) {
282                         g_hash_table_remove (cache->uids, uid);
283                         g_free (old_uid);
284                 } else {
285                         g_ptr_array_add (new_uids, g_strdup (uid));
286                         state = g_new (struct _uid_state, 1);
287                         state->save = FALSE;
288                 }
289                 
290                 state->level = cache->level;
291                 g_hash_table_insert (cache->uids, g_strdup (uid), state);
292         }
293         
294         return new_uids;
295 }
296
297
298 /**
299  * camel_uid_cache_save_uid:
300  * @cache: a CamelUIDCache
301  * @uid: a uid to save
302  *
303  * Marks a uid for saving.
304  **/
305 void
306 camel_uid_cache_save_uid (CamelUIDCache *cache, const char *uid)
307 {
308         struct _uid_state *state;
309         gpointer old_uid;
310         
311         g_return_if_fail (uid != NULL);
312
313         if (g_hash_table_lookup_extended (cache->uids, uid, (void **)&old_uid, (void **)&state)) {
314                 state->save = TRUE;
315                 state->level = cache->level;
316         } else {
317                 state = g_new (struct _uid_state, 1);
318                 state->save = TRUE;
319                 state->level = cache->level;
320
321                 g_hash_table_insert (cache->uids, g_strdup (uid), state);
322         }
323 }
324
325
326 /**
327  * camel_uid_cache_free_uids:
328  * @uids: an array returned from camel_uid_cache_get_new_uids()
329  *
330  * Frees the array of UIDs.
331  **/
332 void
333 camel_uid_cache_free_uids (GPtrArray *uids)
334 {
335         int i;
336         
337         for (i = 0; i < uids->len; i++)
338                 g_free (uids->pdata[i]);
339         g_ptr_array_free (uids, TRUE);
340 }