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;
221 GHashTable *msgid_table;
222 GRelation *subject_relation;
226 root = g_node_new(NULL);
227 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
228 subject_relation = g_relation_new(2);
229 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
231 for (; mlist != NULL; mlist = mlist->next) {
232 msginfo = (MsgInfo *)mlist->data;
235 if (msginfo->inreplyto) {
236 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
237 if (parent == NULL) {
241 node = g_node_insert_data_before
242 (parent, parent == root ? parent->children : NULL,
244 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
245 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
247 /* CLAWS: add subject to relation (without prefix) */
248 if (prefs_common.thread_by_subject) {
249 subject_relation_insert(subject_relation, node);
253 /* complete the unfinished threads */
254 for (node = root->children; node != NULL; ) {
257 msginfo = (MsgInfo *)node->data;
258 if (msginfo->inreplyto) {
259 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
260 /* node should not be the parent, and node should not
261 be an ancestor of parent (circular reference) */
262 if (parent && parent != node &&
263 !g_node_is_ancestor(node, parent)) {
266 (parent, parent->children, node);
272 if (prefs_common.thread_by_subject) {
273 for (node = root->children; node && node != NULL;) {
275 msginfo = (MsgInfo *) node->data;
277 parent = subject_relation_lookup(subject_relation, msginfo);
279 /* the node may already be threaded by IN-REPLY-TO, so go up
281 find the parent node */
282 if (parent != NULL) {
283 if (g_node_is_ancestor(node, parent))
291 g_node_append(parent, node);
298 g_relation_destroy(subject_relation);
299 g_hash_table_destroy(msgid_table);
304 void procmsg_move_messages(GSList *mlist)
306 GSList *cur, *movelist = NULL;
308 FolderItem *dest = NULL;
312 folder_item_update_freeze();
314 for (cur = mlist; cur != NULL; cur = cur->next) {
315 msginfo = (MsgInfo *)cur->data;
317 dest = msginfo->to_folder;
318 movelist = g_slist_append(movelist, msginfo);
319 } else if (dest == msginfo->to_folder) {
320 movelist = g_slist_append(movelist, msginfo);
322 folder_item_move_msgs(dest, movelist);
323 g_slist_free(movelist);
325 dest = msginfo->to_folder;
326 movelist = g_slist_append(movelist, msginfo);
328 procmsg_msginfo_set_to_folder(msginfo, NULL);
332 folder_item_move_msgs(dest, movelist);
333 g_slist_free(movelist);
336 folder_item_update_thaw();
339 void procmsg_copy_messages(GSList *mlist)
341 GSList *cur, *copylist = NULL;
343 FolderItem *dest = NULL;
347 folder_item_update_freeze();
349 for (cur = mlist; cur != NULL; cur = cur->next) {
350 msginfo = (MsgInfo *)cur->data;
352 dest = msginfo->to_folder;
353 copylist = g_slist_append(copylist, msginfo);
354 } else if (dest == msginfo->to_folder) {
355 copylist = g_slist_append(copylist, msginfo);
357 folder_item_copy_msgs(dest, copylist);
358 g_slist_free(copylist);
360 dest = msginfo->to_folder;
361 copylist = g_slist_append(copylist, msginfo);
363 procmsg_msginfo_set_to_folder(msginfo, NULL);
367 folder_item_copy_msgs(dest, copylist);
368 g_slist_free(copylist);
371 folder_item_update_thaw();
374 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
378 g_return_val_if_fail(msginfo != NULL, NULL);
380 if (msginfo->plaintext_file)
381 file = g_strdup(msginfo->plaintext_file);
383 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
389 gchar *procmsg_get_message_file(MsgInfo *msginfo)
391 gchar *filename = NULL;
393 g_return_val_if_fail(msginfo != NULL, NULL);
395 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
397 debug_print("can't fetch message %d\n", msginfo->msgnum);
402 GSList *procmsg_get_message_file_list(GSList *mlist)
404 GSList *file_list = NULL;
406 MsgFileInfo *fileinfo;
409 while (mlist != NULL) {
410 msginfo = (MsgInfo *)mlist->data;
411 file = procmsg_get_message_file(msginfo);
413 procmsg_message_file_list_free(file_list);
416 fileinfo = g_new(MsgFileInfo, 1);
417 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
418 fileinfo->file = file;
419 fileinfo->flags = g_new(MsgFlags, 1);
420 *fileinfo->flags = msginfo->flags;
421 file_list = g_slist_prepend(file_list, fileinfo);
425 file_list = g_slist_reverse(file_list);
430 void procmsg_message_file_list_free(MsgInfoList *file_list)
433 MsgFileInfo *fileinfo;
435 for (cur = file_list; cur != NULL; cur = cur->next) {
436 fileinfo = (MsgFileInfo *)cur->data;
437 procmsg_msginfo_free(fileinfo->msginfo);
438 g_free(fileinfo->file);
439 g_free(fileinfo->flags);
443 g_slist_free(file_list);
446 FILE *procmsg_open_message(MsgInfo *msginfo)
451 g_return_val_if_fail(msginfo != NULL, NULL);
453 file = procmsg_get_message_file_path(msginfo);
454 g_return_val_if_fail(file != NULL, NULL);
456 if (!is_file_exist(file)) {
458 file = procmsg_get_message_file(msginfo);
463 if ((fp = fopen(file, "rb")) == NULL) {
464 FILE_OP_ERROR(file, "fopen");
471 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
474 while (fgets(buf, sizeof(buf), fp) != NULL)
475 if (buf[0] == '\r' || buf[0] == '\n') break;
481 gboolean procmsg_msg_exist(MsgInfo *msginfo)
486 if (!msginfo) return FALSE;
488 path = folder_item_get_path(msginfo->folder);
490 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
496 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
497 PrefsFilterType type)
499 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
500 {"X-ML-Name:", NULL, TRUE},
501 {"X-List:", NULL, TRUE},
502 {"X-Mailing-list:", NULL, TRUE},
503 {"List-Id:", NULL, TRUE},
504 {"X-Sequence:", NULL, TRUE},
505 {NULL, NULL, FALSE}};
511 H_X_MAILING_LIST = 3,
518 g_return_if_fail(msginfo != NULL);
519 g_return_if_fail(header != NULL);
520 g_return_if_fail(key != NULL);
529 if ((fp = procmsg_open_message(msginfo)) == NULL)
531 procheader_get_header_fields(fp, hentry);
534 #define SET_FILTER_KEY(hstr, idx) \
536 *header = g_strdup(hstr); \
537 *key = hentry[idx].body; \
538 hentry[idx].body = NULL; \
541 if (hentry[H_X_BEENTHERE].body != NULL) {
542 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
543 } else if (hentry[H_X_ML_NAME].body != NULL) {
544 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
545 } else if (hentry[H_X_LIST].body != NULL) {
546 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
547 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
548 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
549 } else if (hentry[H_LIST_ID].body != NULL) {
550 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
551 extract_list_id_str(*key);
552 } else if (hentry[H_X_SEQUENCE].body != NULL) {
555 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
558 while (*p != '\0' && !isspace(*p)) p++;
559 while (isspace(*p)) p++;
566 } else if (msginfo->subject) {
567 *header = g_strdup("subject");
568 *key = g_strdup(msginfo->subject);
571 #undef SET_FILTER_KEY
573 g_free(hentry[H_X_BEENTHERE].body);
574 hentry[H_X_BEENTHERE].body = NULL;
575 g_free(hentry[H_X_ML_NAME].body);
576 hentry[H_X_ML_NAME].body = NULL;
577 g_free(hentry[H_X_LIST].body);
578 hentry[H_X_LIST].body = NULL;
579 g_free(hentry[H_X_MAILING_LIST].body);
580 hentry[H_X_MAILING_LIST].body = NULL;
581 g_free(hentry[H_LIST_ID].body);
582 hentry[H_LIST_ID].body = NULL;
586 *header = g_strdup("from");
587 *key = g_strdup(msginfo->from);
590 *header = g_strdup("to");
591 *key = g_strdup(msginfo->to);
593 case FILTER_BY_SUBJECT:
594 *header = g_strdup("subject");
595 *key = g_strdup(msginfo->subject);
602 void procmsg_empty_trash(void)
607 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
608 trash = FOLDER(cur->data)->trash;
609 if (trash && trash->total_msgs > 0)
610 folder_item_remove_all_msg(trash);
615 *\brief Send messages in queue
617 *\param queue Queue folder to process
618 *\param save_msgs Unused
620 *\return Number of messages sent, negative if an error occurred
621 * positive if no error occurred
623 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
625 gint ret = 1, count = 0;
629 queue = folder_get_default_queue();
630 g_return_val_if_fail(queue != NULL, -1);
632 folder_item_scan(queue);
633 list = folder_item_get_msg_list(queue);
635 for (elem = list; elem != NULL; elem = elem->next) {
639 msginfo = (MsgInfo *)(elem->data);
640 if (!MSG_IS_LOCKED(msginfo->flags)) {
641 file = folder_item_fetch_msg(queue, msginfo->msgnum);
643 if (procmsg_send_message_queue(file) < 0) {
644 g_warning("Sending queued message %d failed.\n",
649 * We save in procmsg_send_message_queue because
650 * we need the destination folder from the queue
654 procmsg_save_to_outbox
655 (queue->folder->outbox,
659 folder_item_remove_msg(queue, msginfo->msgnum);
664 /* FIXME: supposedly if only one message is locked, and queue
665 * is being flushed, the following free says something like
666 * "freeing msg ## in folder (nil)". */
667 procmsg_msginfo_free(msginfo);
673 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
678 if ((fp = fopen(in, "rb")) == NULL) {
679 FILE_OP_ERROR(in, "fopen");
682 if ((outfp = fopen(out, "wb")) == NULL) {
683 FILE_OP_ERROR(out, "fopen");
687 while (fgets(buf, sizeof(buf), fp) != NULL)
688 if (buf[0] == '\r' || buf[0] == '\n') break;
689 while (fgets(buf, sizeof(buf), fp) != NULL)
696 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
700 MsgInfo *msginfo, *tmp_msginfo;
701 MsgFlags flag = {0, 0};
703 debug_print("saving sent message...\n");
706 outbox = folder_get_default_outbox();
707 g_return_val_if_fail(outbox != NULL, -1);
709 /* remove queueing headers */
711 gchar tmp[MAXPATHLEN + 1];
713 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
714 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
716 if (procmsg_remove_special_headers(file, tmp) !=0)
719 folder_item_scan(outbox);
720 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
721 g_warning("can't save message\n");
726 folder_item_scan(outbox);
727 if ((num = folder_item_add_msg
728 (outbox, file, &flag, FALSE)) < 0) {
729 g_warning("can't save message\n");
734 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
735 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
736 if (msginfo != NULL) {
737 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
738 procmsg_msginfo_free(msginfo); /* refcnt-- */
739 /* tmp_msginfo == msginfo */
740 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
741 msginfo->returnreceiptto)) {
742 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
743 procmsg_msginfo_free(msginfo); /* refcnt-- */
750 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
752 static const gchar *def_cmd = "lpr %s";
759 g_return_if_fail(msginfo);
761 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
762 g_warning("Can't get text part\n");
766 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
767 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
769 if ((prfp = fopen(prtmp, "wb")) == NULL) {
770 FILE_OP_ERROR(prtmp, "fopen");
776 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
777 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
778 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
779 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
780 if (msginfo->newsgroups)
781 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
782 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
785 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
791 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
793 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
796 g_warning("Print command line is invalid: `%s'\n",
798 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
804 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
808 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
815 MsgInfo *procmsg_msginfo_new(void)
819 newmsginfo = g_new0(MsgInfo, 1);
820 newmsginfo->refcnt = 1;
825 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
829 if (msginfo == NULL) return NULL;
831 newmsginfo = g_new0(MsgInfo, 1);
833 newmsginfo->refcnt = 1;
835 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
836 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
837 g_strdup(msginfo->mmb) : NULL
861 MEMBDUP(dispositionnotificationto);
862 MEMBDUP(returnreceiptto);
866 MEMBCOPY(threadscore);
871 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
873 MsgInfo *full_msginfo;
876 if (msginfo == NULL) return NULL;
878 file = procmsg_get_message_file(msginfo);
880 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
884 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
886 if (!full_msginfo) return NULL;
888 /* CLAWS: make sure we add the missing members; see:
889 * procheader.c::procheader_get_headernames() */
891 msginfo->xface = g_strdup(full_msginfo->xface);
892 if (!msginfo->dispositionnotificationto)
893 msginfo->dispositionnotificationto =
894 g_strdup(full_msginfo->dispositionnotificationto);
895 if (!msginfo->returnreceiptto)
896 msginfo->returnreceiptto = g_strdup
897 (full_msginfo->returnreceiptto);
898 procmsg_msginfo_free(full_msginfo);
900 return procmsg_msginfo_new_ref(msginfo);
903 void procmsg_msginfo_free(MsgInfo *msginfo)
905 if (msginfo == NULL) return;
908 if (msginfo->refcnt > 0)
911 if (msginfo->to_folder) {
912 msginfo->to_folder->op_count--;
913 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
916 g_free(msginfo->fromspace);
917 g_free(msginfo->references);
918 g_free(msginfo->returnreceiptto);
919 g_free(msginfo->dispositionnotificationto);
920 g_free(msginfo->xface);
922 g_free(msginfo->fromname);
924 g_free(msginfo->date);
925 g_free(msginfo->from);
928 g_free(msginfo->newsgroups);
929 g_free(msginfo->subject);
930 g_free(msginfo->msgid);
931 g_free(msginfo->inreplyto);
932 g_free(msginfo->xref);
937 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
941 memusage += sizeof(MsgInfo);
942 if (msginfo->fromname)
943 memusage += strlen(msginfo->fromname);
945 memusage += strlen(msginfo->date);
947 memusage += strlen(msginfo->from);
949 memusage += strlen(msginfo->to);
951 memusage += strlen(msginfo->cc);
952 if (msginfo->newsgroups)
953 memusage += strlen(msginfo->newsgroups);
954 if (msginfo->subject)
955 memusage += strlen(msginfo->subject);
957 memusage += strlen(msginfo->msgid);
958 if (msginfo->inreplyto)
959 memusage += strlen(msginfo->inreplyto);
961 memusage += strlen(msginfo->xface);
962 if (msginfo->dispositionnotificationto)
963 memusage += strlen(msginfo->dispositionnotificationto);
964 if (msginfo->returnreceiptto)
965 memusage += strlen(msginfo->returnreceiptto);
966 if (msginfo->references)
967 memusage += strlen(msginfo->references);
968 if (msginfo->fromspace)
969 memusage += strlen(msginfo->fromspace);
974 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
976 const MsgInfo *msginfo1 = a;
977 const MsgInfo *msginfo2 = b;
984 return msginfo1->msgnum - msginfo2->msgnum;
993 Q_MAIL_ACCOUNT_ID = 4,
994 Q_NEWS_ACCOUNT_ID = 5,
995 Q_SAVE_COPY_FOLDER = 6,
996 Q_REPLY_MESSAGE_ID = 7,
1000 gint procmsg_send_message_queue(const gchar *file)
1002 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1003 {"SSV:", NULL, FALSE},
1004 {"R:", NULL, FALSE},
1005 {"NG:", NULL, FALSE},
1006 {"MAID:", NULL, FALSE},
1007 {"NAID:", NULL, FALSE},
1008 {"SCF:", NULL, FALSE},
1009 {"RMID:", NULL, FALSE},
1010 {"FMID:", NULL, FALSE},
1011 {NULL, NULL, FALSE}};
1014 gint mailval = 0, newsval = 0;
1016 gchar *smtpserver = NULL;
1017 GSList *to_list = NULL;
1018 GSList *newsgroup_list = NULL;
1019 gchar *savecopyfolder = NULL;
1020 gchar *replymessageid = NULL;
1021 gchar *fwdmessageid = NULL;
1022 gchar buf[BUFFSIZE];
1024 PrefsAccount *mailac = NULL, *newsac = NULL;
1027 g_return_val_if_fail(file != NULL, -1);
1029 if ((fp = fopen(file, "rb")) == NULL) {
1030 FILE_OP_ERROR(file, "fopen");
1034 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1036 gchar *p = buf + strlen(qentry[hnum].name);
1040 if (!from) from = g_strdup(p);
1043 if (!smtpserver) smtpserver = g_strdup(p);
1046 to_list = address_list_append(to_list, p);
1049 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1051 case Q_MAIL_ACCOUNT_ID:
1052 mailac = account_find_from_id(atoi(p));
1054 case Q_NEWS_ACCOUNT_ID:
1055 newsac = account_find_from_id(atoi(p));
1057 case Q_SAVE_COPY_FOLDER:
1058 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1060 case Q_REPLY_MESSAGE_ID:
1061 if (!replymessageid) replymessageid = g_strdup(p);
1063 case Q_FWD_MESSAGE_ID:
1064 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1068 filepos = ftell(fp);
1071 debug_print("Sending message by mail\n");
1073 g_warning("Queued message header is broken.\n");
1075 } else if (mailac && mailac->use_mail_command &&
1076 mailac->mail_command && (* mailac->mail_command)) {
1077 mailval = send_message_local(mailac->mail_command, fp);
1081 mailac = account_find_from_smtp_server(from, smtpserver);
1083 g_warning("Account not found. "
1084 "Using current account...\n");
1085 mailac = cur_account;
1090 mailval = send_message_smtp(mailac, to_list, fp);
1092 PrefsAccount tmp_ac;
1094 g_warning("Account not found.\n");
1096 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1097 tmp_ac.address = from;
1098 tmp_ac.smtp_server = smtpserver;
1099 tmp_ac.smtpport = SMTP_PORT;
1100 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1105 fseek(fp, filepos, SEEK_SET);
1106 if (newsgroup_list && (newsval == 0)) {
1111 /* write to temporary file */
1112 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1113 G_DIR_SEPARATOR, (gint)file);
1114 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1115 FILE_OP_ERROR(tmp, "fopen");
1117 alertpanel_error(_("Could not create temporary file for news sending."));
1119 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1120 FILE_OP_ERROR(tmp, "chmod");
1121 g_warning("can't change file mode\n");
1124 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1125 if (fputs(buf, tmpfp) == EOF) {
1126 FILE_OP_ERROR(tmp, "fputs");
1128 alertpanel_error(_("Error when writing temporary file for news sending."));
1134 debug_print("Sending message by news\n");
1136 folder = FOLDER(newsac->folder);
1138 newsval = news_post(folder, tmp);
1140 alertpanel_error(_("Error occurred while posting the message to %s ."),
1141 newsac->nntp_server);
1149 slist_free_strings(to_list);
1150 g_slist_free(to_list);
1151 slist_free_strings(newsgroup_list);
1152 g_slist_free(newsgroup_list);
1157 /* save message to outbox */
1158 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1161 debug_print("saving sent message...\n");
1163 outbox = folder_find_item_from_identifier(savecopyfolder);
1165 outbox = folder_get_default_outbox();
1167 procmsg_save_to_outbox(outbox, file, TRUE);
1170 if (replymessageid != NULL || fwdmessageid != NULL) {
1174 if (replymessageid != NULL)
1175 tokens = g_strsplit(replymessageid, "\x7f", 0);
1177 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1178 item = folder_find_item_from_identifier(tokens[0]);
1180 /* check if queued message has valid folder and message id */
1181 if (item != NULL && tokens[2] != NULL) {
1184 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1186 /* check if referring message exists and has a message id */
1187 if ((msginfo != NULL) &&
1188 (msginfo->msgid != NULL) &&
1189 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1190 procmsg_msginfo_free(msginfo);
1194 if (msginfo == NULL) {
1195 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1198 if (msginfo != NULL) {
1199 if (replymessageid != NULL) {
1200 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1201 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1204 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1205 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1207 procmsg_msginfo_free(msginfo);
1213 g_free(savecopyfolder);
1214 g_free(replymessageid);
1215 g_free(fwdmessageid);
1217 return (newsval != 0 ? newsval : mailval);
1220 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1222 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1225 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1229 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1234 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1235 item->unread_msgs++;
1236 if (procmsg_msg_has_marked_parent(msginfo))
1237 item->unreadmarked_msgs++;
1240 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1241 item->unread_msgs--;
1242 if (procmsg_msg_has_marked_parent(msginfo))
1243 item->unreadmarked_msgs--;
1247 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1248 procmsg_update_unread_children(msginfo, TRUE);
1251 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1252 procmsg_update_unread_children(msginfo, FALSE);
1256 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1259 MsgInfoUpdate msginfo_update;
1260 MsgPermFlags perm_flags_new, perm_flags_old;
1262 g_return_if_fail(msginfo != NULL);
1263 item = msginfo->folder;
1264 g_return_if_fail(item != NULL);
1266 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1268 /* Perm Flags handling */
1269 perm_flags_old = msginfo->flags.perm_flags;
1270 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1271 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1272 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1275 if (perm_flags_old != perm_flags_new) {
1276 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1278 update_folder_msg_counts(item, msginfo, perm_flags_old);
1280 msginfo_update.msginfo = msginfo;
1281 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1282 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1283 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1286 /* Tmp flags hanlding */
1287 msginfo->flags.tmp_flags |= tmp_flags;
1290 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1293 MsgInfoUpdate msginfo_update;
1294 MsgPermFlags perm_flags_new, perm_flags_old;
1296 g_return_if_fail(msginfo != NULL);
1297 item = msginfo->folder;
1298 g_return_if_fail(item != NULL);
1300 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1302 /* Perm Flags handling */
1303 perm_flags_old = msginfo->flags.perm_flags;
1304 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1306 if (perm_flags_old != perm_flags_new) {
1307 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1309 update_folder_msg_counts(item, msginfo, perm_flags_old);
1311 msginfo_update.msginfo = msginfo;
1312 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1313 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1314 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1317 /* Tmp flags hanlding */
1318 msginfo->flags.tmp_flags &= ~tmp_flags;
1322 *\brief check for flags (e.g. mark) in prior msgs of current thread
1324 *\param info Current message
1325 *\param perm_flags Flags to be checked
1326 *\param parentmsgs Hash of prior msgs to avoid loops
1328 *\return gboolean TRUE if perm_flags are found
1330 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1331 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1335 g_return_val_if_fail(info != NULL, FALSE);
1337 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1338 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1340 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1341 procmsg_msginfo_free(tmp);
1343 } else if (tmp != NULL) {
1346 if (g_hash_table_lookup(parentmsgs, info)) {
1347 debug_print("loop detected: %s%c%d\n",
1348 folder_item_get_path(info->folder),
1349 G_DIR_SEPARATOR, info->msgnum);
1352 g_hash_table_insert(parentmsgs, info, "1");
1353 result = procmsg_msg_has_flagged_parent_real(
1354 tmp, perm_flags, parentmsgs);
1356 procmsg_msginfo_free(tmp);
1366 *\brief Callback for cleaning up hash of parentmsgs
1368 gboolean parentmsgs_hash_remove(gpointer key,
1376 *\brief Set up list of parentmsgs
1377 * See procmsg_msg_has_flagged_parent_real()
1379 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1382 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1384 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1385 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1386 g_hash_table_destroy(parentmsgs);
1391 *\brief Check if msgs prior in thread are marked
1392 * See procmsg_msg_has_flagged_parent_real()
1394 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1396 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1400 GSList *procmsg_find_children_func(MsgInfo *info,
1401 GSList *children, GSList *all)
1405 g_return_val_if_fail(info!=NULL, children);
1406 if (info->msgid == NULL)
1409 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1410 MsgInfo *tmp = (MsgInfo *)cur->data;
1411 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1412 /* Check if message is already in the list */
1413 if ((children == NULL) ||
1414 (g_slist_index(children, tmp) == -1)) {
1415 children = g_slist_prepend(children,
1416 procmsg_msginfo_new_ref(tmp));
1417 children = procmsg_find_children_func(tmp,
1426 GSList *procmsg_find_children (MsgInfo *info)
1431 g_return_val_if_fail(info!=NULL, NULL);
1432 all = folder_item_get_msg_list(info->folder);
1433 children = procmsg_find_children_func(info, NULL, all);
1434 if (children != NULL) {
1435 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1436 /* this will not free the used pointers
1437 created with procmsg_msginfo_new_ref */
1438 procmsg_msginfo_free((MsgInfo *)cur->data);
1446 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1448 GSList *children = procmsg_find_children(info);
1450 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1451 MsgInfo *tmp = (MsgInfo *)cur->data;
1452 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1454 info->folder->unreadmarked_msgs++;
1456 info->folder->unreadmarked_msgs--;
1457 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1459 procmsg_msginfo_free(tmp);
1461 g_slist_free(children);
1465 * Set the destination folder for a copy or move operation
1467 * \param msginfo The message which's destination folder is changed
1468 * \param to_folder The destination folder for the operation
1470 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1472 if(msginfo->to_folder != NULL) {
1473 msginfo->to_folder->op_count--;
1474 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1476 msginfo->to_folder = to_folder;
1477 if(to_folder != NULL) {
1478 to_folder->op_count++;
1479 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1484 * Apply filtering actions to the msginfo
1486 * \param msginfo The MsgInfo describing the message that should be filtered
1487 * \return TRUE if the message was moved and MsgInfo is now invalid,
1490 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1492 MailFilteringData mail_filtering_data;
1494 mail_filtering_data.msginfo = msginfo;
1495 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1498 /* filter if enabled in prefs or move to inbox if not */
1499 if((filtering_rules != NULL) &&
1500 filter_message_by_msginfo(filtering_rules, msginfo))