2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 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"
45 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
47 GHashTable *msg_table;
49 if (mlist == NULL) return NULL;
51 msg_table = g_hash_table_new(NULL, g_direct_equal);
52 procmsg_msg_hash_table_append(msg_table, mlist);
57 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
62 if (msg_table == NULL || mlist == NULL) return;
64 for (cur = mlist; cur != NULL; cur = cur->next) {
65 msginfo = (MsgInfo *)cur->data;
67 g_hash_table_insert(msg_table,
68 GUINT_TO_POINTER(msginfo->msgnum),
73 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
75 GHashTable *msg_table;
79 if (mlist == NULL) return NULL;
81 msg_table = g_hash_table_new(NULL, g_direct_equal);
83 for (cur = mlist; cur != NULL; cur = cur->next) {
84 msginfo = (MsgInfo *)cur->data;
85 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
91 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
97 for (cur = mlist; cur != NULL; cur = cur->next) {
98 msginfo = (MsgInfo *)cur->data;
99 if (msginfo && msginfo->msgnum > last)
100 last = msginfo->msgnum;
106 void procmsg_msg_list_free(GSList *mlist)
111 for (cur = mlist; cur != NULL; cur = cur->next) {
112 msginfo = (MsgInfo *)cur->data;
113 procmsg_msginfo_free(msginfo);
127 /* CLAWS subject threading:
129 in the first round it inserts subject lines in a
130 relation (subject <-> node)
132 the second round finishes the threads by attaching
133 matching subject lines to the one found in the
134 relation. will use the oldest node with the same
135 subject that is not more then thread_by_subject_max_age
136 days old (see subject_relation_lookup)
139 static void subject_relation_insert(GRelation *relation, GNode *node)
144 g_return_if_fail(relation != NULL);
145 g_return_if_fail(node != NULL);
146 msginfo = (MsgInfo *) node->data;
147 g_return_if_fail(msginfo != NULL);
149 subject = msginfo->subject;
152 subject += subject_get_prefix_length(subject);
154 g_relation_insert(relation, subject, node);
157 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
164 g_return_val_if_fail(relation != NULL, NULL);
166 subject = msginfo->subject;
169 prefix_length = subject_get_prefix_length(subject);
170 if (prefix_length <= 0)
172 subject += prefix_length;
174 tuples = g_relation_select(relation, subject, 0);
178 if (tuples->len > 0) {
180 GNode *relation_node;
181 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
184 /* check all nodes with the same subject to find the best parent */
185 for (i = 0; i < tuples->len; i++) {
186 relation_node = (GNode *) g_tuples_index(tuples, i, 1);
187 relation_msginfo = (MsgInfo *) relation_node->data;
190 /* best node should be the oldest in the found nodes */
191 /* parent node must not be older then msginfo */
192 if ((relation_msginfo->date_t < msginfo->date_t) &&
193 ((best_msginfo == NULL) ||
194 (best_msginfo->date_t > relation_msginfo->date_t)))
197 /* parent node must not be more then thread_by_subject_max_age
198 days older then msginfo */
199 if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
200 prefs_common.thread_by_subject_max_age * 3600 * 24)
203 /* can add new tests for all matching
204 nodes found by subject */
207 node = relation_node;
208 best_msginfo = relation_msginfo;
213 g_tuples_destroy(tuples);
217 /* return the reversed thread tree */
218 GNode *procmsg_get_thread_tree(GSList *mlist)
220 GNode *root, *parent, *node, *next, *last;
221 GNode *prev; /* CLAWS */
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. record the last encountered node. */
255 for (node = root->children; node != NULL; ) {
256 parent = prev = 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)) {
265 /* since this node is moved away, the previous
266 * one is the last recorded one */
270 (parent, parent->children, node);
273 last = prev ? prev : node;
277 if (prefs_common.thread_by_subject) {
278 for (node = last; node && node != NULL;) {
280 msginfo = (MsgInfo *) node->data;
282 parent = subject_relation_lookup(subject_relation, msginfo);
284 /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to
285 find the parent node */
286 if (parent != NULL) {
287 if (g_node_is_ancestor(node, parent))
295 g_node_append(parent, node);
302 g_relation_destroy(subject_relation);
303 g_hash_table_destroy(msgid_table);
308 void procmsg_move_messages(GSList *mlist)
310 GSList *cur, *movelist = NULL;
312 FolderItem *dest = NULL;
316 folder_item_update_freeze();
318 for (cur = mlist; cur != NULL; cur = cur->next) {
319 msginfo = (MsgInfo *)cur->data;
321 dest = msginfo->to_folder;
322 movelist = g_slist_append(movelist, msginfo);
323 } else if (dest == msginfo->to_folder) {
324 movelist = g_slist_append(movelist, msginfo);
326 folder_item_move_msgs(dest, movelist);
327 g_slist_free(movelist);
329 dest = msginfo->to_folder;
330 movelist = g_slist_append(movelist, msginfo);
332 procmsg_msginfo_set_to_folder(msginfo, NULL);
336 folder_item_move_msgs(dest, movelist);
337 g_slist_free(movelist);
340 folder_item_update_thaw();
343 void procmsg_copy_messages(GSList *mlist)
345 GSList *cur, *copylist = NULL;
347 FolderItem *dest = NULL;
351 folder_item_update_freeze();
353 for (cur = mlist; cur != NULL; cur = cur->next) {
354 msginfo = (MsgInfo *)cur->data;
356 dest = msginfo->to_folder;
357 copylist = g_slist_append(copylist, msginfo);
358 } else if (dest == msginfo->to_folder) {
359 copylist = g_slist_append(copylist, msginfo);
361 folder_item_copy_msgs(dest, copylist);
362 g_slist_free(copylist);
364 dest = msginfo->to_folder;
365 copylist = g_slist_append(copylist, msginfo);
367 procmsg_msginfo_set_to_folder(msginfo, NULL);
371 folder_item_copy_msgs(dest, copylist);
372 g_slist_free(copylist);
375 folder_item_update_thaw();
378 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
382 g_return_val_if_fail(msginfo != NULL, NULL);
384 if (msginfo->plaintext_file)
385 file = g_strdup(msginfo->plaintext_file);
387 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
393 gchar *procmsg_get_message_file(MsgInfo *msginfo)
395 gchar *filename = NULL;
397 g_return_val_if_fail(msginfo != NULL, NULL);
399 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
401 debug_print("can't fetch message %d\n", msginfo->msgnum);
406 GSList *procmsg_get_message_file_list(GSList *mlist)
408 GSList *file_list = NULL;
410 MsgFileInfo *fileinfo;
413 while (mlist != NULL) {
414 msginfo = (MsgInfo *)mlist->data;
415 file = procmsg_get_message_file(msginfo);
417 procmsg_message_file_list_free(file_list);
420 fileinfo = g_new(MsgFileInfo, 1);
421 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
422 fileinfo->file = file;
423 fileinfo->flags = g_new(MsgFlags, 1);
424 *fileinfo->flags = msginfo->flags;
425 file_list = g_slist_prepend(file_list, fileinfo);
429 file_list = g_slist_reverse(file_list);
434 void procmsg_message_file_list_free(MsgInfoList *file_list)
437 MsgFileInfo *fileinfo;
439 for (cur = file_list; cur != NULL; cur = cur->next) {
440 fileinfo = (MsgFileInfo *)cur->data;
441 procmsg_msginfo_free(fileinfo->msginfo);
442 g_free(fileinfo->file);
443 g_free(fileinfo->flags);
447 g_slist_free(file_list);
450 FILE *procmsg_open_message(MsgInfo *msginfo)
455 g_return_val_if_fail(msginfo != NULL, NULL);
457 file = procmsg_get_message_file_path(msginfo);
458 g_return_val_if_fail(file != NULL, NULL);
460 if (!is_file_exist(file)) {
462 file = procmsg_get_message_file(msginfo);
467 if ((fp = fopen(file, "rb")) == NULL) {
468 FILE_OP_ERROR(file, "fopen");
475 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
478 while (fgets(buf, sizeof(buf), fp) != NULL)
479 if (buf[0] == '\r' || buf[0] == '\n') break;
485 gboolean procmsg_msg_exist(MsgInfo *msginfo)
490 if (!msginfo) return FALSE;
492 path = folder_item_get_path(msginfo->folder);
494 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
500 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
501 PrefsFilterType type)
503 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
504 {"X-ML-Name:", NULL, TRUE},
505 {"X-List:", NULL, TRUE},
506 {"X-Mailing-list:", NULL, TRUE},
507 {"List-Id:", NULL, TRUE},
508 {"X-Sequence:", NULL, TRUE},
509 {NULL, NULL, FALSE}};
515 H_X_MAILING_LIST = 3,
522 g_return_if_fail(msginfo != NULL);
523 g_return_if_fail(header != NULL);
524 g_return_if_fail(key != NULL);
533 if ((fp = procmsg_open_message(msginfo)) == NULL)
535 procheader_get_header_fields(fp, hentry);
538 #define SET_FILTER_KEY(hstr, idx) \
540 *header = g_strdup(hstr); \
541 *key = hentry[idx].body; \
542 hentry[idx].body = NULL; \
545 if (hentry[H_X_BEENTHERE].body != NULL) {
546 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
547 } else if (hentry[H_X_ML_NAME].body != NULL) {
548 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
549 } else if (hentry[H_X_LIST].body != NULL) {
550 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
551 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
552 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
553 } else if (hentry[H_LIST_ID].body != NULL) {
554 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
555 extract_list_id_str(*key);
556 } else if (hentry[H_X_SEQUENCE].body != NULL) {
559 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
562 while (*p != '\0' && !isspace(*p)) p++;
563 while (isspace(*p)) p++;
570 } else if (msginfo->subject) {
571 *header = g_strdup("subject");
572 *key = g_strdup(msginfo->subject);
575 #undef SET_FILTER_KEY
577 g_free(hentry[H_X_BEENTHERE].body);
578 hentry[H_X_BEENTHERE].body = NULL;
579 g_free(hentry[H_X_ML_NAME].body);
580 hentry[H_X_ML_NAME].body = NULL;
581 g_free(hentry[H_X_LIST].body);
582 hentry[H_X_LIST].body = NULL;
583 g_free(hentry[H_X_MAILING_LIST].body);
584 hentry[H_X_MAILING_LIST].body = NULL;
585 g_free(hentry[H_LIST_ID].body);
586 hentry[H_LIST_ID].body = NULL;
590 *header = g_strdup("from");
591 *key = g_strdup(msginfo->from);
594 *header = g_strdup("to");
595 *key = g_strdup(msginfo->to);
597 case FILTER_BY_SUBJECT:
598 *header = g_strdup("subject");
599 *key = g_strdup(msginfo->subject);
606 void procmsg_empty_trash(void)
611 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
612 trash = FOLDER(cur->data)->trash;
613 if (trash && trash->total_msgs > 0)
614 folder_item_remove_all_msg(trash);
619 *\brief Send messages in queue
621 *\param queue Queue folder to process
622 *\param save_msgs Unused
624 *\return Number of messages sent, negative if an error occurred
625 * positive if no error occurred
627 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
629 gint ret = 1, count = 0;
633 queue = folder_get_default_queue();
634 g_return_val_if_fail(queue != NULL, -1);
636 folder_item_scan(queue);
637 list = folder_item_get_msg_list(queue);
639 for (elem = list; elem != NULL; elem = elem->next) {
643 msginfo = (MsgInfo *)(elem->data);
644 if (!MSG_IS_LOCKED(msginfo->flags)) {
645 file = folder_item_fetch_msg(queue, msginfo->msgnum);
647 if (procmsg_send_message_queue(file) < 0) {
648 g_warning("Sending queued message %d failed.\n",
653 * We save in procmsg_send_message_queue because
654 * we need the destination folder from the queue
658 procmsg_save_to_outbox
659 (queue->folder->outbox,
663 folder_item_remove_msg(queue, msginfo->msgnum);
668 /* FIXME: supposedly if only one message is locked, and queue
669 * is being flushed, the following free says something like
670 * "freeing msg ## in folder (nil)". */
671 procmsg_msginfo_free(msginfo);
677 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
682 if ((fp = fopen(in, "rb")) == NULL) {
683 FILE_OP_ERROR(in, "fopen");
686 if ((outfp = fopen(out, "wb")) == NULL) {
687 FILE_OP_ERROR(out, "fopen");
691 while (fgets(buf, sizeof(buf), fp) != NULL)
692 if (buf[0] == '\r' || buf[0] == '\n') break;
693 while (fgets(buf, sizeof(buf), fp) != NULL)
700 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
704 MsgInfo *msginfo, *tmp_msginfo;
705 MsgFlags flag = {0, 0};
707 debug_print("saving sent message...\n");
710 outbox = folder_get_default_outbox();
711 g_return_val_if_fail(outbox != NULL, -1);
713 /* remove queueing headers */
715 gchar tmp[MAXPATHLEN + 1];
717 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
718 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
720 if (procmsg_remove_special_headers(file, tmp) !=0)
723 folder_item_scan(outbox);
724 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
725 g_warning("can't save message\n");
730 folder_item_scan(outbox);
731 if ((num = folder_item_add_msg
732 (outbox, file, &flag, FALSE)) < 0) {
733 g_warning("can't save message\n");
738 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
739 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
740 if (msginfo != NULL) {
741 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
742 procmsg_msginfo_free(msginfo); /* refcnt-- */
743 /* tmp_msginfo == msginfo */
744 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
745 msginfo->returnreceiptto)) {
746 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
747 procmsg_msginfo_free(msginfo); /* refcnt-- */
754 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
756 static const gchar *def_cmd = "lpr %s";
763 g_return_if_fail(msginfo);
765 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
766 g_warning("Can't get text part\n");
770 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
771 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
773 if ((prfp = fopen(prtmp, "wb")) == NULL) {
774 FILE_OP_ERROR(prtmp, "fopen");
780 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
781 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
782 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
783 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
784 if (msginfo->newsgroups)
785 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
786 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
789 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
795 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
797 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
800 g_warning("Print command line is invalid: `%s'\n",
802 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
808 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
812 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
819 MsgInfo *procmsg_msginfo_new(void)
823 newmsginfo = g_new0(MsgInfo, 1);
824 newmsginfo->refcnt = 1;
829 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
833 if (msginfo == NULL) return NULL;
835 newmsginfo = g_new0(MsgInfo, 1);
837 newmsginfo->refcnt = 1;
839 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
840 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
841 g_strdup(msginfo->mmb) : NULL
865 MEMBDUP(dispositionnotificationto);
866 MEMBDUP(returnreceiptto);
870 MEMBCOPY(threadscore);
875 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
877 MsgInfo *full_msginfo;
880 if (msginfo == NULL) return NULL;
882 file = procmsg_get_message_file(msginfo);
884 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
888 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
890 if (!full_msginfo) return NULL;
892 /* CLAWS: make sure we add the missing members; see:
893 * procheader.c::procheader_get_headernames() */
895 msginfo->xface = g_strdup(full_msginfo->xface);
896 if (!msginfo->dispositionnotificationto)
897 msginfo->dispositionnotificationto =
898 g_strdup(full_msginfo->dispositionnotificationto);
899 if (!msginfo->returnreceiptto)
900 msginfo->returnreceiptto = g_strdup
901 (full_msginfo->returnreceiptto);
902 procmsg_msginfo_free(full_msginfo);
904 return procmsg_msginfo_new_ref(msginfo);
907 void procmsg_msginfo_free(MsgInfo *msginfo)
909 if (msginfo == NULL) return;
912 if (msginfo->refcnt > 0)
915 if (msginfo->to_folder) {
916 msginfo->to_folder->op_count--;
917 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
920 g_free(msginfo->fromspace);
921 g_free(msginfo->references);
922 g_free(msginfo->returnreceiptto);
923 g_free(msginfo->dispositionnotificationto);
924 g_free(msginfo->xface);
926 g_free(msginfo->fromname);
928 g_free(msginfo->date);
929 g_free(msginfo->from);
932 g_free(msginfo->newsgroups);
933 g_free(msginfo->subject);
934 g_free(msginfo->msgid);
935 g_free(msginfo->inreplyto);
936 g_free(msginfo->xref);
941 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
945 memusage += sizeof(MsgInfo);
946 if (msginfo->fromname)
947 memusage += strlen(msginfo->fromname);
949 memusage += strlen(msginfo->date);
951 memusage += strlen(msginfo->from);
953 memusage += strlen(msginfo->to);
955 memusage += strlen(msginfo->cc);
956 if (msginfo->newsgroups)
957 memusage += strlen(msginfo->newsgroups);
958 if (msginfo->subject)
959 memusage += strlen(msginfo->subject);
961 memusage += strlen(msginfo->msgid);
962 if (msginfo->inreplyto)
963 memusage += strlen(msginfo->inreplyto);
965 memusage += strlen(msginfo->xface);
966 if (msginfo->dispositionnotificationto)
967 memusage += strlen(msginfo->dispositionnotificationto);
968 if (msginfo->returnreceiptto)
969 memusage += strlen(msginfo->returnreceiptto);
970 if (msginfo->references)
971 memusage += strlen(msginfo->references);
972 if (msginfo->fromspace)
973 memusage += strlen(msginfo->fromspace);
978 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
980 const MsgInfo *msginfo1 = a;
981 const MsgInfo *msginfo2 = b;
988 return msginfo1->msgnum - msginfo2->msgnum;
997 Q_MAIL_ACCOUNT_ID = 4,
998 Q_NEWS_ACCOUNT_ID = 5,
999 Q_SAVE_COPY_FOLDER = 6,
1000 Q_REPLY_MESSAGE_ID = 7,
1001 Q_FWD_MESSAGE_ID = 8
1004 gint procmsg_send_message_queue(const gchar *file)
1006 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1007 {"SSV:", NULL, FALSE},
1008 {"R:", NULL, FALSE},
1009 {"NG:", NULL, FALSE},
1010 {"MAID:", NULL, FALSE},
1011 {"NAID:", NULL, FALSE},
1012 {"SCF:", NULL, FALSE},
1013 {"RMID:", NULL, FALSE},
1014 {"FMID:", NULL, FALSE},
1015 {NULL, NULL, FALSE}};
1018 gint mailval = 0, newsval = 0;
1020 gchar *smtpserver = NULL;
1021 GSList *to_list = NULL;
1022 GSList *newsgroup_list = NULL;
1023 gchar *savecopyfolder = NULL;
1024 gchar *replymessageid = NULL;
1025 gchar *fwdmessageid = NULL;
1026 gchar buf[BUFFSIZE];
1028 PrefsAccount *mailac = NULL, *newsac = NULL;
1031 g_return_val_if_fail(file != NULL, -1);
1033 if ((fp = fopen(file, "rb")) == NULL) {
1034 FILE_OP_ERROR(file, "fopen");
1038 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1040 gchar *p = buf + strlen(qentry[hnum].name);
1044 if (!from) from = g_strdup(p);
1047 if (!smtpserver) smtpserver = g_strdup(p);
1050 to_list = address_list_append(to_list, p);
1053 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1055 case Q_MAIL_ACCOUNT_ID:
1056 mailac = account_find_from_id(atoi(p));
1058 case Q_NEWS_ACCOUNT_ID:
1059 newsac = account_find_from_id(atoi(p));
1061 case Q_SAVE_COPY_FOLDER:
1062 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1064 case Q_REPLY_MESSAGE_ID:
1065 if (!replymessageid) replymessageid = g_strdup(p);
1067 case Q_FWD_MESSAGE_ID:
1068 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1072 filepos = ftell(fp);
1075 debug_print("Sending message by mail\n");
1077 g_warning("Queued message header is broken.\n");
1079 } else if (mailac && mailac->use_mail_command &&
1080 mailac->mail_command && (* mailac->mail_command)) {
1081 mailval = send_message_local(mailac->mail_command, fp);
1085 mailac = account_find_from_smtp_server(from, smtpserver);
1087 g_warning("Account not found. "
1088 "Using current account...\n");
1089 mailac = cur_account;
1094 mailval = send_message_smtp(mailac, to_list, fp);
1096 PrefsAccount tmp_ac;
1098 g_warning("Account not found.\n");
1100 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1101 tmp_ac.address = from;
1102 tmp_ac.smtp_server = smtpserver;
1103 tmp_ac.smtpport = SMTP_PORT;
1104 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1109 fseek(fp, filepos, SEEK_SET);
1110 if (newsgroup_list && (newsval == 0)) {
1115 /* write to temporary file */
1116 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1117 G_DIR_SEPARATOR, (gint)file);
1118 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1119 FILE_OP_ERROR(tmp, "fopen");
1121 alertpanel_error(_("Could not create temporary file for news sending."));
1123 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1124 FILE_OP_ERROR(tmp, "chmod");
1125 g_warning("can't change file mode\n");
1128 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1129 if (fputs(buf, tmpfp) == EOF) {
1130 FILE_OP_ERROR(tmp, "fputs");
1132 alertpanel_error(_("Error when writing temporary file for news sending."));
1138 debug_print("Sending message by news\n");
1140 folder = FOLDER(newsac->folder);
1142 newsval = news_post(folder, tmp);
1144 alertpanel_error(_("Error occurred while posting the message to %s ."),
1145 newsac->nntp_server);
1153 slist_free_strings(to_list);
1154 g_slist_free(to_list);
1155 slist_free_strings(newsgroup_list);
1156 g_slist_free(newsgroup_list);
1161 /* save message to outbox */
1162 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1165 debug_print("saving sent message...\n");
1167 outbox = folder_find_item_from_identifier(savecopyfolder);
1169 outbox = folder_get_default_outbox();
1171 procmsg_save_to_outbox(outbox, file, TRUE);
1174 if (replymessageid != NULL || fwdmessageid != NULL) {
1178 if (replymessageid != NULL)
1179 tokens = g_strsplit(replymessageid, "\x7f", 0);
1181 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1182 item = folder_find_item_from_identifier(tokens[0]);
1184 /* check if queued message has valid folder and message id */
1185 if (item != NULL && tokens[2] != NULL) {
1188 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1190 /* check if referring message exists and has a message id */
1191 if ((msginfo != NULL) &&
1192 (msginfo->msgid != NULL) &&
1193 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1194 procmsg_msginfo_free(msginfo);
1198 if (msginfo == NULL) {
1199 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1202 if (msginfo != NULL) {
1203 if (replymessageid != NULL) {
1204 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1205 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1208 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1209 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1211 procmsg_msginfo_free(msginfo);
1217 g_free(savecopyfolder);
1218 g_free(replymessageid);
1219 g_free(fwdmessageid);
1221 return (newsval != 0 ? newsval : mailval);
1224 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1226 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1229 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1233 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1238 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1239 item->unread_msgs++;
1240 if (procmsg_msg_has_marked_parent(msginfo))
1241 item->unreadmarked_msgs++;
1244 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1245 item->unread_msgs--;
1246 if (procmsg_msg_has_marked_parent(msginfo))
1247 item->unreadmarked_msgs--;
1251 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1252 procmsg_update_unread_children(msginfo, TRUE);
1255 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1256 procmsg_update_unread_children(msginfo, FALSE);
1260 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1263 MsgInfoUpdate msginfo_update;
1264 MsgPermFlags perm_flags_new, perm_flags_old;
1266 g_return_if_fail(msginfo != NULL);
1267 item = msginfo->folder;
1268 g_return_if_fail(item != NULL);
1270 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1272 /* Perm Flags handling */
1273 perm_flags_old = msginfo->flags.perm_flags;
1274 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1275 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1276 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1279 if (perm_flags_old != perm_flags_new) {
1280 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1282 update_folder_msg_counts(item, msginfo, perm_flags_old);
1284 msginfo_update.msginfo = msginfo;
1285 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1286 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1287 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1290 /* Tmp flags hanlding */
1291 msginfo->flags.tmp_flags |= tmp_flags;
1294 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1297 MsgInfoUpdate msginfo_update;
1298 MsgPermFlags perm_flags_new, perm_flags_old;
1300 g_return_if_fail(msginfo != NULL);
1301 item = msginfo->folder;
1302 g_return_if_fail(item != NULL);
1304 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1306 /* Perm Flags handling */
1307 perm_flags_old = msginfo->flags.perm_flags;
1308 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1310 if (perm_flags_old != perm_flags_new) {
1311 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1313 update_folder_msg_counts(item, msginfo, perm_flags_old);
1315 msginfo_update.msginfo = msginfo;
1316 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1317 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1318 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1321 /* Tmp flags hanlding */
1322 msginfo->flags.tmp_flags &= ~tmp_flags;
1326 *\brief check for flags (e.g. mark) in prior msgs of current thread
1328 *\param info Current message
1329 *\param perm_flags Flags to be checked
1330 *\param parentmsgs Hash of prior msgs to avoid loops
1332 *\return gboolean TRUE if perm_flags are found
1334 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1335 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1339 g_return_val_if_fail(info != NULL, FALSE);
1341 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1342 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1344 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1345 procmsg_msginfo_free(tmp);
1347 } else if (tmp != NULL) {
1350 if (g_hash_table_lookup(parentmsgs, info)) {
1351 debug_print("loop detected: %s%c%d\n",
1352 folder_item_get_path(info->folder),
1353 G_DIR_SEPARATOR, info->msgnum);
1356 g_hash_table_insert(parentmsgs, info, "1");
1357 result = procmsg_msg_has_flagged_parent_real(
1358 tmp, perm_flags, parentmsgs);
1360 procmsg_msginfo_free(tmp);
1370 *\brief Callback for cleaning up hash of parentmsgs
1372 gboolean parentmsgs_hash_remove(gpointer key,
1380 *\brief Set up list of parentmsgs
1381 * See procmsg_msg_has_flagged_parent_real()
1383 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1386 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1388 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1389 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1390 g_hash_table_destroy(parentmsgs);
1395 *\brief Check if msgs prior in thread are marked
1396 * See procmsg_msg_has_flagged_parent_real()
1398 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1400 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1404 GSList *procmsg_find_children_func(MsgInfo *info,
1405 GSList *children, GSList *all)
1409 g_return_val_if_fail(info!=NULL, children);
1410 if (info->msgid == NULL)
1413 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1414 MsgInfo *tmp = (MsgInfo *)cur->data;
1415 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1416 /* Check if message is already in the list */
1417 if ((children == NULL) ||
1418 (g_slist_index(children, tmp) == -1)) {
1419 children = g_slist_prepend(children,
1420 procmsg_msginfo_new_ref(tmp));
1421 children = procmsg_find_children_func(tmp,
1430 GSList *procmsg_find_children (MsgInfo *info)
1435 g_return_val_if_fail(info!=NULL, NULL);
1436 all = folder_item_get_msg_list(info->folder);
1437 children = procmsg_find_children_func(info, NULL, all);
1438 if (children != NULL) {
1439 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1440 /* this will not free the used pointers
1441 created with procmsg_msginfo_new_ref */
1442 procmsg_msginfo_free((MsgInfo *)cur->data);
1450 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1452 GSList *children = procmsg_find_children(info);
1454 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1455 MsgInfo *tmp = (MsgInfo *)cur->data;
1456 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1458 info->folder->unreadmarked_msgs++;
1460 info->folder->unreadmarked_msgs--;
1461 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1463 procmsg_msginfo_free(tmp);
1465 g_slist_free(children);
1469 * Set the destination folder for a copy or move operation
1471 * \param msginfo The message which's destination folder is changed
1472 * \param to_folder The destination folder for the operation
1474 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1476 if(msginfo->to_folder != NULL) {
1477 msginfo->to_folder->op_count--;
1478 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1480 msginfo->to_folder = to_folder;
1481 if(to_folder != NULL) {
1482 to_folder->op_count++;
1483 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1488 * Apply filtering actions to the msginfo
1490 * \param msginfo The MsgInfo describing the message that should be filtered
1491 * \return TRUE if the message was moved and MsgInfo is now invalid,
1494 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1496 MailFilteringData mail_filtering_data;
1498 mail_filtering_data.msginfo = msginfo;
1499 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1502 /* filter if enabled in prefs or move to inbox if not */
1503 if((filtering_rules != NULL) &&
1504 filter_message_by_msginfo(filtering_rules, msginfo))