2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2004 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31 #include "procheader.h"
32 #include "send_message.h"
34 #include "statusbar.h"
35 #include "prefs_filtering.h"
36 #include "filtering.h"
38 #include "prefs_common.h"
40 #include "alertpanel.h"
44 #include "partial_download.h"
46 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
48 GHashTable *msg_table;
50 if (mlist == NULL) return NULL;
52 msg_table = g_hash_table_new(NULL, g_direct_equal);
53 procmsg_msg_hash_table_append(msg_table, mlist);
58 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
63 if (msg_table == NULL || mlist == NULL) return;
65 for (cur = mlist; cur != NULL; cur = cur->next) {
66 msginfo = (MsgInfo *)cur->data;
68 g_hash_table_insert(msg_table,
69 GUINT_TO_POINTER(msginfo->msgnum),
74 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
76 GHashTable *msg_table;
80 if (mlist == NULL) return NULL;
82 msg_table = g_hash_table_new(NULL, g_direct_equal);
84 for (cur = mlist; cur != NULL; cur = cur->next) {
85 msginfo = (MsgInfo *)cur->data;
86 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
92 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
98 for (cur = mlist; cur != NULL; cur = cur->next) {
99 msginfo = (MsgInfo *)cur->data;
100 if (msginfo && msginfo->msgnum > last)
101 last = msginfo->msgnum;
107 void procmsg_msg_list_free(GSList *mlist)
112 for (cur = mlist; cur != NULL; cur = cur->next) {
113 msginfo = (MsgInfo *)cur->data;
114 procmsg_msginfo_free(msginfo);
128 /* CLAWS subject threading:
130 in the first round it inserts subject lines in a
131 relation (subject <-> node)
133 the second round finishes the threads by attaching
134 matching subject lines to the one found in the
135 relation. will use the oldest node with the same
136 subject that is not more then thread_by_subject_max_age
137 days old (see subject_relation_lookup)
140 static void subject_relation_insert(GRelation *relation, GNode *node)
145 g_return_if_fail(relation != NULL);
146 g_return_if_fail(node != NULL);
147 msginfo = (MsgInfo *) node->data;
148 g_return_if_fail(msginfo != NULL);
150 subject = msginfo->subject;
153 subject += subject_get_prefix_length(subject);
155 g_relation_insert(relation, subject, node);
158 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
165 g_return_val_if_fail(relation != NULL, NULL);
167 subject = msginfo->subject;
170 prefix_length = subject_get_prefix_length(subject);
171 if (prefix_length <= 0)
173 subject += prefix_length;
175 tuples = g_relation_select(relation, subject, 0);
179 if (tuples->len > 0) {
181 GNode *relation_node;
182 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
185 /* check all nodes with the same subject to find the best parent */
186 for (i = 0; i < tuples->len; i++) {
187 relation_node = (GNode *) g_tuples_index(tuples, i, 1);
188 relation_msginfo = (MsgInfo *) relation_node->data;
191 /* best node should be the oldest in the found nodes */
192 /* parent node must not be older then msginfo */
193 if ((relation_msginfo->date_t < msginfo->date_t) &&
194 ((best_msginfo == NULL) ||
195 (best_msginfo->date_t > relation_msginfo->date_t)))
198 /* parent node must not be more then thread_by_subject_max_age
199 days older then msginfo */
200 if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
201 prefs_common.thread_by_subject_max_age * 3600 * 24)
204 /* can add new tests for all matching
205 nodes found by subject */
208 node = relation_node;
209 best_msginfo = relation_msginfo;
214 g_tuples_destroy(tuples);
218 /* return the reversed thread tree */
219 GNode *procmsg_get_thread_tree(GSList *mlist)
221 GNode *root, *parent, *node, *next;
222 GHashTable *msgid_table;
223 GRelation *subject_relation;
227 root = g_node_new(NULL);
228 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
229 subject_relation = g_relation_new(2);
230 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
232 for (; mlist != NULL; mlist = mlist->next) {
233 msginfo = (MsgInfo *)mlist->data;
236 if (msginfo->inreplyto) {
237 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
238 if (parent == NULL) {
242 node = g_node_insert_data_before
243 (parent, parent == root ? parent->children : NULL,
245 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
246 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
248 /* CLAWS: add subject to relation (without prefix) */
249 if (prefs_common.thread_by_subject) {
250 subject_relation_insert(subject_relation, node);
254 /* complete the unfinished threads */
255 for (node = root->children; node != NULL; ) {
258 msginfo = (MsgInfo *)node->data;
259 if (msginfo->inreplyto) {
260 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
261 /* node should not be the parent, and node should not
262 be an ancestor of parent (circular reference) */
263 if (parent && parent != node &&
264 !g_node_is_ancestor(node, parent)) {
267 (parent, parent->children, node);
273 if (prefs_common.thread_by_subject) {
274 for (node = root->children; node && node != NULL;) {
276 msginfo = (MsgInfo *) node->data;
278 parent = subject_relation_lookup(subject_relation, msginfo);
280 /* the node may already be threaded by IN-REPLY-TO, so go up
282 find the parent node */
283 if (parent != NULL) {
284 if (g_node_is_ancestor(node, parent))
292 g_node_append(parent, node);
299 g_relation_destroy(subject_relation);
300 g_hash_table_destroy(msgid_table);
305 void procmsg_move_messages(GSList *mlist)
307 GSList *cur, *movelist = NULL;
309 FolderItem *dest = NULL;
313 folder_item_update_freeze();
315 for (cur = mlist; cur != NULL; cur = cur->next) {
316 msginfo = (MsgInfo *)cur->data;
318 dest = msginfo->to_folder;
319 movelist = g_slist_append(movelist, msginfo);
320 } else if (dest == msginfo->to_folder) {
321 movelist = g_slist_append(movelist, msginfo);
323 folder_item_move_msgs(dest, movelist);
324 g_slist_free(movelist);
326 dest = msginfo->to_folder;
327 movelist = g_slist_append(movelist, msginfo);
329 procmsg_msginfo_set_to_folder(msginfo, NULL);
333 folder_item_move_msgs(dest, movelist);
334 g_slist_free(movelist);
337 folder_item_update_thaw();
340 void procmsg_copy_messages(GSList *mlist)
342 GSList *cur, *copylist = NULL;
344 FolderItem *dest = NULL;
348 folder_item_update_freeze();
350 for (cur = mlist; cur != NULL; cur = cur->next) {
351 msginfo = (MsgInfo *)cur->data;
353 dest = msginfo->to_folder;
354 copylist = g_slist_append(copylist, msginfo);
355 } else if (dest == msginfo->to_folder) {
356 copylist = g_slist_append(copylist, msginfo);
358 folder_item_copy_msgs(dest, copylist);
359 g_slist_free(copylist);
361 dest = msginfo->to_folder;
362 copylist = g_slist_append(copylist, msginfo);
364 procmsg_msginfo_set_to_folder(msginfo, NULL);
368 folder_item_copy_msgs(dest, copylist);
369 g_slist_free(copylist);
372 folder_item_update_thaw();
375 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
379 g_return_val_if_fail(msginfo != NULL, NULL);
381 if (msginfo->plaintext_file)
382 file = g_strdup(msginfo->plaintext_file);
384 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
390 gchar *procmsg_get_message_file(MsgInfo *msginfo)
392 gchar *filename = NULL;
394 g_return_val_if_fail(msginfo != NULL, NULL);
396 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
398 debug_print("can't fetch message %d\n", msginfo->msgnum);
403 GSList *procmsg_get_message_file_list(GSList *mlist)
405 GSList *file_list = NULL;
407 MsgFileInfo *fileinfo;
410 while (mlist != NULL) {
411 msginfo = (MsgInfo *)mlist->data;
412 file = procmsg_get_message_file(msginfo);
414 procmsg_message_file_list_free(file_list);
417 fileinfo = g_new(MsgFileInfo, 1);
418 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
419 fileinfo->file = file;
420 fileinfo->flags = g_new(MsgFlags, 1);
421 *fileinfo->flags = msginfo->flags;
422 file_list = g_slist_prepend(file_list, fileinfo);
426 file_list = g_slist_reverse(file_list);
431 void procmsg_message_file_list_free(MsgInfoList *file_list)
434 MsgFileInfo *fileinfo;
436 for (cur = file_list; cur != NULL; cur = cur->next) {
437 fileinfo = (MsgFileInfo *)cur->data;
438 procmsg_msginfo_free(fileinfo->msginfo);
439 g_free(fileinfo->file);
440 g_free(fileinfo->flags);
444 g_slist_free(file_list);
447 FILE *procmsg_open_message(MsgInfo *msginfo)
452 g_return_val_if_fail(msginfo != NULL, NULL);
454 file = procmsg_get_message_file_path(msginfo);
455 g_return_val_if_fail(file != NULL, NULL);
457 if (!is_file_exist(file)) {
459 file = procmsg_get_message_file(msginfo);
464 if ((fp = fopen(file, "rb")) == NULL) {
465 FILE_OP_ERROR(file, "fopen");
472 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
475 while (fgets(buf, sizeof(buf), fp) != NULL)
476 if (buf[0] == '\r' || buf[0] == '\n') break;
482 gboolean procmsg_msg_exist(MsgInfo *msginfo)
487 if (!msginfo) return FALSE;
489 path = folder_item_get_path(msginfo->folder);
491 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
497 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
498 PrefsFilterType type)
500 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
501 {"X-ML-Name:", NULL, TRUE},
502 {"X-List:", NULL, TRUE},
503 {"X-Mailing-list:", NULL, TRUE},
504 {"List-Id:", NULL, TRUE},
505 {"X-Sequence:", NULL, TRUE},
506 {NULL, NULL, FALSE}};
512 H_X_MAILING_LIST = 3,
519 g_return_if_fail(msginfo != NULL);
520 g_return_if_fail(header != NULL);
521 g_return_if_fail(key != NULL);
530 if ((fp = procmsg_open_message(msginfo)) == NULL)
532 procheader_get_header_fields(fp, hentry);
535 #define SET_FILTER_KEY(hstr, idx) \
537 *header = g_strdup(hstr); \
538 *key = hentry[idx].body; \
539 hentry[idx].body = NULL; \
542 if (hentry[H_X_BEENTHERE].body != NULL) {
543 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
544 } else if (hentry[H_X_ML_NAME].body != NULL) {
545 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
546 } else if (hentry[H_X_LIST].body != NULL) {
547 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
548 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
549 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
550 } else if (hentry[H_LIST_ID].body != NULL) {
551 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
552 extract_list_id_str(*key);
553 } else if (hentry[H_X_SEQUENCE].body != NULL) {
556 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
559 while (*p != '\0' && !isspace(*p)) p++;
560 while (isspace(*p)) p++;
567 } else if (msginfo->subject) {
568 *header = g_strdup("subject");
569 *key = g_strdup(msginfo->subject);
572 #undef SET_FILTER_KEY
574 g_free(hentry[H_X_BEENTHERE].body);
575 hentry[H_X_BEENTHERE].body = NULL;
576 g_free(hentry[H_X_ML_NAME].body);
577 hentry[H_X_ML_NAME].body = NULL;
578 g_free(hentry[H_X_LIST].body);
579 hentry[H_X_LIST].body = NULL;
580 g_free(hentry[H_X_MAILING_LIST].body);
581 hentry[H_X_MAILING_LIST].body = NULL;
582 g_free(hentry[H_LIST_ID].body);
583 hentry[H_LIST_ID].body = NULL;
587 *header = g_strdup("from");
588 *key = g_strdup(msginfo->from);
591 *header = g_strdup("to");
592 *key = g_strdup(msginfo->to);
594 case FILTER_BY_SUBJECT:
595 *header = g_strdup("subject");
596 *key = g_strdup(msginfo->subject);
603 void procmsg_empty_trash(void)
608 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
609 trash = FOLDER(cur->data)->trash;
610 if (trash && trash->total_msgs > 0) {
611 GSList *mlist = folder_item_get_msg_list(trash);
613 for (cur = mlist ; cur != NULL ; cur = cur->next) {
614 MsgInfo * msginfo = (MsgInfo *) cur->data;
615 partial_mark_for_delete(msginfo);
616 procmsg_msginfo_free(msginfo);
619 folder_item_remove_all_msg(trash);
625 *\brief Send messages in queue
627 *\param queue Queue folder to process
628 *\param save_msgs Unused
630 *\return Number of messages sent, negative if an error occurred
631 * positive if no error occurred
633 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
635 gint ret = 1, count = 0;
639 queue = folder_get_default_queue();
640 g_return_val_if_fail(queue != NULL, -1);
642 folder_item_scan(queue);
643 list = folder_item_get_msg_list(queue);
645 for (elem = list; elem != NULL; elem = elem->next) {
649 msginfo = (MsgInfo *)(elem->data);
650 if (!MSG_IS_LOCKED(msginfo->flags)) {
651 file = folder_item_fetch_msg(queue, msginfo->msgnum);
653 if (procmsg_send_message_queue(file) < 0) {
654 g_warning("Sending queued message %d failed.\n",
659 * We save in procmsg_send_message_queue because
660 * we need the destination folder from the queue
664 procmsg_save_to_outbox
665 (queue->folder->outbox,
669 folder_item_remove_msg(queue, msginfo->msgnum);
674 /* FIXME: supposedly if only one message is locked, and queue
675 * is being flushed, the following free says something like
676 * "freeing msg ## in folder (nil)". */
677 procmsg_msginfo_free(msginfo);
683 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
688 if ((fp = fopen(in, "rb")) == NULL) {
689 FILE_OP_ERROR(in, "fopen");
692 if ((outfp = fopen(out, "wb")) == NULL) {
693 FILE_OP_ERROR(out, "fopen");
697 while (fgets(buf, sizeof(buf), fp) != NULL)
698 if (buf[0] == '\r' || buf[0] == '\n') break;
699 while (fgets(buf, sizeof(buf), fp) != NULL)
706 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
710 MsgInfo *msginfo, *tmp_msginfo;
711 MsgFlags flag = {0, 0};
713 debug_print("saving sent message...\n");
716 outbox = folder_get_default_outbox();
717 g_return_val_if_fail(outbox != NULL, -1);
719 /* remove queueing headers */
721 gchar tmp[MAXPATHLEN + 1];
723 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
724 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
726 if (procmsg_remove_special_headers(file, tmp) !=0)
729 folder_item_scan(outbox);
730 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
731 g_warning("can't save message\n");
736 folder_item_scan(outbox);
737 if ((num = folder_item_add_msg
738 (outbox, file, &flag, FALSE)) < 0) {
739 g_warning("can't save message\n");
744 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
745 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
746 if (msginfo != NULL) {
747 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
748 procmsg_msginfo_free(msginfo); /* refcnt-- */
749 /* tmp_msginfo == msginfo */
750 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
751 msginfo->returnreceiptto)) {
752 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
753 procmsg_msginfo_free(msginfo); /* refcnt-- */
760 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
762 static const gchar *def_cmd = "lpr %s";
769 g_return_if_fail(msginfo);
771 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
772 g_warning("Can't get text part\n");
776 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
777 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
779 if ((prfp = fopen(prtmp, "wb")) == NULL) {
780 FILE_OP_ERROR(prtmp, "fopen");
786 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
787 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
788 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
789 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
790 if (msginfo->newsgroups)
791 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
792 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
795 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
801 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
803 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
806 g_warning("Print command line is invalid: `%s'\n",
808 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
814 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
818 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
825 MsgInfo *procmsg_msginfo_new(void)
829 newmsginfo = g_new0(MsgInfo, 1);
830 newmsginfo->refcnt = 1;
835 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
839 if (msginfo == NULL) return NULL;
841 newmsginfo = g_new0(MsgInfo, 1);
843 newmsginfo->refcnt = 1;
845 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
846 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
847 g_strdup(msginfo->mmb) : NULL
872 MEMBDUP(dispositionnotificationto);
873 MEMBDUP(returnreceiptto);
877 MEMBCOPY(threadscore);
878 MEMBDUP(plaintext_file);
883 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
885 MsgInfo *full_msginfo;
888 if (msginfo == NULL) return NULL;
890 file = procmsg_get_message_file(msginfo);
892 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
896 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
898 if (!full_msginfo) return NULL;
900 /* CLAWS: make sure we add the missing members; see:
901 * procheader.c::procheader_get_headernames() */
903 msginfo->xface = g_strdup(full_msginfo->xface);
904 if (!msginfo->dispositionnotificationto)
905 msginfo->dispositionnotificationto =
906 g_strdup(full_msginfo->dispositionnotificationto);
907 if (!msginfo->returnreceiptto)
908 msginfo->returnreceiptto = g_strdup
909 (full_msginfo->returnreceiptto);
910 if (!msginfo->partial_recv && full_msginfo->partial_recv)
911 msginfo->partial_recv = g_strdup
912 (full_msginfo->partial_recv);
913 msginfo->total_size = full_msginfo->total_size;
914 if (!msginfo->account_server && full_msginfo->account_server)
915 msginfo->account_server = g_strdup
916 (full_msginfo->account_server);
917 if (!msginfo->account_login && full_msginfo->account_login)
918 msginfo->account_login = g_strdup
919 (full_msginfo->account_login);
920 msginfo->planned_download = full_msginfo->planned_download;
921 procmsg_msginfo_free(full_msginfo);
923 return procmsg_msginfo_new_ref(msginfo);
926 void procmsg_msginfo_free(MsgInfo *msginfo)
928 if (msginfo == NULL) return;
931 if (msginfo->refcnt > 0)
934 if (msginfo->to_folder) {
935 msginfo->to_folder->op_count--;
936 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
939 g_free(msginfo->fromspace);
940 g_free(msginfo->references);
941 g_free(msginfo->returnreceiptto);
942 g_free(msginfo->dispositionnotificationto);
943 g_free(msginfo->xface);
945 g_free(msginfo->fromname);
947 g_free(msginfo->date);
948 g_free(msginfo->from);
951 g_free(msginfo->newsgroups);
952 g_free(msginfo->subject);
953 g_free(msginfo->msgid);
954 g_free(msginfo->inreplyto);
955 g_free(msginfo->xref);
957 g_free(msginfo->partial_recv);
958 g_free(msginfo->account_server);
959 g_free(msginfo->account_login);
961 g_free(msginfo->plaintext_file);
966 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
970 memusage += sizeof(MsgInfo);
971 if (msginfo->fromname)
972 memusage += strlen(msginfo->fromname);
974 memusage += strlen(msginfo->date);
976 memusage += strlen(msginfo->from);
978 memusage += strlen(msginfo->to);
980 memusage += strlen(msginfo->cc);
981 if (msginfo->newsgroups)
982 memusage += strlen(msginfo->newsgroups);
983 if (msginfo->subject)
984 memusage += strlen(msginfo->subject);
986 memusage += strlen(msginfo->msgid);
987 if (msginfo->inreplyto)
988 memusage += strlen(msginfo->inreplyto);
990 memusage += strlen(msginfo->xface);
991 if (msginfo->dispositionnotificationto)
992 memusage += strlen(msginfo->dispositionnotificationto);
993 if (msginfo->returnreceiptto)
994 memusage += strlen(msginfo->returnreceiptto);
995 if (msginfo->references)
996 memusage += strlen(msginfo->references);
997 if (msginfo->fromspace)
998 memusage += strlen(msginfo->fromspace);
1003 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1005 const MsgInfo *msginfo1 = a;
1006 const MsgInfo *msginfo2 = b;
1013 return msginfo1->msgnum - msginfo2->msgnum;
1022 Q_MAIL_ACCOUNT_ID = 4,
1023 Q_NEWS_ACCOUNT_ID = 5,
1024 Q_SAVE_COPY_FOLDER = 6,
1025 Q_REPLY_MESSAGE_ID = 7,
1026 Q_FWD_MESSAGE_ID = 8,
1027 Q_PRIVACY_SYSTEM = 9,
1029 Q_ENCRYPT_DATA = 11,
1032 gint procmsg_send_message_queue(const gchar *file)
1034 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1035 {"SSV:", NULL, FALSE},
1036 {"R:", NULL, FALSE},
1037 {"NG:", NULL, FALSE},
1038 {"MAID:", NULL, FALSE},
1039 {"NAID:", NULL, FALSE},
1040 {"SCF:", NULL, FALSE},
1041 {"RMID:", NULL, FALSE},
1042 {"FMID:", NULL, FALSE},
1043 {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1044 {"X-Sylpheed-Encrypt:", NULL, FALSE},
1045 {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1046 {NULL, NULL, FALSE}};
1049 gint mailval = 0, newsval = 0;
1051 gchar *smtpserver = NULL;
1052 GSList *to_list = NULL;
1053 GSList *newsgroup_list = NULL;
1054 gchar *savecopyfolder = NULL;
1055 gchar *replymessageid = NULL;
1056 gchar *fwdmessageid = NULL;
1057 gchar *privacy_system = NULL;
1058 gboolean encrypt = FALSE;
1059 gchar *encrypt_data = NULL;
1060 gchar buf[BUFFSIZE];
1062 PrefsAccount *mailac = NULL, *newsac = NULL;
1065 g_return_val_if_fail(file != NULL, -1);
1067 if ((fp = fopen(file, "rb")) == NULL) {
1068 FILE_OP_ERROR(file, "fopen");
1072 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1074 gchar *p = buf + strlen(qentry[hnum].name);
1078 if (!from) from = g_strdup(p);
1081 if (!smtpserver) smtpserver = g_strdup(p);
1084 to_list = address_list_append(to_list, p);
1087 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1089 case Q_MAIL_ACCOUNT_ID:
1090 mailac = account_find_from_id(atoi(p));
1092 case Q_NEWS_ACCOUNT_ID:
1093 newsac = account_find_from_id(atoi(p));
1095 case Q_SAVE_COPY_FOLDER:
1096 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1098 case Q_REPLY_MESSAGE_ID:
1099 if (!replymessageid) replymessageid = g_strdup(p);
1101 case Q_FWD_MESSAGE_ID:
1102 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1104 case Q_PRIVACY_SYSTEM:
1105 if (privacy_system == NULL) privacy_system = g_strdup(p);
1108 if (p[0] == '1') encrypt = TRUE;
1110 case Q_ENCRYPT_DATA:
1111 if (encrypt_data == NULL) encrypt_data = g_strdup(p);
1115 filepos = ftell(fp);
1118 /* FIXME: memory leaks, in case of errors */
1123 mimeinfo = procmime_scan_queue_file(file);
1124 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data))
1128 if (procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1132 procmime_mimeinfo_free_all(mimeinfo);
1139 debug_print("Sending message by mail\n");
1141 g_warning("Queued message header is broken.\n");
1143 } else if (mailac && mailac->use_mail_command &&
1144 mailac->mail_command && (* mailac->mail_command)) {
1145 mailval = send_message_local(mailac->mail_command, fp);
1149 mailac = account_find_from_smtp_server(from, smtpserver);
1151 g_warning("Account not found. "
1152 "Using current account...\n");
1153 mailac = cur_account;
1158 mailval = send_message_smtp(mailac, to_list, fp);
1160 PrefsAccount tmp_ac;
1162 g_warning("Account not found.\n");
1164 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1165 tmp_ac.address = from;
1166 tmp_ac.smtp_server = smtpserver;
1167 tmp_ac.smtpport = SMTP_PORT;
1168 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1173 fseek(fp, filepos, SEEK_SET);
1174 if (newsgroup_list && (mailval == 0)) {
1179 /* write to temporary file */
1180 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1181 G_DIR_SEPARATOR, (gint)file);
1182 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1183 FILE_OP_ERROR(tmp, "fopen");
1185 alertpanel_error(_("Could not create temporary file for news sending."));
1187 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1188 FILE_OP_ERROR(tmp, "chmod");
1189 g_warning("can't change file mode\n");
1192 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1193 if (fputs(buf, tmpfp) == EOF) {
1194 FILE_OP_ERROR(tmp, "fputs");
1196 alertpanel_error(_("Error when writing temporary file for news sending."));
1202 debug_print("Sending message by news\n");
1204 folder = FOLDER(newsac->folder);
1206 newsval = news_post(folder, tmp);
1208 alertpanel_error(_("Error occurred while posting the message to %s ."),
1209 newsac->nntp_server);
1217 slist_free_strings(to_list);
1218 g_slist_free(to_list);
1219 slist_free_strings(newsgroup_list);
1220 g_slist_free(newsgroup_list);
1225 /* save message to outbox */
1226 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1229 debug_print("saving sent message...\n");
1231 outbox = folder_find_item_from_identifier(savecopyfolder);
1233 outbox = folder_get_default_outbox();
1235 procmsg_save_to_outbox(outbox, file, TRUE);
1238 if (replymessageid != NULL || fwdmessageid != NULL) {
1242 if (replymessageid != NULL)
1243 tokens = g_strsplit(replymessageid, "\x7f", 0);
1245 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1246 item = folder_find_item_from_identifier(tokens[0]);
1248 /* check if queued message has valid folder and message id */
1249 if (item != NULL && tokens[2] != NULL) {
1252 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1254 /* check if referring message exists and has a message id */
1255 if ((msginfo != NULL) &&
1256 (msginfo->msgid != NULL) &&
1257 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1258 procmsg_msginfo_free(msginfo);
1262 if (msginfo == NULL) {
1263 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1266 if (msginfo != NULL) {
1267 if (replymessageid != NULL) {
1268 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1269 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1271 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1272 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1274 procmsg_msginfo_free(msginfo);
1280 g_free(savecopyfolder);
1281 g_free(replymessageid);
1282 g_free(fwdmessageid);
1284 return (newsval != 0 ? newsval : mailval);
1287 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1289 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1292 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1296 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1301 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1302 item->unread_msgs++;
1303 if (procmsg_msg_has_marked_parent(msginfo))
1304 item->unreadmarked_msgs++;
1307 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1308 item->unread_msgs--;
1309 if (procmsg_msg_has_marked_parent(msginfo))
1310 item->unreadmarked_msgs--;
1314 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1315 procmsg_update_unread_children(msginfo, TRUE);
1318 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1319 procmsg_update_unread_children(msginfo, FALSE);
1323 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1326 MsgInfoUpdate msginfo_update;
1327 MsgPermFlags perm_flags_new, perm_flags_old;
1328 MsgTmpFlags tmp_flags_old;
1330 g_return_if_fail(msginfo != NULL);
1331 item = msginfo->folder;
1332 g_return_if_fail(item != NULL);
1334 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1336 /* Perm Flags handling */
1337 perm_flags_old = msginfo->flags.perm_flags;
1338 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1339 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1340 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1343 if (perm_flags_old != perm_flags_new) {
1344 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1346 update_folder_msg_counts(item, msginfo, perm_flags_old);
1350 /* Tmp flags handling */
1351 tmp_flags_old = msginfo->flags.tmp_flags;
1352 msginfo->flags.tmp_flags |= tmp_flags;
1354 /* update notification */
1355 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1356 msginfo_update.msginfo = msginfo;
1357 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1358 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1359 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1363 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1366 MsgInfoUpdate msginfo_update;
1367 MsgPermFlags perm_flags_new, perm_flags_old;
1368 MsgTmpFlags tmp_flags_old;
1370 g_return_if_fail(msginfo != NULL);
1371 item = msginfo->folder;
1372 g_return_if_fail(item != NULL);
1374 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1376 /* Perm Flags handling */
1377 perm_flags_old = msginfo->flags.perm_flags;
1378 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1380 if (perm_flags_old != perm_flags_new) {
1381 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1383 update_folder_msg_counts(item, msginfo, perm_flags_old);
1385 msginfo_update.msginfo = msginfo;
1386 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1387 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1388 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1391 /* Tmp flags hanlding */
1392 tmp_flags_old = msginfo->flags.tmp_flags;
1393 msginfo->flags.tmp_flags &= ~tmp_flags;
1395 /* update notification */
1396 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1397 msginfo_update.msginfo = msginfo;
1398 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1399 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1400 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1405 *\brief check for flags (e.g. mark) in prior msgs of current thread
1407 *\param info Current message
1408 *\param perm_flags Flags to be checked
1409 *\param parentmsgs Hash of prior msgs to avoid loops
1411 *\return gboolean TRUE if perm_flags are found
1413 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1414 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1418 g_return_val_if_fail(info != NULL, FALSE);
1420 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1421 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1423 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1424 procmsg_msginfo_free(tmp);
1426 } else if (tmp != NULL) {
1429 if (g_hash_table_lookup(parentmsgs, info)) {
1430 debug_print("loop detected: %s%c%d\n",
1431 folder_item_get_path(info->folder),
1432 G_DIR_SEPARATOR, info->msgnum);
1435 g_hash_table_insert(parentmsgs, info, "1");
1436 result = procmsg_msg_has_flagged_parent_real(
1437 tmp, perm_flags, parentmsgs);
1439 procmsg_msginfo_free(tmp);
1449 *\brief Callback for cleaning up hash of parentmsgs
1451 gboolean parentmsgs_hash_remove(gpointer key,
1459 *\brief Set up list of parentmsgs
1460 * See procmsg_msg_has_flagged_parent_real()
1462 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1465 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1467 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1468 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1469 g_hash_table_destroy(parentmsgs);
1474 *\brief Check if msgs prior in thread are marked
1475 * See procmsg_msg_has_flagged_parent_real()
1477 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1479 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1483 GSList *procmsg_find_children_func(MsgInfo *info,
1484 GSList *children, GSList *all)
1488 g_return_val_if_fail(info!=NULL, children);
1489 if (info->msgid == NULL)
1492 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1493 MsgInfo *tmp = (MsgInfo *)cur->data;
1494 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1495 /* Check if message is already in the list */
1496 if ((children == NULL) ||
1497 (g_slist_index(children, tmp) == -1)) {
1498 children = g_slist_prepend(children,
1499 procmsg_msginfo_new_ref(tmp));
1500 children = procmsg_find_children_func(tmp,
1509 GSList *procmsg_find_children (MsgInfo *info)
1514 g_return_val_if_fail(info!=NULL, NULL);
1515 all = folder_item_get_msg_list(info->folder);
1516 children = procmsg_find_children_func(info, NULL, all);
1517 if (children != NULL) {
1518 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1519 /* this will not free the used pointers
1520 created with procmsg_msginfo_new_ref */
1521 procmsg_msginfo_free((MsgInfo *)cur->data);
1529 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1531 GSList *children = procmsg_find_children(info);
1533 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1534 MsgInfo *tmp = (MsgInfo *)cur->data;
1535 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1537 info->folder->unreadmarked_msgs++;
1539 info->folder->unreadmarked_msgs--;
1540 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1542 procmsg_msginfo_free(tmp);
1544 g_slist_free(children);
1548 * Set the destination folder for a copy or move operation
1550 * \param msginfo The message which's destination folder is changed
1551 * \param to_folder The destination folder for the operation
1553 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1555 if(msginfo->to_folder != NULL) {
1556 msginfo->to_folder->op_count--;
1557 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1559 msginfo->to_folder = to_folder;
1560 if(to_folder != NULL) {
1561 to_folder->op_count++;
1562 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1567 * Apply filtering actions to the msginfo
1569 * \param msginfo The MsgInfo describing the message that should be filtered
1570 * \return TRUE if the message was moved and MsgInfo is now invalid,
1573 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1575 MailFilteringData mail_filtering_data;
1577 mail_filtering_data.msginfo = msginfo;
1578 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1581 /* filter if enabled in prefs or move to inbox if not */
1582 if((filtering_rules != NULL) &&
1583 filter_message_by_msginfo(filtering_rules, msginfo))