Logo Search packages:      
Sourcecode: pan version File versions  Download package

article-actions.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Pan - A Newsreader for Gtk+
 * Copyright (C) 2002  Charles Kerr <charles@rebelbase.com>
 *
 * 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; version 2 of the License.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <glib.h>

#include <string.h>

#include <pan/base/acache.h>
#include <pan/base/argset.h>
#include <pan/base/article.h>
#include <pan/base/debug.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/gnksa.h>
#include <pan/base/group.h>
#include <pan/base/log.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/serverlist.h>
#include <pan/base/status-item.h>

#include <pan/filters/filter-aggregate.h>
#include <pan/filters/filter-phrase.h>
#include <pan/filters/filter-score.h>
#include <pan/filters/score.h>

#include <pan/article-actions.h>
#include <pan/articlelist.h>
#include <pan/message-window.h>
#include <pan/nntp.h>
#include <pan/queue.h>
#include <pan/save-ui.h>
#include <pan/task.h>
#include <pan/task-bodies.h>
#include <pan/task-cancel.h>
#include <pan/task-save.h>
#include <pan/util.h>

/***
****  Finding a match in pan.sent
***/

typedef struct
{
      GMimeMessage * message;
      char * body;
      GMimeMessage * match;
}
FindMatchingStruct;

static void
find_matching_sent_foreach (GMimeMessage * compare, gpointer user_data)
{
      char * body;
      gboolean is_html;
      FindMatchingStruct * data = (FindMatchingStruct*) user_data;

      /* do we already have a match? */
      if (data->match != NULL)
            return;

      /* comparison #1: subject must match */
      if (pan_strcmp (g_mime_message_get_subject(compare), g_mime_message_get_subject(data->message)))
            return;

      /* comparison #2: author address must match */
      if (pan_strcmp (g_mime_message_get_sender(compare), g_mime_message_get_sender(data->message)))
            return;

      /* comparison #3: body must match, if we have one */
      body = g_mime_message_get_body (compare, TRUE, &is_html);
      if (body!=NULL && data->body!=NULL) {
            g_strstrip (body);
            if (!strcmp (body, data->body)) {
                  g_object_ref (compare);
                  data->match = compare;
                  g_free (body);
            }
      }
}

/**
 * Find a matching article without comparing by message-id.
 * This is because many news servers alter the message-id of
 * an article being posted, so we can't rely on searching by
 * the message-id that Pan generated.
 */
static GMimeMessage*
find_matching_sent (GMimeMessage* message)
{
      gboolean is_html;
      FindMatchingStruct data;
      debug_enter ("find_matching_sent_article");

      /* sanity clause */
      g_return_val_if_fail (GMIME_IS_MESSAGE(message), NULL);

      /* find the match */
      data.message = message;
      data.body = g_strstrip (g_mime_message_get_body (data.message, TRUE, &is_html));
      data.match = NULL;
      acache_path_foreach (PAN_SENT.str, find_matching_sent_foreach, &data);
      g_free (data.body);
      return data.match;
}

/***
****
***/

void
article_cancel (Server * server, GMimeMessage * message)
{
      GMimeMessage * ours = find_matching_sent (message);

      if (ours != NULL)
            queue_add (TASK(task_cancel_new (server, ours)));
      else
            log_add_va (LOG_ERROR|LOG_URGENT,
                        _("Unable to cancel article: Couldn't find matching article in folder `pan.sent'!"));
}

/****
*****
****/

void
article_supersede (GMimeMessage * message)
{
      GMimeMessage * ours;
      debug_enter ("article_supersede");

      /* sanity clause */
      g_return_if_fail (GMIME_IS_MESSAGE (message));

      /* find the original copy so that the headers will match. */
      ours = find_matching_sent (message);
      if (ours == NULL)
      {
            log_add_va (LOG_ERROR|LOG_URGENT,
                        _("Unable to supersede article: Couldn't find matching article in folder `pan.sent'!"));
      }
      else
      {
            char       * domain;
            char       * pch;
            const char * old_message_id;
            char       * new_message_id;

            /* get the new message-id */
            old_message_id = g_mime_message_get_message_id (ours);
            pch = g_strdup (old_message_id);
            domain = strchr (pch, '@');
            if (domain)
            {
                  size_t len = strlen (++domain);
                  if (domain[len-1] == '>')
                        domain[len-1]='\0';
            }
            new_message_id = gnksa_generate_message_id (domain);

            /* update the headers for superseding */
            g_mime_message_set_header (ours, HEADER_SUPERSEDES, old_message_id);
            g_mime_message_set_header (ours, HEADER_MESSAGE_ID, new_message_id);

            /* edit the message */
            message_edit_window (ours);

            /* cleanup */
            g_free (new_message_id);
            g_free (pch);
      }

      debug_exit ("article_supersede");
}

/****
*****
****/

static void
article_copy_articles_to_folder_cb (gpointer obj, gpointer arg, gpointer data)
{
      int status;
      Task * task;
      Group * folder;
      ArgSet * args;
      char * acache_key;
      debug_enter ("article_copy_articles_to_folder_cb");

      /* sanity clause */
      g_return_if_fail (obj!=NULL);
      args = (ArgSet*) data;
      g_return_if_fail (args!=NULL);
      acache_key = (char*) argset_get (args, 0);
      g_return_if_fail (is_nonempty_string (acache_key));
      folder = (Group*) argset_get (args, 1);
      g_return_if_fail (group_is_valid(folder));
      g_return_if_fail (group_is_folder(folder));

      /* pump argument list */
      status = GPOINTER_TO_INT(arg);
      task = TASK(obj);

      /* copy them over if the task succeeded ... */
      if (status == TASK_OK)
      {
            guint i;
            for (i=0; i<task->identifiers->len; ++i)
            {
                  MessageIdentifier * mid = MESSAGE_IDENTIFIER (g_ptr_array_index (task->identifiers, i));
                  const PString * id = &mid->message_id;
                  GMimeMessage * message = acache_get_message (acache_key, &id, 1);
                  folder_add_message (folder, message);
                  g_object_unref (message);
            }
      }

      /* cleanup */
      g_free (acache_key);
      argset_free (args);
      debug_exit ("articlelist_article_to_folder_cb");
}

gboolean
article_copy_articles_to_folder (Group * folder, const Article ** articles, int qty)
{
      int i;
      int missing_qty;
      MessageIdentifier ** missing;
      debug_enter ("article_copy_articles_to_folder");

      /* sanity clause */
      g_return_val_if_fail (folder!=NULL, FALSE);
      g_return_val_if_fail (group_is_folder(folder), FALSE);
      g_return_val_if_fail (articles!=NULL, FALSE);
      g_return_val_if_fail (qty>0, FALSE);
      g_return_val_if_fail (articles_are_valid(articles, qty), FALSE);

      /* copy anything that's already cached, and make a list of those not cached */
      missing = g_newa (MessageIdentifier*, qty);
      missing_qty = 0;
      for (i=0; i<qty; ++i)
      {
            const Article * article = articles[i];
            const char * acache_key = group_get_acache_key (article->group);
            MessageIdentifier * mid = message_identifier_new_from_article (article);
            const PString * id = &mid->message_id;
            GMimeMessage * message = acache_get_message (acache_key, &id, 1);

            if (message == NULL)
                  missing[missing_qty++] = mid;
            else
            {
                  folder_add_message (folder, message);
                  g_object_unref (message);
                  g_object_unref (mid);
            }
      }

      /* queue anything that's missing */
      if (missing_qty != 0)
      {
            Task * task = TASK (task_bodies_new (articles[0]->group->server, missing, missing_qty));

            if (task != NULL)
            {
                  const char * source_key = group_get_acache_key (articles[0]->group);
                  ArgSet * args = argset_new2 (g_strdup(source_key), folder);
                  pan_callback_add (task->task_ran_callback, article_copy_articles_to_folder_cb, args);
                  queue_add (task);
            }
      }

      /* cleanup */
      for (i=0; i<missing_qty; ++i)
            g_object_unref (missing[i]);
      debug_exit ("article_copy_articles_to_folder");
      return TRUE;
}

/***
****
****
****   SAVING ARTICLES / OPENING ATTACHMENTS
****
****
***/

static void
save_attachments_impl (Article ** articles, guint len, gpointer unused)
{
      int i;
      GSList * tasks = NULL;
      debug_enter ("save_attachments_impl");

      g_return_if_fail (articles_are_valid ((const Article**)articles, len));

      for (i=0; i<len; ++i)
      {
            GSList * l;
            Article * a = articles[i];
            PanObject * task;
            GPtrArray * tmp = g_ptr_array_sized_new (64);

            /* build an array of this article and all its children. */
            g_ptr_array_add (tmp, a);
            for (l=a->threads; l!=NULL; l=l->next)
                  g_ptr_array_add (tmp, l->data);

            /* queue for decode */
            task_save_weed_duplicates (tmp);
            task = task_save_new_from_articles ((const Article**)tmp->pdata, tmp->len);
            task_save_set_attachments (TASK_SAVE(task), NULL, NULL);
            tasks = g_slist_prepend (tasks, task);

            /* cleanup */
            g_ptr_array_free (tmp, TRUE);
      }

      if (tasks != NULL)
      {
            tasks = g_slist_reverse (tasks);
            queue_insert_tasks (tasks, -1);
            tasks = NULL;
      }

      /* cleanup this loop */
      debug_exit ("save_attachments_impl");
}

void
article_action_selected_save_attachments (void)
{
      debug_enter ("article_action_selected_save_attachments");

      header_pane_forall_selected (save_attachments_impl, NULL, FALSE);

      debug_exit ("article_action_selected_save_attachments");
}

typedef struct
{
      Group * group;
      GSList * list;
}
SelectedSaveAsStruct;

static void
add_article_to_gslist (Article * article, gpointer user_data)
{
      SelectedSaveAsStruct * data = (SelectedSaveAsStruct*) user_data;

      /* get the first group */
      if (data->group == NULL)
            data->group = article->group;

      /* append this article and its children to the gslist of articles */
      data->list = g_slist_append (data->list, article);
      if (article->parts > 1)
            data->list = g_slist_concat (data->list,  g_slist_copy(article->threads));
}

void
article_action_selected_manual_decode (void)
{
      SelectedSaveAsStruct data;
      data.group = NULL;
      data.list = NULL;

      /* build the list of articles */
      header_pane_foreach_selected (add_article_to_gslist, &data);

      /* if any articles are selected, save them */
      if (data.list != NULL) {
            GSList * slist_of_slists = g_slist_append (NULL, data.list);
            manual_decode_attachment_as (slist_of_slists, data.group->download_dir);
            /* save_attach_as owns slist_of_slists now */
      }
}

static void
add_article_gslist_to_gslist (Article * article, gpointer user_data)
{
      GSList * l = NULL;
      SelectedSaveAsStruct * data = (SelectedSaveAsStruct*) user_data;

      /* get the first group */
      if (data->group == NULL)
            data->group = article->group;

      /* build a new gslist of this node and its children */
      if (article->parts > 1)
            l = g_slist_copy (article->threads);
      l = g_slist_prepend (l, article);

      /* add the new gslist to the gslist-of-gslists-of-articles */
      data->list = g_slist_prepend (data->list, l);
}

void
article_action_selected_save_as (void)
{
      SelectedSaveAsStruct data;
      data.group = NULL;
      data.list = NULL;

      /* build the list of articles */
      header_pane_foreach_selected (add_article_gslist_to_gslist, &data);

      /* if any articles are selected, save them */
      if (data.list != NULL) {
            data.list = g_slist_reverse (data.list);
            save_attachment_as (data.list, data.group->download_dir);
            /* save_attach_as owns data.list now */
      }
}

/***
****   DELETING ARTICLES
***/

static void
add_articles_and_multiparts_to_array (Article * article, gpointer user_data)
{
      GPtrArray * articles = (GPtrArray *) user_data;

      /* add the article */
      g_ptr_array_add (articles, article);

      /* if it's the first in a multipart, add the other parts */
      if (article->part==1 && article->parts>1) {
            GSList * l;
            for (l=article->threads; l!=NULL; l=l->next)
                  if (ARTICLE(l->data)->part > 1)
                        g_ptr_array_add (articles, l->data);
      }
}

void
article_action_delete_articles (Article ** articles, guint article_qty, gpointer unused)
{
      debug_enter ("article_action_delete_articles");

      /* delete the articles */
      if (article_qty)
      {
            int i;
            Group * group;
            const PString ** ids = g_newa (const PString*, article_qty);
            MessageIdentifier ** mids;

            /* remove the headers */
            mids = g_new (MessageIdentifier*, article_qty);
            for (i=0; i<article_qty; ++i)
                  mids[i] = message_identifier_new_from_article (articles[i]);
            message_identifiers_delete ((const MessageIdentifier**)mids, article_qty, SERVER_GROUPS_SUBSCRIBED);

            /* remove the cached bodies */
            for (i=0; i<article_qty; ++i)
                  ids[i] = &mids[i]->message_id;
                  group = articles[0]->group;
            acache_expire_messages (group_get_acache_key(group), ids, article_qty);

            /* cleanup */
            for (i=0; i<article_qty; ++i)
                  g_object_unref (mids[i]);
            g_free (mids);
      }

      debug_exit ("article_action_delete_articles");
}

void
article_action_delete_selected_articles (void)
{
      GPtrArray * articles;
      debug_enter ("article_action_delete_selected_articles");

      /* if it's a multipart, delete the children too */
      articles = g_ptr_array_new ();
      header_pane_foreach_active (add_articles_and_multiparts_to_array, articles);

      /* any articles to delete? */
      if (articles->len)
            article_action_delete_articles ((Article**)articles->pdata, articles->len, NULL);

      /* cleanup */
      g_ptr_array_free (articles, TRUE);
      debug_exit ("article_action_delete_selected_articles");
}

static void
edit_article (Article * article, gpointer unused)
{
      MessageIdentifier * mid = NULL;
      GMimeMessage * message = NULL;

      if (article != NULL)
            mid = message_identifier_new_from_article (article);
      if (mid != NULL) {
            const PString * id = &mid->message_id;
            message = acache_get_message (group_get_acache_key(article->group), &id, 1);
      }
      if (message != NULL)
            message_edit_window (message);

      g_object_unref (mid);
}

void
article_action_edit_selected (void)
{
      header_pane_first_selected (edit_article, NULL);
}

/***
****
***/

enum {
      DO_WATCH,
      DO_IGNORE,
      DO_LIST
};

static void
article_action_something_thread (const Article * article, int action)
{
      ScoreAddItem add_items[2];
      char * section_name;
      char * score_name;
      char * thread_message_id;
      struct {
            int score;
            const char * verb;
      } do_list[DO_LIST] = {
            { 9999, "watch" },
            { -9999, "ignore" }
      };

      /* sanity checking */
      g_return_if_fail (article_is_valid (article));
      g_return_if_fail (action==DO_WATCH || action==DO_IGNORE);

      /* get the new score's name */
      thread_message_id = article_get_thread_message_id (article);
      score_name = g_strdup_printf ("%s thread \"%s\"",
            do_list[action].verb,
            article_get_subject (article_get_root (article)));

      /* if the article's Message-ID is the top article, 
       * the article's Referencess contains the top article,
       * set the score to watch/ignore */

      section_name = score_create_section_str(article);
      add_items[0].on = TRUE;
      add_items[0].negate = FALSE;
      add_items[0].key = HEADER_MESSAGE_ID;
      add_items[0].value = filter_phrase_create_regex (thread_message_id, PHRASE_MATCH_IS);
      add_items[1].on = TRUE;
      add_items[1].negate = FALSE;
      add_items[1].key = HEADER_REFERENCES;
      add_items[1].value = filter_phrase_create_regex (thread_message_id, PHRASE_MATCH_STARTS_WITH);
      score_add (score_name, section_name, do_list[action].score, TRUE, 31, AGGREGATE_TYPE_OR, add_items, 2, TRUE);

      /* cleanup */
      g_free (add_items[1].value);
      g_free (add_items[0].value);
      g_free (score_name);
      g_free (section_name);
      g_free (thread_message_id);
}

void
article_action_watch_thread (const Article * article)
{
      article_action_something_thread (article, DO_WATCH);
}

static void
watch_article (Article * article, gpointer user_data)
{
      article_action_watch_thread (article);
}
void
article_action_watch_selected_thread (void)
{
      header_pane_first_selected (watch_article, NULL);
}

void
article_action_ignore_thread (const Article * article)
{
      article_action_something_thread (article, DO_IGNORE);
}
static void
ignore_article (Article * article, gpointer user_data)
{
      article_action_ignore_thread (article);
}
void
article_action_ignore_selected_thread (void)
{
      header_pane_first_selected (ignore_article, NULL);
}

/**
***  Mark read/unread
**/

static void
header_pane_mark_articles_nolock (Article ** articles, guint article_qty, gpointer user_data)
{
      const gboolean read = user_data!=NULL;

      articles_set_read (articles, article_qty, read);
}

void
article_action_mark_selected_unread (void)
{
      header_pane_forall_active (header_pane_mark_articles_nolock, GINT_TO_POINTER(FALSE));
}
void 
article_action_mark_selected_read (void)
{
      header_pane_forall_active (header_pane_mark_articles_nolock, GINT_TO_POINTER(TRUE));
}

/**
***  Download
**/

static void
queue_task_bodies (Article ** articles, guint article_qty, gpointer user_data)
{
      queue_add (TASK(task_bodies_new_from_articles ((const Article**)articles, article_qty)));
}
void
article_action_download_selected (void)
{
      header_pane_forall_active (queue_task_bodies, NULL);
}

/**
***  Cancel / Supersede
**/

static void
cancel_article (Article * article, gpointer user_data)
{
      const PString * id = &article->message_id;
      GMimeMessage * message = acache_get_message (group_get_acache_key(article->group), &id, 1);

      if (message == NULL)
            log_add (LOG_ERROR|LOG_URGENT, _("You must download the article before you can cancel it."));
      else {
            article_cancel (article->group->server, message);
            g_object_unref (message);
      }
}
void
article_action_cancel_selected (void)
{
      header_pane_first_selected (cancel_article, NULL);
}

static void
supersede_article (Article * article, gpointer user_data)
{
      const PString * id = &article->message_id;
      GMimeMessage * message = acache_get_message (group_get_acache_key(article->group), &id, 1);

      if (message == NULL)
            log_add (LOG_ERROR|LOG_URGENT, _("You must download the article before you can supersede it."));
      else {
            article_supersede (message);
            g_object_unref (message);
      }
}

void
article_action_supersede_selected (void)
{
      header_pane_first_selected (supersede_article, NULL);
}

Generated by  Doxygen 1.6.0   Back to index