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

task-save.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
 */

/*********************
**********************  Includes
*********************/

#include <config.h>

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>

#include <glib.h>

#include <gmime/gmime-stream-file.h>

#include <pan/base/acache.h>
#include <pan/base/article.h>
#include <pan/base/base-prefs.h>
#include <pan/base/debug.h>
#include <pan/base/decode.h>
#include <pan/base/log.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/util-file.h>

#include <pan/globals.h>
#include <pan/nntp.h>
#include <pan/queue.h>
#include <pan/task-save.h>

/*********************
**********************  Defines / Enumerated types
*********************/

/*********************
**********************  Macros
*********************/

/*********************
**********************  Structures / Typedefs
*********************/

/*********************
**********************  Private Function Prototypes
*********************/

static void task_save_run_download (Task * task, PanSocket * sock);
static void task_save_run_decode (Task * task, PanSocket * sock);

static char* task_save_describe (const StatusItem* item);

/*********************
**********************  Variables
*********************/

/***********
************  Extern
***********/

/***********
************  Public
***********/

/***********
************  Private
***********/

/*********************
**********************  BEGINNING OF SOURCE
*********************/

static char*
task_save_describe (const StatusItem* item)
{
      const MessageIdentifier * mid;

      /* sanity checks */
      g_return_val_if_fail (item!=NULL, NULL);
      g_return_val_if_fail (TASK(item)->identifiers!=NULL, NULL);
      g_return_val_if_fail (TASK(item)->identifiers->len >= 1u, NULL);

            mid = MESSAGE_IDENTIFIER(g_ptr_array_index (TASK(item)->identifiers, 0));
        return g_strdup_printf (_("Saving \"%s\""), message_identifier_get_readable_name(mid));
}

/************
*************  PUBLIC ROUTINES
************/

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

char*
expand_download_dir (const char * dir, const char * groupname)
{
      char * retval = g_strdup (dir);

      if (is_nonempty_string(groupname))
      {
            if (strstr (dir, "%g") != NULL)
            {
                  replace_gstr (&retval, pan_substitute (retval, "%g", groupname));
            }

            if (strstr (dir, "%G") != NULL)
            {
                  char tmp[PATH_MAX], *pch;
                  g_snprintf (tmp, sizeof(tmp), "%s.", groupname);
                  for (pch=tmp; *pch; ++pch)
                        if (*pch == '.')
                              *pch = G_DIR_SEPARATOR;
                  replace_gstr (&retval, pan_substitute (retval, "%G", tmp));
            }
      }

      return retval;
}

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

static void
task_save_destructor (PanObject* object)
{
      gpointer p;
      const PString ** ids;
      Task * task = TASK(object);
      TaskSave * task_save = TASK_SAVE(object);
      debug_enter ("task_save_destructor");

      /* destruct the TaskSave parts */
        debug1 (DEBUG_PAN_OBJECT, "task_save destructor: %p", object);
      replace_gstr (&task_save->path_attachments, NULL);
      replace_gstr (&task_save->filename_attachments, NULL);
      replace_gstr (&task_save->path_bodies, NULL);
      replace_gstr (&task_save->filename_bodies, NULL);
      while ((p = g_queue_pop_head (task_save->download_mids)))
            g_object_unref (G_OBJECT(p));
      g_queue_free (task_save->download_mids);

      ids = message_identifiers_get_id_array ((const MessageIdentifier**)task->identifiers->pdata, task->identifiers->len);
      acache_checkin (ACACHE_DEFAULT_KEY, ids, task->identifiers->len);
      g_free (ids);

      /* destruct the superclass */
      task_destructor (PAN_OBJECT(object));
      debug_exit ("task_save_destructor");
}

PanObject*
task_save_new (Server              * server,
               MessageIdentifier  ** mids,
               int                   mid_qty)
{
      int i;
      int lines_total = 0;
      int lines_cached = 0;
      const PString ** ids;
      TaskSave * task;
      debug_enter ("task_save_new");

      /* sanity clause */
      g_return_val_if_fail (server_is_valid(server), NULL);
      g_return_val_if_fail (mid_qty>0, NULL);
      g_return_val_if_fail (message_identifiers_are_valid ((const MessageIdentifier**)mids, mid_qty), FALSE);

      /* checkout the article bodies */
      ids = message_identifiers_get_id_array ((const MessageIdentifier**)mids, mid_qty);
      acache_checkout (ACACHE_DEFAULT_KEY, ids, mid_qty);

      /* create the task */
            task = g_new0 (TaskSave, 1);
        debug1 (DEBUG_PAN_OBJECT, "task_save_new: %p", task);
      task_constructor (TASK(task),
                    task_save_destructor,
                    task_save_describe,
                    server, FALSE);
      task_add_identifiers (TASK(task), mids, mid_qty);

      /* create the task-save */
      TASK(task)->type = TASK_TYPE_SAVE;
      task->path_attachments = NULL;
      task->filename_attachments = NULL;
      task->path_bodies = NULL;
      task->filename_bodies = NULL;
      task->download_mids = g_queue_new ();
      for (i=0; i<mid_qty; ++i) {
            lines_total += mids[i]->line_qty;
            if (acache_has_message (ACACHE_DEFAULT_KEY, ids[i]))
                  lines_cached += mids[i]->line_qty;
            else {
                  g_object_ref (mids[i]);
                  g_queue_push_tail (task->download_mids, mids[i]);
            }
      }
      task->download_mids_qty = task->download_mids->length;

      /* set the task status */
      if (g_queue_is_empty (task->download_mids))
            task_state_set_work_need_work (&TASK(task)->state, task_save_run_decode);
      else
            task_state_set_work_need_socket (&TASK(task)->state, server, task_save_run_download);

      /* init the status-item */
      status_item_emit_init_steps (STATUS_ITEM(task), lines_total);
      status_item_emit_set_step (STATUS_ITEM(task), lines_cached);
      status_item_emit_status_va (STATUS_ITEM(task), _("Saving `%s'"), message_identifier_get_readable_name(mids[0]));

      /* return the task */
      debug_exit ("task_save_new");
      g_free (ids);
      return PAN_OBJECT(task);
}

PanObject*
task_save_new_from_articles     (const Article  ** articles,
                                 int               article_qty)
{
      int i;
      MessageIdentifier ** mids;
      PanObject * retval;
      debug_enter ("task_save_new_from_articles");

      /* sanity clause */
      g_return_val_if_fail (articles!=NULL, NULL);
      g_return_val_if_fail (article_qty>0, NULL);
      g_return_val_if_fail (articles_are_valid (articles, article_qty), NULL);

      /* build the task */
      mids = g_newa (MessageIdentifier*, article_qty);
      for (i=0; i<article_qty; ++i)
            mids[i] = message_identifier_new_from_article (articles[i]);
      retval = task_save_new (articles[0]->group->server, mids, article_qty);
      for (i=0; i<article_qty; ++i)
            g_object_unref (mids[i]);

      /* cleanup */
      debug_exit ("task_save_new_from_articles");
      return retval;
}


extern char*
expand_download_dir (const char * dir, const char * groupname);

void
task_save_set_attachments (TaskSave      * task,
                           const char    * path,
                           const char    * filename)
{
      MessageIdentifier * mid;
      const PString * group_name;
      debug_enter ("task_save_set_attachments");

      /* sanity clause */
      g_return_if_fail (task!=NULL);

      task->save_attachments = TRUE;
      replace_gstr (&task->path_attachments, g_strdup(path));
      replace_gstr (&task->filename_attachments, g_strdup(filename));

      mid = MESSAGE_IDENTIFIER (g_ptr_array_index (TASK(task)->identifiers, 0));
      group_name = message_identifier_get_primary_group (mid);

      /* if no path specified, fall back on Pan group and global defaults */
      if (!task->path_attachments) {
            Group * group = server_get_named_group (TASK(task)->server, group_name);
            if (group != NULL)
                  task->path_attachments = g_strdup (group->download_dir);
      }
      if (!task->path_attachments)
            task->path_attachments = g_strdup (download_dir);

      /* expand */
      replace_gstr (&task->path_attachments,
                    expand_download_dir (task->path_attachments, group_name->str));

      debug_exit ("task_save_set_attachments");
}
 
void
task_save_set_bodies (TaskSave      * task,
                      const char    * path,
                      const char    * filename)
{
      MessageIdentifier * mid;
      const PString * group_name;
      debug_enter ("task_save_set_bodies");

      g_return_if_fail (task!=NULL);

      task->save_bodies = TRUE;
      replace_gstr (&task->path_bodies, g_strdup(path));
      replace_gstr (&task->filename_bodies, g_strdup(filename));

      mid = MESSAGE_IDENTIFIER (g_ptr_array_index (TASK(task)->identifiers, 0));
      group_name = message_identifier_get_primary_group (mid);

      /* if no path specified, fall back on Pan group and global defaults */
      if (!task->path_bodies) {
            Group * group = server_get_named_group (TASK(task)->server, group_name);
            if (group != NULL)
                  task->path_bodies = g_strdup (group->download_dir);
      }
      if (!task->path_bodies)
            task->path_bodies = g_strdup (download_dir);

      /* expand */
      replace_gstr (&task->path_bodies,
                    expand_download_dir (task->path_bodies,  group_name->str));

      debug_exit ("task_save_set_bodies");
}

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

/* FIXME: this has a lot in common with decode.c; maybe promote to util-file */
static char*
get_unique_fname (const char * path, const char * fname)
{
      int i;
      GString * filename;

      /* create the unchanged filename */
      if (!is_nonempty_string (fname))
            fname = _("UNKNOWN");
      filename = g_string_new (fname);

      /* filter out directory characters */
      if (1) {
            const char * in;
            char * buf = g_malloc (strlen(fname)*2);
            char * out = buf;
            for (in=fname; *in; ++in) {
                  if (*in==G_DIR_SEPARATOR) {
                        *out++ = '_';
                  }
                  else if (*in=='\\') {
                        *out++ = '\\',
                        *out++ = '\\';
                  }
                  else {
                        *out++ = *in;
                  }
            }
            *out = '\0';
            g_string_assign (filename, buf);
            g_free (buf);
      }

      
      for (i=1;; ++i)
      {
            const char * front = filename->str;
            const char * lastdot = strrchr (front, '.');
            char * unique;
            char * lead;
            char * tail;

            if (lastdot == NULL) {
                  lead = g_strdup (front);
                  tail = g_strdup ("");
            } else {
                  lead = g_strndup (front, lastdot-front);
                  tail = g_strdup (lastdot);
            }

            if (i==1 && is_nonempty_string(path))
            {
                  unique = g_strdup_printf ("%s%c%s%s",
                                            path, G_DIR_SEPARATOR,
                                            lead, tail);
            }
            else if (i==1)
            {
                  unique = g_strdup_printf ("%s%s", lead, tail);
            }
            else if (is_nonempty_string(path))
            {
                  unique = g_strdup_printf ("%s%c%s_copy_%d%s",
                                           path, G_DIR_SEPARATOR,
                                           lead, i, tail);
            }
            else
            {
                  unique = g_strdup_printf ("%s_copy_%d%s", lead, i, tail);
            }

            /* cleanup */
            g_free (lead);
            g_free (tail);

            if (!pan_file_exists (unique)) {
                  g_string_assign (filename, unique);
                  g_free (unique);
                  break;
            }

            g_free (unique);
      }

      return pan_file_normalize_inplace (g_string_free (filename, FALSE));
}

/**
***
**/

static void
task_save_run_decode (Task * task, PanSocket * sock)
{
      TaskSave * task_save;
      TaskStateEnum retval = TASK_OK;

            task_save = TASK_SAVE(task);
      if (task_save->save_attachments)
      {
            gboolean decode_ok;
            decode_data dd;
            status_item_emit_status (STATUS_ITEM(task), _("Saving Attachments"));

            dd.server = task->server;
            dd.acache_key = ACACHE_DEFAULT_KEY;
            dd.mids = (MessageIdentifier**) task->identifiers->pdata;
            dd.mid_qty = task->identifiers->len;
            dd.subject = message_identifier_get_readable_name (dd.mids[0]);
            dd.item = STATUS_ITEM(task);
            dd.path = task_save->path_attachments;
            dd.filename = task_save->filename_attachments;
            decode_ok = decode_article (&dd);

            /* if we failed to decode something that we think
             * we should've been able to decode, then fail. */
            if (!decode_ok)
                  retval = TASK_FAIL;
      }

      if (task_save->save_bodies)
      {
            int i;
            gboolean bodies_ok = TRUE;
            status_item_emit_status (STATUS_ITEM(task), _("Saving Articles"));

            for (i=0; i<task->identifiers->len; ++i)
            {
                  gboolean body_ok = TRUE;
                  MessageIdentifier * mid;
                  GMimeMessage * message;
                  const PString * id;
                  char * fname;
                  char * path;

                  /* get the message */
                  mid = (MessageIdentifier*) g_ptr_array_index (task->identifiers, i);
                  id = &mid->message_id;
                  message = acache_get_message (ACACHE_DEFAULT_KEY, &id, 1);

                  /* get the filename */
                  fname = g_strdup (task_save->filename_bodies);
                  if (!is_nonempty_string(fname))
                        replace_gstr (&fname, g_strdup_printf ("%s.txt", message_identifier_get_readable_name(mid)));
                  replace_gstr (&fname, get_unique_fname (task_save->path_bodies, fname));

                  /* get the directory */
                  path = g_path_get_dirname (fname);
                  if (!pan_file_ensure_path_exists (path)) {
                        log_add_va (LOG_ERROR, _("Save Article can't access path \"%s\""), path);
                        body_ok = FALSE;
                  }

                  /* open the file for writing */
                  if (body_ok) {
                        FILE * fp;
                        errno = 0;
                        fp = fopen (fname, "w+");
                        if (fp == NULL) {
                              log_add_va (LOG_ERROR, _("Can't create file \"%s\" %s"), fname, g_strerror(errno));
                              body_ok = FALSE;
                        } else {
                              GMimeStream * stream = g_mime_stream_file_new (fp);
                              g_mime_message_write_to_stream (message, stream);
                              g_object_unref (stream);
                              log_add_va (LOG_INFO, _("Saved article body to \"%s\""), fname);
                        }
                  }

                  /* cleanup */
                  g_object_unref (message);
                  g_free (path);
                  g_free (fname);

                  if (!body_ok)
                        bodies_ok = FALSE;
            }

            if (!bodies_ok)
                  retval = TASK_FAIL;
      }

      task_state_set_health (&task->state, retval);

      if (retval == TASK_OK)
            task_state_set_work_completed (&task->state);
      else
            task_state_set_work_need_work (&task->state, task_save_run_decode);
}

static void
task_save_run_download (Task * task, PanSocket * sock)
{
      TaskStateEnum val;
      MessageIdentifier * mid;
      TaskSave * task_save = TASK_SAVE(task);
      debug_enter ("task_save_run");

      mid = MESSAGE_IDENTIFIER (g_queue_pop_head (task_save->download_mids));

      /* let the queue know about any more parts left to downloads */
      if (!g_queue_is_empty (task_save->download_mids)) {
            task_state_set_work_need_socket (&task->state, task->server, task_save_run_download);
            queue_wakeup ();
      }

      /* try to download the part... */
      val = nntp_article_download (STATUS_ITEM(task), sock, mid, &task->hint_abort, NNTP_VERBOSE_NEXT_STEP);
      task_state_set_health (&TASK(task)->state, val);
      if (val == TASK_OK) {
            g_object_unref (mid);
            if (!--task_save->download_mids_qty)
                  task_state_set_work_need_work (&task->state, task_save_run_decode);
      } else {
            g_queue_push_head (task_save->download_mids, mid);
            task_state_set_work_need_socket (&TASK(task)->state, task->server, task_save_run_download);
      }

      debug_exit ("task_save_run");
}


/*****
******
******  VALIDATE-AND-QUEUE or SELF-DESTRUCT
******
*****/

static void
weed_duplicate_parts (GPtrArray * articles)
{
      int i;

      for (i=0; i<articles->len; ++i)
      {
            Article * prev = i==0 ? NULL : ARTICLE (g_ptr_array_index (articles, i-1));
            Article * article = ARTICLE (g_ptr_array_index (articles, i));

            if (article->part == 0)
            {
                  g_ptr_array_remove (articles, article);
            }
            else if (prev && prev->part == article->part)
            {
                  gpointer removeme = NULL;
                  /* Select the better of the two assuming that more lines
                   * is better.  Otherwise go for the most recently posted,
                   * as it's possibly a repost to fix a broken post. */

                  if (prev->linecount > article->linecount)
                        removeme = article;
                  else if (prev->linecount < article->linecount)
                        removeme = prev;
                  else if (prev->date > article->date)
                        removeme = article;
                  else
                        removeme = prev;

                  if (removeme) {
                        g_ptr_array_remove (articles, removeme);
                        --i;
                  }
            }
      }
}


void
task_save_weed_duplicates (GPtrArray  * articles)
{
      const int length = articles->len;

      /**
       * (1) If we have a number of articles in the save list, and
       * (2) the number of parts we expected doesn't match it, and
       * (3) if we have a clue what the number of parts is supposed to be
       *     (we may not, if user didn't post with a [1/24]-style header),
       * then try to weed out the ones we don't want.
       */
      if (length > 1) {
            Article * a = ARTICLE (g_ptr_array_index (articles, 0));
            if (a->parts && length!=a->parts)
                  weed_duplicate_parts (articles);    
      }
}

Generated by  Doxygen 1.6.0   Back to index