Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-disco-folder.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-disco-folder.c: abstract class for a disconnectable folder */
3
4 /* 
5  * Authors: Dan Winship <danw@ximian.com>
6  *
7  * Copyright (C) 2001 Ximian, Inc.
8  *
9  * This program is free software; you can redistribute it and/or 
10  * modify it under the terms of version 2 of the GNU Lesser General Public 
11  * License as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21  * USA
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <glib.h>
29 #include <glib/gi18n-lib.h>
30
31 #include "camel-disco-folder.h"
32 #include "camel-disco-store.h"
33 #include "camel-exception.h"
34 #include "camel-session.h"
35
36 #define CF_CLASS(o) (CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS (o)))
37 #define CDF_CLASS(o) (CAMEL_DISCO_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS (o)))
38
39 static CamelFolderClass *parent_class = NULL;
40 static GSList *disco_folder_properties;
41
42 static CamelProperty disco_property_list[] = {
43         { CAMEL_DISCO_FOLDER_OFFLINE_SYNC, "offline_sync", N_("Copy folder content locally for offline operation") },
44 };
45
46 static int disco_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args);
47 static int disco_setv(CamelObject *object, CamelException *ex, CamelArgV *args);
48
49 static void disco_refresh_info (CamelFolder *folder, CamelException *ex);
50 static void disco_refresh_info_online (CamelFolder *folder, CamelException *ex);
51 static void disco_sync (CamelFolder *folder, gboolean expunge, CamelException *ex);
52 static void disco_expunge (CamelFolder *folder, CamelException *ex);
53
54 static void disco_append_message (CamelFolder *folder, CamelMimeMessage *message,
55                                   const CamelMessageInfo *info, char **appended_uid, CamelException *ex);
56 static void disco_transfer_messages_to (CamelFolder *source, GPtrArray *uids,
57                                         CamelFolder *destination,
58                                         GPtrArray **transferred_uids,
59                                         gboolean delete_originals,
60                                         CamelException *ex);
61
62 static void disco_cache_message       (CamelDiscoFolder *disco_folder,
63                                        const char *uid, CamelException *ex);
64 static void disco_prepare_for_offline (CamelDiscoFolder *disco_folder,
65                                        const char *expression,
66                                        CamelException *ex);
67
68 static void
69 camel_disco_folder_class_init (CamelDiscoFolderClass *camel_disco_folder_class)
70 {
71         CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_disco_folder_class);
72
73         parent_class = CAMEL_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_folder_get_type ()));
74
75         ((CamelObjectClass *)camel_folder_class)->getv = disco_getv;
76         ((CamelObjectClass *)camel_folder_class)->setv = disco_setv;
77
78         /* virtual method definition */
79         camel_disco_folder_class->cache_message = disco_cache_message;
80         camel_disco_folder_class->prepare_for_offline = disco_prepare_for_offline;
81         camel_disco_folder_class->refresh_info_online = disco_refresh_info_online;
82
83         /* virtual method overload */
84         camel_folder_class->refresh_info = disco_refresh_info;
85         camel_folder_class->sync = disco_sync;
86         camel_folder_class->expunge = disco_expunge;
87
88         camel_folder_class->append_message = disco_append_message;
89         camel_folder_class->transfer_messages_to = disco_transfer_messages_to;
90 }
91
92 struct _cdf_sync_msg {
93         CamelSessionThreadMsg msg;
94
95         CamelFolder *folder;
96         CamelFolderChangeInfo *changes;
97 };
98
99 static void
100 cdf_sync_offline(CamelSession *session, CamelSessionThreadMsg *mm)
101 {
102         struct _cdf_sync_msg *m = (struct _cdf_sync_msg *)mm;
103         int i;
104
105         camel_operation_start(NULL, _("Downloading new messages for offline mode"));
106
107         if (m->changes) {
108                 for (i=0;i<m->changes->uid_added->len;i++) {
109                         int pc = i * 100 / m->changes->uid_added->len;
110
111                         camel_operation_progress(NULL, pc);
112                         camel_disco_folder_cache_message((CamelDiscoFolder *)m->folder,
113                                                          m->changes->uid_added->pdata[i],
114                                                          &mm->ex);
115                 }
116         } else {
117                 camel_disco_folder_prepare_for_offline((CamelDiscoFolder *)m->folder,
118                                                        "(match-all)",
119                                                        &mm->ex);
120         }
121
122         camel_operation_end(NULL);
123 }
124
125 static void
126 cdf_sync_free(CamelSession *session, CamelSessionThreadMsg *mm)
127 {
128         struct _cdf_sync_msg *m = (struct _cdf_sync_msg *)mm;
129
130         if (m->changes)
131                 camel_folder_change_info_free(m->changes);
132         camel_object_unref(m->folder);
133 }
134
135 static CamelSessionThreadOps cdf_sync_ops = {
136         cdf_sync_offline,
137         cdf_sync_free,
138 };
139
140 static void
141 cdf_folder_changed(CamelFolder *folder, CamelFolderChangeInfo *changes, void *dummy)
142 {
143         if (changes->uid_added->len > 0
144             && (((CamelDiscoFolder *)folder)->offline_sync
145                 || camel_url_get_param(((CamelService *)folder->parent_store)->url, "offline_sync"))) {
146                 CamelSession *session = ((CamelService *)folder->parent_store)->session;
147                 struct _cdf_sync_msg *m;
148
149                 m = camel_session_thread_msg_new(session, &cdf_sync_ops, sizeof(*m));
150                 m->changes = camel_folder_change_info_new();
151                 camel_folder_change_info_cat(m->changes, changes);
152                 m->folder = folder;
153                 camel_object_ref(folder);
154                 camel_session_thread_queue(session, &m->msg, 0);
155         }
156 }
157
158 static void
159 camel_disco_folder_init(CamelDiscoFolder *folder)
160 {
161         camel_object_hook_event(folder, "folder_changed", (CamelObjectEventHookFunc)cdf_folder_changed, NULL);
162 }
163
164 CamelType
165 camel_disco_folder_get_type (void)
166 {
167         static CamelType camel_disco_folder_type = CAMEL_INVALID_TYPE;
168         int i;
169
170         if (camel_disco_folder_type == CAMEL_INVALID_TYPE) {
171                 camel_disco_folder_type = camel_type_register (
172                         CAMEL_FOLDER_TYPE, "CamelDiscoFolder",
173                         sizeof (CamelDiscoFolder),
174                         sizeof (CamelDiscoFolderClass),
175                         (CamelObjectClassInitFunc)camel_disco_folder_class_init, NULL,
176                         (CamelObjectInitFunc)camel_disco_folder_init, NULL);
177
178                 for (i=0;i<sizeof(disco_property_list)/sizeof(disco_property_list[0]);i++) {
179                         disco_property_list[i].description = _(disco_property_list[i].description);
180                         disco_folder_properties = g_slist_prepend(disco_folder_properties, &disco_property_list[i]);
181                 }
182         }
183
184         return camel_disco_folder_type;
185 }
186
187 static int
188 disco_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
189 {
190         int i, count=0;
191         guint32 tag;
192
193         for (i=0;i<args->argc;i++) {
194                 CamelArgGet *arg = &args->argv[i];
195
196                 tag = arg->tag;
197
198                 switch (tag & CAMEL_ARG_TAG) {
199                 case CAMEL_OBJECT_ARG_PERSISTENT_PROPERTIES:
200                 case CAMEL_FOLDER_ARG_PROPERTIES: {
201                         CamelArgGetV props;
202
203                         props.argc = 1;
204                         props.argv[0] = *arg;
205                         ((CamelObjectClass *)parent_class)->getv(object, ex, &props);
206                         *arg->ca_ptr = g_slist_concat(*arg->ca_ptr, g_slist_copy(disco_folder_properties));
207                         break; }
208                         /* disco args */
209                 case CAMEL_DISCO_FOLDER_ARG_OFFLINE_SYNC:
210                         *arg->ca_int = ((CamelDiscoFolder *)object)->offline_sync;
211                         break;
212                 default:
213                         count++;
214                         continue;
215                 }
216
217                 arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
218         }
219
220         if (count)
221                 return ((CamelObjectClass *)parent_class)->getv(object, ex, args);
222
223         return 0;
224 }
225
226 static int
227 disco_setv(CamelObject *object, CamelException *ex, CamelArgV *args)
228 {
229         int save = 0;
230         int i;
231         guint32 tag;
232
233         for (i=0;i<args->argc;i++) {
234                 CamelArg *arg = &args->argv[i];
235
236                 tag = arg->tag;
237
238                 switch (tag & CAMEL_ARG_TAG) {
239                 case CAMEL_DISCO_FOLDER_ARG_OFFLINE_SYNC:
240                         if (((CamelDiscoFolder *)object)->offline_sync != arg->ca_int) {
241                                 ((CamelDiscoFolder *)object)->offline_sync = arg->ca_int;
242                                 save = 1;
243                         }
244                         break;
245                 default:
246                         continue;
247                 }
248
249                 arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
250         }
251
252         if (save)
253                 camel_object_state_write(object);
254
255         return ((CamelObjectClass *)parent_class)->setv(object, ex, args);
256 }
257
258 static void
259 disco_refresh_info_online(CamelFolder *folder, CamelException *ex)
260 {
261         /* NOOP */;
262 }
263
264 static void
265 disco_refresh_info (CamelFolder *folder, CamelException *ex)
266 {
267         if (camel_disco_store_status (CAMEL_DISCO_STORE (folder->parent_store)) != CAMEL_DISCO_STORE_ONLINE)
268                 return;
269         CDF_CLASS (folder)->refresh_info_online (folder, ex);
270 }
271
272 static void
273 disco_sync (CamelFolder *folder, gboolean expunge, CamelException *ex)
274 {
275         void (*sync)(CamelFolder *, CamelException *) = NULL;
276
277         if (expunge) {
278                 disco_expunge (folder, ex);
279                 if (camel_exception_is_set (ex))
280                         return;
281         }
282
283         camel_object_state_write(folder);
284
285         switch (camel_disco_store_status (CAMEL_DISCO_STORE (folder->parent_store))) {
286         case CAMEL_DISCO_STORE_ONLINE:
287                 sync = CDF_CLASS (folder)->sync_online;
288                 break;
289
290         case CAMEL_DISCO_STORE_OFFLINE:
291                 sync = CDF_CLASS (folder)->sync_offline;
292                 break;
293
294         case CAMEL_DISCO_STORE_RESYNCING:
295                 sync = CDF_CLASS (folder)->sync_resyncing;
296                 break;
297         }
298
299         if (sync) {
300                 sync(folder, ex);
301         } else {
302                 g_warning("Class '%s' doesn't implement CamelDiscoFolder:sync methods",
303                           ((CamelObject *)folder)->klass->name);
304         }
305 }
306
307 static void
308 disco_expunge_uids (CamelFolder *folder, GPtrArray *uids, CamelException *ex)
309 {
310         CamelDiscoStore *disco = CAMEL_DISCO_STORE (folder->parent_store);
311         void (*expunge_uids)(CamelFolder *, GPtrArray *, CamelException *) = NULL;
312
313         if (uids->len == 0)
314                 return;
315
316         switch (camel_disco_store_status (disco)) {
317         case CAMEL_DISCO_STORE_ONLINE:
318                 expunge_uids = CDF_CLASS (folder)->expunge_uids_online;
319                 break;
320
321         case CAMEL_DISCO_STORE_OFFLINE:
322                 expunge_uids = CDF_CLASS (folder)->expunge_uids_offline;
323                 break;
324
325         case CAMEL_DISCO_STORE_RESYNCING:
326                 expunge_uids = CDF_CLASS (folder)->expunge_uids_resyncing;
327                 break;
328         }
329
330         if (expunge_uids) {
331                 expunge_uids(folder, uids, ex);
332         } else {
333                 g_warning("Class '%s' doesn't implement CamelDiscoFolder:expunge_uids methods",
334                           ((CamelObject *)folder)->klass->name);
335         }
336 }
337
338 static void
339 disco_expunge (CamelFolder *folder, CamelException *ex)
340 {
341         GPtrArray *uids;
342         int i, count;
343         CamelMessageInfo *info;
344
345         uids = g_ptr_array_new ();
346         count = camel_folder_summary_count (folder->summary);
347         for (i = 0; i < count; i++) {
348                 info = camel_folder_summary_index (folder->summary, i);
349                 if (camel_message_info_flags(info) & CAMEL_MESSAGE_DELETED)
350                         g_ptr_array_add (uids, g_strdup (camel_message_info_uid (info)));
351                 camel_message_info_free(info);
352         }
353
354         disco_expunge_uids (folder, uids, ex);
355
356         for (i = 0; i < uids->len; i++)
357                 g_free (uids->pdata[i]);
358         g_ptr_array_free (uids, TRUE);
359 }
360
361 static void
362 disco_append_message (CamelFolder *folder, CamelMimeMessage *message,
363                       const CamelMessageInfo *info, char **appended_uid,
364                       CamelException *ex)
365 {
366         CamelDiscoStore *disco = CAMEL_DISCO_STORE (folder->parent_store);
367
368         switch (camel_disco_store_status (disco)) {
369         case CAMEL_DISCO_STORE_ONLINE:
370                 CDF_CLASS (folder)->append_online (folder, message, info,
371                                                    appended_uid, ex);
372                 break;
373
374         case CAMEL_DISCO_STORE_OFFLINE:
375                 CDF_CLASS (folder)->append_offline (folder, message, info,
376                                                     appended_uid, ex);
377                 break;
378
379         case CAMEL_DISCO_STORE_RESYNCING:
380                 CDF_CLASS (folder)->append_resyncing (folder, message, info,
381                                                       appended_uid, ex);
382                 break;
383         }
384 }
385
386 static void
387 disco_transfer_messages_to (CamelFolder *source, GPtrArray *uids,
388                             CamelFolder *dest, GPtrArray **transferred_uids,
389                             gboolean delete_originals, CamelException *ex)
390 {
391         CamelDiscoStore *disco = CAMEL_DISCO_STORE (source->parent_store);
392
393         switch (camel_disco_store_status (disco)) {
394         case CAMEL_DISCO_STORE_ONLINE:
395                 CDF_CLASS (source)->transfer_online (source, uids,
396                                                      dest, transferred_uids,
397                                                      delete_originals, ex);
398                 break;
399
400         case CAMEL_DISCO_STORE_OFFLINE:
401                 CDF_CLASS (source)->transfer_offline (source, uids,
402                                                       dest, transferred_uids,
403                                                       delete_originals, ex);
404                 break;
405
406         case CAMEL_DISCO_STORE_RESYNCING:
407                 CDF_CLASS (source)->transfer_resyncing (source, uids,
408                                                         dest, transferred_uids,
409                                                         delete_originals, ex);
410                 break;
411         }
412 }
413
414
415 /**
416  * camel_disco_folder_expunge_uids:
417  * @folder: a (disconnectable) folder
418  * @uids: array of UIDs to expunge
419  * @ex: a CamelException
420  *
421  * This expunges the messages in @uids from @folder. It should take
422  * whatever steps are needed to avoid expunging any other messages,
423  * although in some cases it may not be possible to avoid expunging
424  * messages that are marked deleted by another client at the same time
425  * as the expunge_uids call is running.
426  **/
427 void
428 camel_disco_folder_expunge_uids (CamelFolder *folder, GPtrArray *uids,
429                                  CamelException *ex)
430 {
431         disco_expunge_uids (folder, uids, ex);
432 }
433
434
435 static void
436 disco_cache_message (CamelDiscoFolder *disco_folder, const char *uid,
437                      CamelException *ex)
438 {
439         g_warning ("CamelDiscoFolder::cache_message not implemented for `%s'",
440                    camel_type_to_name (CAMEL_OBJECT_GET_TYPE (disco_folder)));
441 }
442
443 /**
444  * camel_disco_folder_cache_message:
445  * @disco_folder: the folder
446  * @uid: the UID of the message to cache
447  * @ex: a CamelException
448  *
449  * Requests that @disco_folder cache message @uid to disk.
450  **/
451 void
452 camel_disco_folder_cache_message (CamelDiscoFolder *disco_folder,
453                                   const char *uid, CamelException *ex)
454 {
455         CDF_CLASS (disco_folder)->cache_message (disco_folder, uid, ex);
456 }
457
458
459 static void
460 disco_prepare_for_offline (CamelDiscoFolder *disco_folder,
461                            const char *expression,
462                            CamelException *ex)
463 {
464         CamelFolder *folder = CAMEL_FOLDER (disco_folder);
465         GPtrArray *uids;
466         int i;
467
468         camel_operation_start(NULL, _("Preparing folder '%s' for offline"), folder->full_name);
469
470         if (expression)
471                 uids = camel_folder_search_by_expression (folder, expression, ex);
472         else
473                 uids = camel_folder_get_uids (folder);
474
475         if (!uids) {
476                 camel_operation_end(NULL);
477                 return;
478         }
479
480         for (i = 0; i < uids->len; i++) {
481                 int pc = i * 100 / uids->len;
482
483                 camel_disco_folder_cache_message (disco_folder, uids->pdata[i], ex);
484                 camel_operation_progress(NULL, pc);
485                 if (camel_exception_is_set (ex))
486                         break;
487         }
488
489         if (expression)
490                 camel_folder_search_free (folder, uids);
491         else
492                 camel_folder_free_uids (folder, uids);
493
494         camel_operation_end(NULL);
495 }
496
497 /**
498  * camel_disco_folder_prepare_for_offline:
499  * @disco_folder: the folder
500  * @expression: an expression describing messages to synchronize, or %NULL
501  * if all messages should be sync'ed.
502  * @ex: a CamelException
503  *
504  * This prepares @disco_folder for offline operation, by downloading
505  * the bodies of all messages described by @expression (using the
506  * same syntax as camel_folder_search_by_expression() ).
507  **/
508 void 
509 camel_disco_folder_prepare_for_offline (CamelDiscoFolder *disco_folder,
510                                         const char *expression,
511                                         CamelException *ex)
512 {
513         g_return_if_fail (CAMEL_IS_DISCO_FOLDER (disco_folder));
514
515         CDF_CLASS (disco_folder)->prepare_for_offline (disco_folder, expression, ex);
516 }