Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-vtrash-folder.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *           Michael Zucchi <notzed@ximian.com>
5  *
6  *  Copyright 2001 Ximian, Inc. (www.ximian.com)
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of version 2 of the GNU Lesser General Public
10  * License as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <string.h>
29
30 #include <glib.h>
31 #include <glib/gi18n-lib.h>
32
33 #include "camel-exception.h"
34 #include "camel-mime-message.h"
35 #include "camel-private.h"
36 #include "camel-store.h"
37 #include "camel-vee-store.h"
38 #include "camel-vtrash-folder.h"
39
40
41 /* Returns the class for a CamelFolder */
42 #define CF_CLASS(so) ((CamelFolderClass *)((CamelObject *)(so))->klass)
43
44 static struct {
45         const char *full_name;
46         const char *name;
47         const char *expr;
48         guint32 bit;
49         guint32 flags;
50         const char *error_copy;
51 } vdata[] = {
52         { CAMEL_VTRASH_NAME, N_("Trash"), "(match-all (system-flag \"Deleted\"))", CAMEL_MESSAGE_DELETED, CAMEL_FOLDER_IS_TRASH,
53           N_("Cannot copy messages to the Trash folder") },
54         { CAMEL_VJUNK_NAME, N_("Junk"), "(match-all (system-flag \"Junk\"))", CAMEL_MESSAGE_JUNK, CAMEL_FOLDER_IS_JUNK,
55           N_("Cannot copy messages to the Junk folder") },
56 };
57
58 static CamelVeeFolderClass *camel_vtrash_folder_parent;
59
60 static void camel_vtrash_folder_class_init (CamelVTrashFolderClass *klass);
61
62 static void
63 camel_vtrash_folder_init (CamelVTrashFolder *vtrash)
64 {
65         /*CamelFolder *folder = CAMEL_FOLDER (vtrash);*/
66 }
67
68 CamelType
69 camel_vtrash_folder_get_type (void)
70 {
71         static CamelType type = CAMEL_INVALID_TYPE;
72         
73         if (type == CAMEL_INVALID_TYPE) {
74                 type = camel_type_register (camel_vee_folder_get_type (),
75                                             "CamelVTrashFolder",
76                                             sizeof (CamelVTrashFolder),
77                                             sizeof (CamelVTrashFolderClass),
78                                             (CamelObjectClassInitFunc) camel_vtrash_folder_class_init,
79                                             NULL,
80                                             (CamelObjectInitFunc) camel_vtrash_folder_init,
81                                             NULL);
82         }
83         
84         return type;
85 }
86
87 /**
88  * camel_vtrash_folder_new:
89  * @parent_store: the parent #CamelVeeStore object
90  * @type: type of vfolder, #CAMEL_VTRASH_FOLDER_TRASH or #CAMEL_VTRASH_FOLDER_JUNK currently.
91  * @ex: a #CamelException
92  *
93  * Create a new CamelVTrashFolder object.
94  *
95  * Returns a new #CamelVTrashFolder object
96  **/
97 CamelFolder *
98 camel_vtrash_folder_new (CamelStore *parent_store, camel_vtrash_folder_t type)
99 {
100         CamelVTrashFolder *vtrash;
101         
102         g_assert(type < CAMEL_VTRASH_FOLDER_LAST);
103
104         vtrash = (CamelVTrashFolder *)camel_object_new(camel_vtrash_folder_get_type());
105         camel_vee_folder_construct(CAMEL_VEE_FOLDER (vtrash), parent_store, vdata[type].full_name, _(vdata[type].name),
106                                    CAMEL_STORE_FOLDER_PRIVATE|CAMEL_STORE_FOLDER_CREATE|CAMEL_STORE_VEE_FOLDER_AUTO);
107
108         ((CamelFolder *)vtrash)->folder_flags |= vdata[type].flags;
109         camel_vee_folder_set_expression((CamelVeeFolder *)vtrash, vdata[type].expr);
110         vtrash->bit = vdata[type].bit;
111         vtrash->type = type;
112
113         return (CamelFolder *)vtrash;
114 }
115
116 static int
117 vtrash_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
118 {
119         CamelFolder *folder = (CamelFolder *)object;
120         int i;
121         guint32 tag;
122         int unread = -1, deleted = 0, junked = 0, visible = 0, count = -1;
123
124         for (i=0;i<args->argc;i++) {
125                 CamelArgGet *arg = &args->argv[i];
126
127                 tag = arg->tag;
128
129                 /* NB: this is a copy of camel-folder.c with the unread count logic altered.
130                    makes sure its still atomically calculated */
131                 switch (tag & CAMEL_ARG_TAG) {
132                 case CAMEL_FOLDER_ARG_UNREAD:
133                 case CAMEL_FOLDER_ARG_DELETED:
134                 case CAMEL_FOLDER_ARG_JUNKED:
135                 case CAMEL_FOLDER_ARG_VISIBLE:
136                         /* This is so we can get the values atomically, and also so we can calculate them only once */
137                         if (unread == -1) {
138                                 int j;
139                                 CamelMessageInfo *info;
140
141                                 unread = 0;
142                                 count = camel_folder_summary_count(folder->summary);
143                                 for (j=0; j<count; j++) {
144                                         if ((info = camel_folder_summary_index(folder->summary, j))) {
145                                                 guint32 flags = camel_message_info_flags(info);
146
147                                                 if ((flags & (CAMEL_MESSAGE_SEEN)) == 0)
148                                                         unread++;
149                                                 if (flags & CAMEL_MESSAGE_DELETED)
150                                                         deleted++;
151                                                 if (flags & CAMEL_MESSAGE_JUNK)
152                                                         junked++;
153                                                 if ((flags & (CAMEL_MESSAGE_DELETED|CAMEL_MESSAGE_JUNK)) == 0)
154                                                         visible++;
155                                                 camel_message_info_free(info);
156                                         }
157                                 }
158                         }
159
160                         switch (tag & CAMEL_ARG_TAG) {
161                         case CAMEL_FOLDER_ARG_UNREAD:
162                                 count = unread;
163                                 break;
164                         case CAMEL_FOLDER_ARG_DELETED:
165                                 count = deleted;
166                                 break;
167                         case CAMEL_FOLDER_ARG_JUNKED:
168                                 count = junked;
169                                 break;
170                         case CAMEL_FOLDER_ARG_VISIBLE:
171                                 count = visible;
172                                 break;
173                         }
174
175                         *arg->ca_int = count;
176                         break;
177                 default:
178                         continue;
179                 }
180
181                 arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
182         }
183
184         return ((CamelObjectClass *)camel_vtrash_folder_parent)->getv(object, ex, args);
185 }
186
187 static void
188 vtrash_append_message (CamelFolder *folder, CamelMimeMessage *message,
189                        const CamelMessageInfo *info, char **appended_uid,
190                        CamelException *ex)
191 {
192         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, 
193                              _(vdata[((CamelVTrashFolder *)folder)->type].error_copy));
194 }
195
196 struct _transfer_data {
197         CamelFolder *folder;
198         CamelFolder *dest;
199         GPtrArray *uids;
200         gboolean delete;
201 };
202
203 static void
204 transfer_messages(CamelFolder *folder, struct _transfer_data *md, CamelException *ex)
205 {
206         int i;
207
208         if (!camel_exception_is_set (ex))
209                 camel_folder_transfer_messages_to(md->folder, md->uids, md->dest, NULL, md->delete, ex);
210
211         for (i=0;i<md->uids->len;i++)
212                 g_free(md->uids->pdata[i]);
213         g_ptr_array_free(md->uids, TRUE);
214         camel_object_unref((CamelObject *)md->folder);
215         g_free(md);
216 }
217
218 static void
219 vtrash_transfer_messages_to (CamelFolder *source, GPtrArray *uids,
220                              CamelFolder *dest, GPtrArray **transferred_uids,
221                              gboolean delete_originals, CamelException *ex)
222 {
223         CamelVeeMessageInfo *mi;
224         int i;
225         GHashTable *batch = NULL;
226         const char *tuid;
227         struct _transfer_data *md;
228         guint32 sbit = ((CamelVTrashFolder *)source)->bit;
229
230         /* This is a special case of transfer_messages_to: Either the
231          * source or the destination is a vtrash folder (but not both
232          * since a store should never have more than one).
233          */
234
235         if (transferred_uids)
236                 *transferred_uids = NULL;
237
238         if (CAMEL_IS_VTRASH_FOLDER (dest)) {
239                 /* Copy to trash is meaningless. */
240                 if (!delete_originals) {
241                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, 
242                                              _(vdata[((CamelVTrashFolder *)dest)->type].error_copy));
243                         return;
244                 }
245
246                 /* Move to trash is the same as setting the message flag */
247                 for (i = 0; i < uids->len; i++)
248                         camel_folder_set_message_flags(source, uids->pdata[i], ((CamelVTrashFolder *)dest)->bit, ~0);
249                 return;
250         }
251
252         /* Moving/Copying from the trash to the original folder = undelete.
253          * Moving/Copying from the trash to a different folder = move/copy.
254          *
255          * Need to check this uid by uid, but we batch up the copies.
256          */
257
258         for (i = 0; i < uids->len; i++) {
259                 mi = (CamelVeeMessageInfo *)camel_folder_get_message_info (source, uids->pdata[i]);
260                 if (mi == NULL) {
261                         g_warning ("Cannot find uid %s in source folder during transfer", (char *) uids->pdata[i]);
262                         continue;
263                 }
264                 
265                 if (dest == mi->real->summary->folder) {
266                         /* Just unset the flag on the original message */
267                         camel_folder_set_message_flags (source, uids->pdata[i], sbit, 0);
268                 } else {
269                         if (batch == NULL)
270                                 batch = g_hash_table_new(NULL, NULL);
271                         md = g_hash_table_lookup(batch, mi->real->summary->folder);
272                         if (md == NULL) {
273                                 md = g_malloc0(sizeof(*md));
274                                 md->folder = mi->real->summary->folder;
275                                 camel_object_ref((CamelObject *)md->folder);
276                                 md->uids = g_ptr_array_new();
277                                 md->dest = dest;
278                                 g_hash_table_insert(batch, mi->real->summary->folder, md);
279                         }
280
281                         tuid = uids->pdata[i];
282                         if (strlen(tuid)>8)
283                                 tuid += 8;
284                         g_ptr_array_add(md->uids, g_strdup(tuid));
285                 }
286                 camel_folder_free_message_info (source, (CamelMessageInfo *)mi);
287         }
288
289         if (batch) {
290                 g_hash_table_foreach(batch, (GHFunc)transfer_messages, ex);
291                 g_hash_table_destroy(batch);
292         }
293 }
294
295 static GPtrArray *
296 vtrash_search_by_expression(CamelFolder *folder, const char *expression, CamelException *ex)
297 {
298         GList *node;
299         GPtrArray *matches, *result = g_ptr_array_new(), *uids = g_ptr_array_new();
300         struct _CamelVeeFolderPrivate *p = ((CamelVeeFolder *)folder)->priv;
301
302         /* we optimise the search by only searching for messages which we have anyway */
303         CAMEL_VEE_FOLDER_LOCK(folder, subfolder_lock);
304         node = p->folders;
305         while (node) {
306                 CamelFolder *f = node->data;
307                 int i;
308                 char hash[8];
309                 GPtrArray *infos = camel_folder_get_summary(f);
310
311                 camel_vee_folder_hash_folder(f, hash);
312
313                 for (i=0;i<infos->len;i++) {
314                         CamelMessageInfo *mi = infos->pdata[i];
315
316                         if (camel_message_info_flags(mi) & ((CamelVTrashFolder *)folder)->bit)
317                                 g_ptr_array_add(uids, (void *)camel_message_info_uid(mi));
318                 }
319
320                 if (uids->len > 0
321                     && (matches = camel_folder_search_by_uids(f, expression, uids, NULL))) {
322                         for (i = 0; i < matches->len; i++) {
323                                 char *uid = matches->pdata[i], *vuid;
324
325                                 vuid = g_malloc(strlen(uid)+9);
326                                 memcpy(vuid, hash, 8);
327                                 strcpy(vuid+8, uid);
328                                 g_ptr_array_add(result, vuid);
329                         }
330                         camel_folder_search_free(f, matches);
331                 }
332                 g_ptr_array_set_size(uids, 0);
333                 camel_folder_free_summary(f, infos);
334
335                 node = g_list_next(node);
336         }
337         CAMEL_VEE_FOLDER_UNLOCK(folder, subfolder_lock);
338
339         g_ptr_array_free(uids, TRUE);
340
341         return result;
342 }
343
344 static GPtrArray *
345 vtrash_search_by_uids(CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex)
346 {
347         GList *node;
348         GPtrArray *matches, *result = g_ptr_array_new(), *folder_uids = g_ptr_array_new();
349         struct _CamelVeeFolderPrivate *p = ((CamelVeeFolder *)folder)->priv;
350         
351         CAMEL_VEE_FOLDER_LOCK(folder, subfolder_lock);
352
353         node = p->folders;
354         while (node) {
355                 CamelFolder *f = node->data;
356                 int i;
357                 char hash[8];
358                 
359                 camel_vee_folder_hash_folder(f, hash);
360
361                 /* map the vfolder uid's to the source folder uid's first */
362                 g_ptr_array_set_size(uids, 0);
363                 for (i=0;i<uids->len;i++) {
364                         char *uid = uids->pdata[i];
365                         
366                         if (strlen(uid) >= 8 && strncmp(uid, hash, 8) == 0) {
367                                 CamelMessageInfo *mi;
368
369                                 mi = camel_folder_get_message_info(f, uid+8);
370                                 if (mi) {
371                                         if(camel_message_info_flags(mi) & ((CamelVTrashFolder *)folder)->bit)
372                                                 g_ptr_array_add(folder_uids, uid+8);
373                                         camel_folder_free_message_info(f, mi);
374                                 }
375                         }
376                 }
377
378                 if (folder_uids->len > 0
379                     && (matches = camel_folder_search_by_uids(f, expression, folder_uids, ex))) {
380                         for (i = 0; i < matches->len; i++) {
381                                 char *uid = matches->pdata[i], *vuid;
382                                 
383                                 vuid = g_malloc(strlen(uid)+9);
384                                 memcpy(vuid, hash, 8);
385                                 strcpy(vuid+8, uid);
386                                 g_ptr_array_add(result, vuid);
387                         }
388                         camel_folder_search_free(f, matches);
389                 }
390                 node = g_list_next(node);
391         }
392
393         CAMEL_VEE_FOLDER_UNLOCK(folder, subfolder_lock);
394
395         g_ptr_array_free(folder_uids, TRUE);
396
397         return result;
398 }
399
400 static void
401 vtrash_uid_removed(CamelVTrashFolder *vf, const char *uid, char hash[8])
402 {
403         char *vuid;
404         CamelVeeMessageInfo *vinfo;
405
406         vuid = g_alloca(strlen(uid)+9);
407         memcpy(vuid, hash, 8);
408         strcpy(vuid+8, uid);
409         vinfo = (CamelVeeMessageInfo *)camel_folder_summary_uid(((CamelFolder *)vf)->summary, vuid);
410         if (vinfo) {
411                 camel_folder_change_info_remove_uid(((CamelVeeFolder *)vf)->changes, vuid);
412                 camel_folder_summary_remove(((CamelFolder *)vf)->summary, (CamelMessageInfo *)vinfo);
413                 camel_message_info_free(vinfo);
414         }
415 }
416
417 static void
418 vtrash_uid_added(CamelVTrashFolder *vf, const char *uid, CamelMessageInfo *info, char hash[8])
419 {
420         char *vuid;
421         CamelVeeMessageInfo *vinfo;
422
423         vuid = g_alloca(strlen(uid)+9);
424         memcpy(vuid, hash, 8);
425         strcpy(vuid+8, uid);
426         vinfo = (CamelVeeMessageInfo *)camel_folder_summary_uid(((CamelFolder *)vf)->summary, vuid);
427         if (vinfo == NULL) {
428                 camel_vee_summary_add((CamelVeeSummary *)((CamelFolder *)vf)->summary, info, hash);
429                 camel_folder_change_info_add_uid(((CamelVeeFolder *)vf)->changes, vuid);
430         } else {
431                 camel_folder_change_info_change_uid(((CamelVeeFolder *)vf)->changes, vuid);
432                 camel_message_info_free(vinfo);
433         }
434 }
435
436 static void
437 vtrash_folder_changed(CamelVeeFolder *vf, CamelFolder *sub, CamelFolderChangeInfo *changes)
438 {
439         CamelMessageInfo *info;
440         char hash[8];
441         CamelFolderChangeInfo *vf_changes = NULL;
442         int i;
443
444         camel_vee_folder_hash_folder(sub, hash);
445
446         CAMEL_VEE_FOLDER_LOCK(vf, summary_lock);
447
448         /* remove any removed that we also have */
449         for (i=0;i<changes->uid_removed->len;i++)
450                 vtrash_uid_removed((CamelVTrashFolder *)vf, (const char *)changes->uid_removed->pdata[i], hash);
451
452         /* check any changed still deleted/junked */
453         for (i=0;i<changes->uid_changed->len;i++) {
454                 const char *uid = changes->uid_changed->pdata[i];
455
456                 info = camel_folder_get_message_info(sub, uid);
457                 if (info == NULL)
458                         continue;
459
460                 if ((camel_message_info_flags(info) & ((CamelVTrashFolder *)vf)->bit) == 0)
461                         vtrash_uid_removed((CamelVTrashFolder *)vf, uid, hash);
462                 else
463                         vtrash_uid_added((CamelVTrashFolder *)vf, uid, info, hash);
464
465                 camel_message_info_free(info);
466         }
467
468         /* add any new ones which are already matching */
469         for (i=0;i<changes->uid_added->len;i++) {
470                 const char *uid = changes->uid_added->pdata[i];
471
472                 info = camel_folder_get_message_info(sub, uid);
473                 if (info == NULL)
474                         continue;
475
476                 if ((camel_message_info_flags(info) & ((CamelVTrashFolder *)vf)->bit) != 0)
477                         vtrash_uid_added((CamelVTrashFolder *)vf, uid, info, hash);
478
479                 camel_message_info_free(info);
480         }
481
482         if (camel_folder_change_info_changed(((CamelVeeFolder *)vf)->changes)) {
483                 vf_changes = ((CamelVeeFolder *)vf)->changes;
484                 ((CamelVeeFolder *)vf)->changes = camel_folder_change_info_new();
485         }
486
487         CAMEL_VEE_FOLDER_UNLOCK(vf, summary_lock);
488         
489         if (vf_changes) {
490                 camel_object_trigger_event(vf, "folder_changed", vf_changes);
491                 camel_folder_change_info_free(vf_changes);
492         }
493 }
494
495 static void
496 vtrash_add_folder(CamelVeeFolder *vf, CamelFolder *sub)
497 {
498         GPtrArray *infos;
499         int i;
500         char hash[8];
501         CamelFolderChangeInfo *vf_changes = NULL;
502
503         camel_vee_folder_hash_folder(sub, hash);
504
505         CAMEL_VEE_FOLDER_LOCK(vf, summary_lock);
506
507         infos = camel_folder_get_summary(sub);
508         for (i=0;i<infos->len;i++) {
509                 CamelMessageInfo *info = infos->pdata[i];
510
511                 if ((camel_message_info_flags(info) & ((CamelVTrashFolder *)vf)->bit))
512                         vtrash_uid_added((CamelVTrashFolder *)vf, camel_message_info_uid(info), info, hash);
513         }
514         camel_folder_free_summary(sub, infos);
515
516         if (camel_folder_change_info_changed(vf->changes)) {
517                 vf_changes = vf->changes;
518                 vf->changes = camel_folder_change_info_new();
519         }
520
521         CAMEL_VEE_FOLDER_UNLOCK(vf, summary_lock);
522
523         if (vf_changes) {
524                 camel_object_trigger_event(vf, "folder_changed", vf_changes);
525                 camel_folder_change_info_free(vf_changes);
526         }
527 }
528
529 static void
530 vtrash_remove_folder(CamelVeeFolder *vf, CamelFolder *sub)
531 {
532         GPtrArray *infos;
533         int i;
534         char hash[8];
535         CamelFolderChangeInfo *vf_changes = NULL;
536         CamelFolderSummary *ssummary = sub->summary;
537         int start, last;
538
539         camel_vee_folder_hash_folder(sub, hash);
540
541         CAMEL_VEE_FOLDER_LOCK(vf, summary_lock);
542
543         start = -1;
544         last = -1;
545         infos = camel_folder_get_summary(sub);
546         for (i=0;i<infos->len;i++) {
547                 CamelVeeMessageInfo *mi = infos->pdata[i];
548
549                 if (mi == NULL || mi->real == NULL)
550                         continue;
551
552                 if (mi->real->summary == ssummary) {
553                         const char *uid = camel_message_info_uid(mi);
554
555                         camel_folder_change_info_remove_uid(vf->changes, uid);
556
557                         if (last == -1) {
558                                 last = start = i;
559                         } else if (last+1 == i) {
560                                 last = i;
561                         } else {
562                                 camel_folder_summary_remove_range(((CamelFolder *)vf)->summary, start, last);
563                                 i -= (last-start)+1;
564                                 start = last = i;
565                         }
566                 }
567         }
568         camel_folder_free_summary(sub, infos);
569
570         if (last != -1)
571                 camel_folder_summary_remove_range(((CamelFolder *)vf)->summary, start, last);
572
573         if (camel_folder_change_info_changed(vf->changes)) {
574                 vf_changes = vf->changes;
575                 vf->changes = camel_folder_change_info_new();
576         }
577
578         CAMEL_VEE_FOLDER_UNLOCK(vf, summary_lock);
579
580         if (vf_changes) {
581                 camel_object_trigger_event(vf, "folder_changed", vf_changes);
582                 camel_folder_change_info_free(vf_changes);
583         }
584 }
585
586 static int
587 vtrash_rebuild_folder(CamelVeeFolder *vf, CamelFolder *source, CamelException *ex)
588 {
589         /* we should always be in sync */
590         return 0;
591 }
592
593 static void
594 camel_vtrash_folder_class_init (CamelVTrashFolderClass *klass)
595 {
596         CamelFolderClass *folder_class = (CamelFolderClass *) klass;
597         
598         camel_vtrash_folder_parent = CAMEL_VEE_FOLDER_CLASS(camel_vee_folder_get_type());
599
600         ((CamelObjectClass *)klass)->getv = vtrash_getv;
601         
602         folder_class->append_message = vtrash_append_message;
603         folder_class->transfer_messages_to = vtrash_transfer_messages_to;
604         folder_class->search_by_expression = vtrash_search_by_expression;
605         folder_class->search_by_uids = vtrash_search_by_uids;
606
607         ((CamelVeeFolderClass *)klass)->add_folder = vtrash_add_folder;
608         ((CamelVeeFolderClass *)klass)->remove_folder = vtrash_remove_folder;
609         ((CamelVeeFolderClass *)klass)->rebuild_folder = vtrash_rebuild_folder;
610
611         ((CamelVeeFolderClass *)klass)->folder_changed = vtrash_folder_changed;
612 }