/* Notification plugin for Claws-Mail
* Copyright (C) 2005-2007 Holger Berndt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pluginconfig.h"
#include "folder.h"
#include "codeconv.h"
#include "notification_prefs.h"
#include "notification_core.h"
#include "notification_banner.h"
#include "notification_popup.h"
#include "notification_command.h"
#include "notification_lcdproc.h"
#include "notification_trayicon.h"
typedef struct {
GSList *collected_msgs;
GSList *folder_items;
gboolean unread_also;
gint max_msgs;
gint num_msgs;
} TraverseCollect;
static gboolean notification_traverse_collect(GNode*, gpointer);
static void notification_new_unnotified_do_msg(MsgInfo*);
static gboolean notification_traverse_hash_startup(GNode*, gpointer);
void notification_update_msg_counts(FolderItem *removed_item)
{
guint unread_msgs;
guint new_msgs;
guint unread_marked_msgs;
guint marked_msgs;
guint total_msgs;
folder_count_total_msgs(&new_msgs, &unread_msgs, &unread_marked_msgs,
&marked_msgs, &total_msgs);
if(removed_item) {
total_msgs -= removed_item->total_msgs;
new_msgs -= removed_item->new_msgs;
unread_msgs -= removed_item->unread_msgs;
}
#ifdef NOTIFICATION_LCDPROC
notification_update_lcdproc(new_msgs, unread_msgs, total_msgs);
#endif
#ifdef NOTIFICATION_TRAYICON
notification_update_trayicon(new_msgs, unread_msgs, unread_marked_msgs,
marked_msgs, total_msgs);
#endif
}
/* Replacement for the post-filtering hook:
Pseudocode by Colin:
hook on FOLDER_ITEM_UPDATE_HOOKLIST
if hook flags & F_ITEM_UPDATE_MSGCOUNT
scan mails (folder_item_get_msg_list)
if MSG_IS_NEW(msginfo->flags) and not in hashtable
notify()
add to hashtable
procmsg_msg_list_free
hook on MSGINFO_UPDATE_HOOKLIST
if hook flags & MSGINFO_UPDATE_FLAGS
if !MSG_IS_NEW(msginfo->flags)
remove from hashtable, it's now useless
*/
/* This hash table holds all mails that we already notified about,
and that still are marked as "new". The keys are the msgid's,
the values are just 1's stored in a pointer. */
static GHashTable *notified_hash = NULL;
/* Remove message from the notified_hash if
* - the message flags changed
* - the message is not new
* - the message is in the hash
*/
gboolean notification_notified_hash_msginfo_update(MsgInfoUpdate *msg_update)
{
g_return_val_if_fail(msg_update != NULL, FALSE);
if((msg_update->flags & MSGINFO_UPDATE_FLAGS) &&
!MSG_IS_NEW(msg_update->msginfo->flags)) {
MsgInfo *msg;
gchar *msgid;
msg = msg_update->msginfo;
if(msg->msgid)
msgid = msg->msgid;
else {
debug_print("Notification Plugin: Message has no message ID!\n");
msgid = "";
}
g_return_val_if_fail(msg != NULL, FALSE);
if(g_hash_table_lookup(notified_hash, msgid) != NULL) {
debug_print("Notification Plugin: Removing message id %s from hash "
"table\n", msgid);
g_hash_table_remove(notified_hash, msgid);
}
}
return FALSE;
}
/* On startup, mark all new mails as already notified
* (by including them in the hash) */
void notification_notified_hash_startup_init(void)
{
GList *folder_list, *walk;
Folder *folder;
if(!notified_hash) {
notified_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
debug_print("Notification Plugin: Hash table created\n");
}
folder_list = folder_get_list();
for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
folder = walk->data;
g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
notification_traverse_hash_startup, NULL);
}
}
static gboolean notification_traverse_hash_startup(GNode *node, gpointer data)
{
GSList *walk;
GSList *msg_list;
FolderItem *item = (FolderItem*) node->data;
gint new_msgs_left;
if(!(item->new_msgs))
return FALSE;
new_msgs_left = item->new_msgs;
msg_list = folder_item_get_msg_list(item);
for(walk = msg_list; walk; walk = g_slist_next(walk)) {
MsgInfo *msg = (MsgInfo*) walk->data;
if(MSG_IS_NEW(msg->flags)) {
gchar *msgid;
if(msg->msgid)
msgid = msg->msgid;
else {
debug_print("Notification Plugin: Message has no message ID!\n");
msgid = "";
}
/* If the message id is not yet in the hash, add it */
g_hash_table_insert(notified_hash, g_strdup(msgid),
GINT_TO_POINTER(1));
debug_print("Notification Plugin: Init: Added msg id %s to the hash\n",
msgid);
/* Decrement left count and check if we're already done */
new_msgs_left--;
if(new_msgs_left == 0)
break;
}
}
procmsg_msg_list_free(msg_list);
return FALSE;
}
void notification_notified_hash_free(void)
{
if(notified_hash) {
g_hash_table_destroy(notified_hash);
notified_hash = NULL;
debug_print("Notification Plugin: Hash table destroyed\n");
}
}
void notification_new_unnotified_msgs(FolderItemUpdateData *update_data)
{
GSList *msg_list, *walk;
g_return_if_fail(notified_hash != NULL);
msg_list = folder_item_get_msg_list(update_data->item);
for(walk = msg_list; walk; walk = g_slist_next(walk)) {
MsgInfo *msg;
msg = (MsgInfo*) walk->data;
if(MSG_IS_NEW(msg->flags)) {
gchar *msgid;
if(msg->msgid)
msgid = msg->msgid;
else {
debug_print("Notification Plugin: Message has not message ID!\n");
msgid = "";
}
debug_print("Notification Plugin: Found msg %s, "
"checking if it is in hash...\n", msgid);
/* Check if message is in hash table */
if(g_hash_table_lookup(notified_hash, msgid) != NULL)
debug_print("yes.\n");
else {
/* Add to hashtable */
g_hash_table_insert(notified_hash, g_strdup(msgid),
GINT_TO_POINTER(1));
debug_print("no, added to table.\n");
/* Do the notification */
notification_new_unnotified_do_msg(msg);
}
} /* msg is 'new' */
} /* for all messages */
procmsg_msg_list_free(msg_list);
}
static void notification_new_unnotified_do_msg(MsgInfo *msg)
{
#ifdef NOTIFICATION_POPUP
notification_popup_msg(msg);
#endif
#ifdef NOTIFICATION_COMMAND
notification_command_msg(msg);
#endif
#ifdef NOTIFICATION_TRAYICON
notification_trayicon_msg(msg);
#endif
}
/* If folders is not NULL, then consider only those folder items
* If max_msgs is not 0, stop after collecting msg_msgs messages
*/
GSList* notification_collect_msgs(gboolean unread_also, GSList *folder_items,
gint max_msgs)
{
GList *folder_list, *walk;
Folder *folder;
TraverseCollect collect_data;
collect_data.unread_also = unread_also;
collect_data.collected_msgs = NULL;
collect_data.folder_items = folder_items;
collect_data.max_msgs = max_msgs;
collect_data.num_msgs = 0;
folder_list = folder_get_list();
for(walk = folder_list; walk != NULL; walk = g_list_next(walk)) {
folder = walk->data;
g_node_traverse(folder->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
notification_traverse_collect, &collect_data);
}
return collect_data.collected_msgs;
}
void notification_collected_msgs_free(GSList *collected_msgs)
{
if(collected_msgs) {
GSList *walk;
for(walk = collected_msgs; walk != NULL; walk = g_slist_next(walk)) {
CollectedMsg *msg = walk->data;
if(msg->from)
g_free(msg->from);
if(msg->subject)
g_free(msg->subject);
if(msg->folderitem_name)
g_free(msg->folderitem_name);
g_free(msg);
}
g_slist_free(collected_msgs);
}
}
static gboolean notification_traverse_collect(GNode *node, gpointer data)
{
TraverseCollect *cdata = data;
FolderItem *item = node->data;
gchar *folder_id_cur;
/* Obey global folder type limitations */
if(!notify_include_folder_type(item->folder->klass->type,
item->folder->klass->uistr))
return FALSE;
/* If a folder_items list was given, check it first */
if((cdata->folder_items) && (item->path != NULL) &&
((folder_id_cur = folder_item_get_identifier(item)) != NULL)) {
FolderItem *list_item;
GSList *walk;
gchar *folder_id_list;
gboolean eq;
gboolean folder_in_list = FALSE;
for(walk = cdata->folder_items; walk != NULL; walk = g_slist_next(walk)) {
list_item = walk->data;
folder_id_list = folder_item_get_identifier(list_item);
eq = !strcmp2(folder_id_list,folder_id_cur);
g_free(folder_id_list);
if(eq) {
folder_in_list = TRUE;
break;
}
}
g_free(folder_id_cur);
if(!folder_in_list)
return FALSE;
}
if(item->new_msgs || (cdata->unread_also && item->unread_msgs)) {
GSList *msg_list = folder_item_get_msg_list(item);
GSList *walk;
for(walk = msg_list; walk != NULL; walk = g_slist_next(walk)) {
MsgInfo *msg_info = walk->data;
CollectedMsg *cmsg;
if((cdata->max_msgs != 0) && (cdata->num_msgs >= cdata->max_msgs))
return FALSE;
if(MSG_IS_NEW(msg_info->flags) ||
(MSG_IS_UNREAD(msg_info->flags) && cdata->unread_also)) {
cmsg = g_new(CollectedMsg, 1);
cmsg->from = g_strdup(msg_info->from ? msg_info->from : "");
cmsg->subject = g_strdup(msg_info->subject ? msg_info->subject : "");
if(msg_info->folder && msg_info->folder->name)
cmsg->folderitem_name = g_strdup(msg_info->folder->path);
else
cmsg->folderitem_name = g_strdup("");
cdata->collected_msgs = g_slist_prepend(cdata->collected_msgs, cmsg);
cdata->num_msgs++;
}
}
procmsg_msg_list_free(msg_list);
}
return FALSE;
}
gboolean notify_include_folder_type(FolderType ftype, gchar *uistr)
{
gboolean retval;
retval = FALSE;
switch(ftype) {
case F_MH:
case F_MBOX:
case F_MAILDIR:
case F_IMAP:
if(notify_config.include_mail)
retval = TRUE;
break;
case F_NEWS:
if(notify_config.include_news)
retval = TRUE;
break;
case F_UNKNOWN:
if(uistr == NULL)
retval = FALSE;
else if(!strcmp(uistr, "vCalendar")) {
if(notify_config.include_calendar)
retval = TRUE;
}
else if(!strcmp(uistr, "RSSyl")) {
if(notify_config.include_rss)
retval = TRUE;
}
else
debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
break;
default:
debug_print("Notification Plugin: Unknown folder type %d\n",ftype);
}
return retval;
}
#ifdef HAVE_LIBNOTIFY
#define STR_MAX_LEN 511
/* Returns a newly allocated string which needs to be freed */
gchar* notification_libnotify_sanitize_str(gchar *in)
{
gint i_out;
gchar tmp_str[STR_MAX_LEN+1];
if(in == NULL) return NULL;
i_out = 0;
while(*in) {
if(*in == '<') {
if(i_out+3 >= STR_MAX_LEN) break;
memcpy(&(tmp_str[i_out]),"<",4);
in++; i_out += 4;
}
else if(*in == '>') {
if(i_out+3 >= STR_MAX_LEN) break;
memcpy(&(tmp_str[i_out]),">",4);
in++; i_out += 4;
}
else if(*in == '&') {
if(i_out+4 >= STR_MAX_LEN) break;
memcpy(&(tmp_str[i_out]),"&",5);
in++; i_out += 5;
}
else {
if(i_out >= STR_MAX_LEN) break;
tmp_str[i_out++] = *in++;
}
}
tmp_str[i_out] = '\0';
return strdup(tmp_str);
}
gchar* notification_validate_utf8_str(gchar *text)
{
gchar *utf8_str = NULL;
if(!g_utf8_validate(text, -1, NULL)) {
debug_print("Notification plugin: String is not valid utf8, "
"trying to fix it...\n");
/* fix it */
utf8_str = conv_codeset_strdup(text,
conv_get_locale_charset_str_no_utf8(),
CS_INTERNAL);
/* check if the fix worked */
if(utf8_str == NULL || !g_utf8_validate(utf8_str, -1, NULL)) {
debug_print("Notification plugin: String is still not valid utf8, "
"sanitizing...\n");
utf8_str = g_malloc(strlen(text)*2+1);
conv_localetodisp(utf8_str, strlen(text)*2+1, text);
}
}
else {
debug_print("Notification plugin: String is valid utf8\n");
utf8_str = g_strdup(text);
}
return utf8_str;
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1